You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

129 lines
4.0 KiB

import base64
import configparser
import hashlib
import hmac
import json
import logging
import pathlib
import flask
import persistqueue
import requests
import tweepy
config = configparser.ConfigParser()
config.read('settings.cfg')
app = flask.Flask(__name__)
app.logger.setLevel(logging.INFO)
app.config.update(
SERVER_NAME=config.get('Flask', 'server_name'),
SECRET_KEY=config.get('Flask', 'secret_key'),
ERROR_404_HELP=False,
JSONIFY_PRETTYPRINT_REGULAR=True
)
storage_path = config.get('General', 'storage_path')
consumer_key = config.get('Twitter', 'consumer_key')
consumer_secret = config.get('Twitter', 'consumer_secret')
callback_url = config.get('Twitter', 'callback_url')
webhook_url = config.get('Twitter', 'webhook_url')
environment_name = config.get('Twitter', 'environment_name')
def update_tokens(user_id, access_token):
path = pathlib.Path(storage_path, 'tokens.json')
path.touch(exist_ok=True)
with open(path, 'r+') as f:
try:
tokens = json.load(f)
except json.decoder.JSONDecodeError:
tokens = dict()
tokens.update({user_id: access_token})
f.seek(0)
json.dump(tokens, f, sort_keys=True, indent=4)
f.truncate()
def push_event(data):
queue = persistqueue.SQLiteQueue(storage_path)
queue.put(data)
queue.task_done()
def call_account_activity_api(api, method, endpoint, data=None):
url = 'https://{0}{1}/account_activity/all/{2}'.format(api.host, api.api_root, environment_name)
r = requests.request(method, '{0}/{1}'.format(url, endpoint), auth=api.auth.apply_auth(), data=data)
if not r.ok:
errors = r.json().get('errors')
if errors:
app.logger.error(errors.pop().get('message'))
return r
@app.route('/auth')
def auth():
handler = tweepy.OAuthHandler(consumer_key, consumer_secret, callback_url)
url = handler.get_authorization_url()
flask.session['request_token'] = handler.request_token
return flask.redirect(url)
@app.route('/callback')
def callback():
request_token = flask.session['request_token']
del flask.session['request_token']
handler = tweepy.OAuthHandler(consumer_key, consumer_secret, callback_url)
handler.request_token = request_token
try:
handler.get_access_token(flask.request.args.get('oauth_verifier'))
except tweepy.TweepError:
return 'Authentication failure', 500
api = tweepy.API(handler)
user = api.verify_credentials(include_entities=False, skip_status=True, include_email=False)
if not user:
return 'Authentication failure', 500
update_tokens(user.id_str, [handler.access_token, handler.access_token_secret])
# TODO: check if the webook already exists
if not call_account_activity_api(api, 'POST', 'webhooks.json', data=dict(url=webhook_url)).ok:
return 'Webhook registration failure', 500
# TODO: check if already subscribed
if not call_account_activity_api(api, 'POST', 'subscriptions.json').ok:
return 'Webhook subscription failure', 500
return 'Success', 200
@app.route('/webhook', methods=['GET'])
def webhook_get():
crc = flask.request.args.get('crc_token')
if not crc:
return 'Invalid request', 403
digest = hmac.new(consumer_secret.encode(), msg=crc.encode(), digestmod=hashlib.sha256).digest()
return dict(response_token='sha256={0}'.format(base64.b64encode(digest).decode())), 200
@app.route('/webhook', methods=['POST'])
def webhook_post():
def verify():
signature = flask.request.headers.get('X-Twitter-Webhooks-Signature')
if not signature:
return False
try:
crc = base64.b64decode(signature[7:])
digest = hmac.new(consumer_secret.encode(), msg=flask.request.get_data(), digestmod=hashlib.sha256).digest()
return hmac.compare_digest(digest, crc)
except base64.binascii.Error:
return False
if not verify():
return 'Invalid request signature', 403
push_event(flask.request.get_json())
return '', 200
if __name__ == '__main__':
app.run()