#!/usr/bin/python3 import base64 import configparser import hashlib import hmac import json import logging import os import pathlib import flask import persistqueue import tweepy import xmltodict from twitivity import Twitivity, TwitivityError config = configparser.ConfigParser() config.read(os.getenv('CHEDDAR_KNIGHT_CONFIG', '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()