import datetime import hashlib import json import os import random import string import dateutil.parser from requests_futures.sessions import FuturesSession TOPIC_URLS = { 'streams': 'https://api.twitch.tv/helix/streams?user_id={0}', 'followers': 'https://api.twitch.tv/helix/users/follows?first=1&to_id={0}', } class Twitch(object): @classmethod def subscribe(cls, app, subscriptions, lease_time): def read_leases(lease_file): result = dict( streams=dict(secret=None, valid_until=None, request_hash=None), followers=dict(secret=None, valid_until=None, request_hash=None)) try: with open(lease_file) as f: leases = json.load(f) except IOError: return result else: result.update(leases) return result def write_leases(lease_file, leases): with open(lease_file, 'w') as f: json.dump(leases, f, indent=4, sort_keys=True) def generate_secret(length): pool = string.ascii_letters + string.digits return ''.join(random.SystemRandom().choice(pool) for _ in range(length)) def sub(topic, secret): client_id = os.getenv('TWITCH_CLIENT_ID') channel_id = os.getenv('TWITCH_CHANNEL_ID') callback_url = os.getenv('CALLBACK_URL') session = FuturesSession() url = 'https://api.twitch.tv/helix/webhooks/hub' headers = {'Client-ID': client_id} data = { 'hub.mode': 'subscribe', 'hub.topic': TOPIC_URLS[topic].format(channel_id), 'hub.callback': callback_url, 'hub.lease_seconds': lease_time, 'hub.secret': secret, } h = '{0[hub.mode]}|{0[hub.topic]}|{0[hub.lease_seconds]}'.format(data) request_hash = hashlib.sha256(h.encode()).hexdigest() return session.post(url, headers=headers, data=data), request_hash with app.app_context(): app.logger.info('Refreshing Twitch subscriptions') lease_file = os.getenv('LEASE_FILE') leases = read_leases(lease_file) for topic, lease in leases.items(): if not lease['secret'] or not lease['valid_until']: remaining = 0 if lease['valid_until']: valid_until = dateutil.parser.parse(lease['valid_until']) remaining = (valid_until - datetime.datetime.utcnow()).total_seconds() if remaining < 3600: app.logger.info('Subscription "%s" expired, re-requesting', topic) secret = generate_secret(32) valid_until = datetime.datetime.utcnow() + datetime.timedelta(seconds=lease_time) request, request_hash = sub(topic, secret) if request.result().ok: lease['secret'] = secret lease['valid_until'] = valid_until.isoformat() lease['request_hash'] = request_hash write_leases(lease_file, leases) subscriptions.update(leases)