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.

162 lines
4.8 KiB

import base64
import configparser
import hashlib
import hmac
import json
import logging
import pathlib
import flask
import persistqueue
import tweepy
import xmltodict
from twitivity import Twitivity, TwitivityError
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')
yt_channel_id = config.get('YouTube', 'channel_id')
def update_twitter_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 setup_twitter_webhook(api):
twitivity = Twitivity(api, environment_name)
try:
webhook_id = twitivity.check_webhook()
except TwitivityError:
webhook_id = None
if not webhook_id:
try:
twitivity.register_webhook(webhook_url)
except TwitivityError as e:
app.logger.error(str(e))
return False
try:
twitivity.check_subscription()
except TwitivityError:
try:
twitivity.subscribe()
except TwitivityError as e:
app.logger.error(str(e))
return False
return True
def push_event(data):
queue = persistqueue.SQLiteQueue(storage_path)
queue.put(data)
queue.task_done()
@app.route('/twitter/auth')
def twitter_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('/twitter/callback')
def twitter_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_twitter_tokens(user.id_str, [handler.access_token, handler.access_token_secret])
if not setup_twitter_webhook(api):
return 'Webhook setup failure', 500
return 'Success', 200
@app.route('/twitter/webhook', methods=['GET'])
def twitter_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('/twitter/webhook', methods=['POST'])
def twitter_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(('twitter', flask.request.get_json()))
return '', 204
@app.route('/youtube/webhook', methods=['GET'])
def youtube_webhook_get():
mode = flask.request.args.get('hub.mode')
topic = flask.request.args.get('hub.topic')
challenge = flask.request.args.get('hub.challenge')
if (topic != 'https://www.youtube.com/xml/feeds/videos.xml?channel_id={0}'.format(yt_channel_id) or
mode not in ('subscribe', 'unsubscribe') or not challenge):
return 'Invalid request', 403
return challenge, 200
@app.route('/youtube/webhook', methods=['POST'])
def youtube_webhook_post():
data = flask.request.get_data()
if not data:
return '', 204
push_event(('youtube', xmltodict.parse(data)))
return '', 204
if __name__ == '__main__':
app.run()