Skip to content

Instantly share code, notes, and snippets.

@branw
Created May 11, 2020 18:23
Show Gist options
  • Save branw/63e792cc4ba4fc78370f1714f16e4fd3 to your computer and use it in GitHub Desktop.
Save branw/63e792cc4ba4fc78370f1714f16e4fd3 to your computer and use it in GitHub Desktop.
Old TextNow API and Cat Fact SMS bot (Dec. 2018) -- current APIs use reCaptcha (for web/Electron app) and SafetyNet (Android app)
from datetime import datetime, timedelta
from fuzzywuzzy import fuzz
import textnow
import random
import time
def matches(*accepted):
for value in accepted:
if fuzz.partial_ratio(text, value) > 80:
return True
return False
def send(*messages):
t.send_message(number, random.choice(messages))
t = textnow.TextNow('username', 'password')
recipients = set()
last_fact = {}
last_read = t.get_messages()['status']['latest_message_id']
while True:
# Handle all newly received messages
new_messages = t.get_messages(last_read)['messages']
for message in new_messages:
# Skip sent messages
if message['message_direction'] == 2:
continue
last_read = message['id']
number = message['e164_contact_value']
text = message['message']
print(f'From {number}: {text}')
recipients.add(number)
if matches('cancel', 'unsubscribe', 'stop'):
send(
"You've got to be kitten me! Do you really want to unsubscribe?",
"Sorry, please reply between our business hours of 9:00am-5:00pm M-F")
elif matches('help'):
send(
"No!")
else:
send('Command not recognized')
for number in recipients:
threshold = datetime.now() - timedelta(hours=1)
if number not in last_fact or last_fact[number] < threshold:
last_fact[number] = datetime.now()
print(number)
send(
"Did you know that all cats are born blind? The ability to see comes within the next couple of weeks.")
time.sleep(15)
import requests
import hashlib
import urllib
import json
class TextNow:
ROOT = 'http://api.textnow.me/api2.0'
SIGNATURE_NONCE = 'f8ab2ceca9163724b6d126aea9620339'
HEADERS = {
'User-Agent': 'TextNow 6.7.0.1 (Android SDK built for x86; Android OS 9.0; en_US)'
}
def __init__(self, username: str, password: str):
self.login(username, password)
def _sign(self, method, endpoint, params, data=None):
encoded_params = urllib.parse.urlencode(params, doseq=True)
message = f'{self.SIGNATURE_NONCE}{method}{endpoint}?{encoded_params}'
if data:
encoded_data = json.dumps(data, separators=(',', ':'))
message += encoded_data
params['signature'] = hashlib.md5(message.encode()).hexdigest()
def _post(self, endpoint, data, params={}):
params = {
'client_type': 'TN_ANDROID',
**params
}
self._sign('POST', endpoint, params, data)
data = {
'json': json.dumps(data, separators=(',', ':')).replace('\\\\', '\\')
}
req = requests.post(f'{self.ROOT}/{endpoint}', headers=self.HEADERS, params=params, data=data)
req.raise_for_status()
return req.json()
def _get(self, endpoint, params={}):
params = {
'client_type': 'TN_ANDROID',
**params
}
self._sign('GET', endpoint, params)
req = requests.get(f'{self.ROOT}/{endpoint}', headers=self.HEADERS, params=params)
req.raise_for_status()
return req.json()
def login(self, username: str, password: str):
resp = self._post('sessions', {
'password': password,
'username': username
})
self._id = resp['id']
self._username = resp['username']
def get_messages(self, start_message_id: int = 1, page_size: int = 30, get_all: int = 1):
return self._get(f'users/{self._username}/messages', {
'client_id': self._id,
'get_all': get_all,
'page_size': page_size,
'start_message_id': start_message_id,
})
def send_message(self, number: str, message: str):
return self._post(f'users/{self._username}/messages', {
'from_name': '',
'contact_type': '2',
'contact_value': number,
'message': message.replace('/', '\/'),
'to_name': '',
}, {
'client_id': self._id,
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment