Created
May 11, 2020 18:23
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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