import datetime import os import flask_restful.inputs from db import Video, Comment, Association, Emote from twitch import Twitch class Sync(object): @staticmethod def _get(d, *keys, default=None): try: result = None for key in keys: if result: if isinstance(result, list): result = result[key] else: result = result.get(key, default) else: result = d.get(key, default) return result except (KeyError, IndexError): return default @staticmethod def _to_datetime(val): if not val: return None result = flask_restful.inputs.datetime_from_iso8601(val) return result.astimezone(tz=datetime.timezone.utc).replace(tzinfo=None) @classmethod def perform(cls, app, db): app.logger.info('Starting synchronization') with app.app_context(): twitch = Twitch(os.getenv('TWITCH_CLIENT_ID'), os.getenv('TWITCH_OAUTH_TOKEN')) channel = os.getenv('TWITCH_CHANNEL_ID') updated = [] for vid in twitch.fetch_videos(channel): id = cls._get(vid, '_id', default='').lstrip('v') if not id: continue if cls._get(vid, 'status') == 'recording': continue q = db.session.query(Video).filter(Video.id == id) video = q.first() if not video: video = Video(id=id) created_at = cls._get(vid, 'created_at') updated_at = cls._to_datetime(cls._get(vid, 'updated_at', default=created_at)) created_at = cls._to_datetime(created_at) if video.updated_at and video.updated_at >= updated_at: continue updated.append(id) video.broadcast_type = cls._get(vid, 'broadcast_type') video.title = cls._get(vid, 'title') video.description = cls._get(vid, 'description') video.game = cls._get(vid, 'game') video.length = cls._get(vid, 'length') video.thumbnail_small = cls._get(vid, 'thumbnails', 'small', 0, 'url') video.thumbnail_medium = cls._get(vid, 'thumbnails', 'medium', 0, 'url') video.thumbnail_large = cls._get(vid, 'thumbnails', 'large', 0, 'url') video.created_at = created_at video.updated_at = updated_at video.recorded_at = cls._to_datetime(cls._get(vid, 'recorded_at')) video.published_at = cls._to_datetime(cls._get(vid, 'published_at')) db.session.add(video) db.session.commit() app.logger.info('Updated videos: %s', ', '.join(updated) if updated else 'NONE') for com in twitch.fetch_comments(updated): q = db.session.query(Video).filter(Video.id == cls._get(com, 'content_id')) video = q.first() if not video: continue id = cls._get(com, '_id') q = db.session.query(Comment).filter(Comment.id == id) comment = q.first() if not comment: comment = Comment(id=id) comment.commenter_id = cls._get(com, 'commenter', '_id') comment.commenter_name = cls._get(com, 'commenter', 'name') comment.commenter_display_name = cls._get(com, 'commenter', 'display_name') comment.commenter_logo = cls._get(com, 'commenter', 'logo') comment.source = cls._get(com, 'source') comment.message_body = cls._get(com, 'message', 'body') comment.message_user_color = cls._get(com, 'message', 'user_color') badges = cls._get(com, 'message', 'user_badges') if badges: badges = ','.join(['{_id}:{version}'.format(**b) for b in badges]) comment.message_user_badges = badges comment.created_at = cls._to_datetime(cls._get(com, 'created_at')) comment.updated_at = cls._to_datetime(cls._get(com, 'updated_at')) q = db.session.query(Association).filter( Association.video_id == video.id, Association.comment_id == comment.id) assoc = q.first() if not assoc: assoc = Association() assoc.comment = comment assoc.offset = cls._get(com, 'content_offset_seconds') video.associations.append(assoc) db.session.add(video) db.session.commit() for em in twitch.fetch_emotes(channel): id = cls._get(em, 'id') if not id: continue q = db.session.query(Emote).filter(Emote.id == id) emote = q.first() if not emote: emote = Emote(id=id) emote.code = cls._get(em, 'code') db.session.add(emote) db.session.commit() app.logger.info('Synchronization completed')