class ChatMessage(db.Model): id = db.Column(db.Integer, primary_key=True) sent_on = db.Column(db.DateTime, default=db.func.now()) streamer = db.relationship('Streamer', backref=db.backref('chat_messages', lazy='dynamic')) streamer_id = db.Column('streamer_id', db.Integer(), db.ForeignKey('streamer.id')) sender = db.Column(db.String()) text = db.Column(db.String())
class YoutubeChannel(db.Model): channel_id = db.Column(db.String(24), primary_key=True) title = db.Column(db.String(30)) streams = db.relationship("YoutubeStream", backref="youtube_channel") streamer_id = db.Column('streamer_id', db.Integer(), db.ForeignKey('streamer.id')) streamer = db.relationship('Streamer', uselist=False, backref=db.backref('youtube_channel_class', uselist=False)) def __init__(self, channel_id, title=None): self.channel_id = channel_id if title: self.title = title else: r = requests_get_with_retries( "https://www.googleapis.com/youtube/v3/channels?id={}&part=snippet&key={}".format( channel_id, app.config['YOUTUBE_KEY']), retries_num=15) r.raise_for_status() for item in r.json()['items']: self.title = item['snippet']['title'] def __eq__(self, other): return type(self) == type(other) and self.channel_id == other.channel_id def __hash__(self): return hash(self.channel_id) def __repr__(self): return u'<YoutubeChannel {} with title {}>'.format(self.channel_id, self.title)
class Stream(db.Model): id = db.Column(db.Integer, primary_key=True) type = db.Column(db.String(50)) scheduled_start_time = db.Column(db.DateTime()) actual_start_time = db.Column(db.DateTime()) status = db.Column(db.Enum('upcoming', 'live', 'completed', name='stream_status')) title = db.Column(db.String(200)) submissions = db.relationship('Submission', secondary=stream_submission, backref=db.backref('streams', lazy='dynamic')) streamer_id = db.Column('streamer_id', db.Integer(), db.ForeignKey('streamer.id')) streamer = db.relationship('Streamer', backref=db.backref('streams', lazy='dynamic')) current_viewers = db.Column(db.Integer) confstream = db.Column(db.Boolean(), default=False) __mapper_args__ = { 'polymorphic_on': type, 'polymorphic_identity': 'stream' } def format_start_time(self, countdown=True, start_time=True): if not self.scheduled_start_time or (not countdown and not start_time): return None if countdown: return humanize.naturaltime(datetime.utcnow() - self.scheduled_start_time) +\ ((", " + datetime.strftime(self.scheduled_start_time, "%Y-%m-%d %H:%M UTC")) if start_time else "") else: return datetime.strftime(self.scheduled_start_time, "%Y-%m-%d %H:%M UTC") def add_submission(self, submission): if submission not in self.submissions: self.submissions.append(submission) def _go_live(self): if self.status != 'live' and self.streamer and\ self.streamer.checked and\ (self.streamer.last_time_notified is None or (datetime.utcnow() - self.streamer.last_time_notified) > timedelta(hours=1)): self.streamer.need_to_notify_subscribers = True self.status = 'live'
class Streamer(db.Model, UserMixin): id = db.Column(db.Integer, primary_key=True) reddit_username = db.column_property(db.Column(db.String(20), unique=True), comparator_factory=CaseInsensitiveComparator) twitch_channel = db.column_property(db.Column(db.String(25), unique=True), comparator_factory=CaseInsensitiveComparator) info = db.Column(db.Text()) checked = db.Column(db.Boolean(), default=False) rtmp_secret = db.Column(db.String(50)) test = db.Column(db.Boolean(), default=False) as_subscriber_id = db.Column('as_subscriber_id', db.Integer(), db.ForeignKey('subscriber.id')) as_subscriber = db.relationship('Subscriber', backref=db.backref('as_streamer')) subscribers = db.relationship('Subscriber', secondary=streamer_subscriptions, lazy='dynamic', backref=db.backref('subscribed_to', lazy='dynamic')) need_to_notify_subscribers = db.Column(db.Boolean, default=False) last_time_notified = db.Column(db.DateTime()) is_banned = db.Column(db.Boolean(), default=False) @property def youtube_channel(self): if self.youtube_channel_class is None: return None return self.youtube_channel_class.channel_id @youtube_channel.setter def youtube_channel(self, channel_id): self.youtube_channel_class = get_or_create(YoutubeChannel, channel_id=channel_id) @youtube_channel.deleter def youtube_channel(self): self.youtube_channel_class = None # XXX: this is kinda ugly, but simple # nginx-rtmp supports only fixed number of redirects # TODO: This should be fixed later rtmp_redirect_1 = db.Column(db.String()) rtmp_redirect_2 = db.Column(db.String()) rtmp_redirect_3 = db.Column(db.String()) def __init__(self, reddit_username, checked=False): self.reddit_username = reddit_username self.checked = checked def __repr__(self): return '<Streamer {} {}>'.format(self.id, self.reddit_username) def get_id(self): return self.reddit_username def already_subscribed(self, another_streamer): return another_streamer and (another_streamer == self or (self.as_subscriber and self.as_subscriber.already_subscribed(another_streamer))) def streaming_key(self): return self.reddit_username + '?pass='******'completed' if not wpcs.status else wpcs.status db.session.commit() def populate_email(self, email): if not self.as_subscriber or len(self.as_subscriber.as_streamer) > 1: self.as_subscriber = get_or_create(Subscriber, email=email) else: alredy_existing_subscriber = Subscriber.query.filter_by(email=email).first() if alredy_existing_subscriber: db.session.delete(self.as_subscriber) self.as_subscriber = alredy_existing_subscriber else: self.as_subscriber.email = email def populate(self, form): self.info = form.info.data tc = form.twitch_channel_extract() # delete inapropriate tstream if tc != self.twitch_channel: ts = self.streams.filter_by(type='twitch_stream').first() if ts: ts.streamer = None # rebind tstream streamer = Streamer.query.filter_by(twitch_channel=tc).first() if streamer and streamer != current_user: streamer.twitch_channel = None for ts in streamer.streams.filter_by(type='twitch_stream'): ts.streamer = self self.twitch_channel = tc if tc else None yc = get_or_create(YoutubeChannel, channel_id=form.youtube_channel_extract()) # delete inapropriate ystreams if yc != self.youtube_channel_class: for ys in self.streams.filter_by(type='youtube_stream'): ys.streamer = None # rebind ystreams streamer = yc.streamer if streamer and streamer != current_user: del streamer.youtube_channel for ys in yc.streams: ys.streamer = self self.youtube_channel_class = yc if yc else None
class YoutubeStream(Stream): ytid = db.Column(db.String(11), unique=True) vod_views = db.Column(db.Integer) youtube_channel_id = db.Column(db.String(24), db.ForeignKey('youtube_channel.channel_id')) def __init__(self, ytid): self.ytid = ytid self.submissions = [] def __eq__(self, other): return type(self) == type(other) and self.ytid == other.ytid def __hash__(self): return hash(self.ytid) def __repr__(self): return '<YoutubeStream {} {}>'.format(self.id, self.ytid) def _update_vod_views(self): app.logger.info("Updating view count for {}".format(self)) r = requests_get_with_retries( "https://www.googleapis.com/youtube/v3/videos?id={}&part=statistics&key={}".format( self.ytid, app.config['YOUTUBE_KEY']), retries_num=15) r.raise_for_status() for item in r.json()['items']: self.vod_views = item['statistics']['viewCount'] def _update_status(self): app.logger.info("Updating status for {}".format(self)) r = requests_get_with_retries( "https://www.googleapis.com/youtube/v3/videos?id={}&part=snippet,liveStreamingDetails&key={}".format( self.ytid, app.config['YOUTUBE_KEY']), retries_num=15) r.raise_for_status() if not r.json()['items']: self.status = 'completed' self.current_viewers = None return for item in r.json()['items']: self.youtube_channel = get_or_create(YoutubeChannel, channel_id=item['snippet']['channelId']) self.youtube_channel.title = item['snippet']['channelTitle'] # if there is streamer with this channel if self.youtube_channel.streamer is not None: self.streamer = self.youtube_channel.streamer elif self.streamer is not None: # if streamer has no yc and didn't ever checked profile if not self.streamer.checked: self.streamer.youtube_channel_class = self.youtube_channel # otherwise else: self.streamer = None self.title = item['snippet']['title'] if 'liveStreamingDetails' in item: self.scheduled_start_time = item['liveStreamingDetails'].get('scheduledStartTime', None) if 'concurrentViewers' in item['liveStreamingDetails']: self.current_viewers = item['liveStreamingDetails']['concurrentViewers'] if item['snippet']['liveBroadcastContent'] == "none" and not self.actual_start_time: # TODO: it is probably better to have a separate column for vids self.actual_start_time = item['snippet'].get('publishedAt') if item['snippet']['liveBroadcastContent'] == 'live': self._go_live() if 'actualStartTime' in item['liveStreamingDetails']: self.actual_start_time = item['liveStreamingDetails'].get('actualStartTime', None) else: # Youtube is weird, and sometimes this happens. If there is no actual start time, then we fall back to scheduledStartTime self.actual_start_time = item['liveStreamingDetails'].get('scheduledStartTime', None) elif item['snippet']['liveBroadcastContent'] == 'upcoming': self.status = 'upcoming' else: self.status = 'completed' self.current_viewers = None def _get_flair(self): fst = self.format_start_time(start_time=False) status_to_flair = {"live": (u"Live", u"one"), "completed": (u"Recording Available", u"four"), "upcoming": (fst if fst else u"Upcoming", u"two"), None: (None, None)} return status_to_flair[self.status] def normal_url(self): return "http://www.youtube.com/watch?v={}".format(self.ytid) def html_code(self, autoplay=False): return """ <iframe width="640" height="390" frameborder="0" allowfullscreen src="http://www.youtube.com/embed/{}?rel=0&autoplay={}"> </iframe> """.format(self.ytid, int(autoplay)) __mapper_args__ = { 'polymorphic_identity': 'youtube_stream' }
class Anon(AnonymousUserMixin): def already_subscribed(self, streamer): email = request.cookies.get("email") return email and streamer and get_or_create(Subscriber, email=email) in streamer.subscribers class CaseInsensitiveComparator(ColumnProperty.Comparator): def __eq__(self, other): return db.func.lower(self.__clause_element__()) == db.func.lower(other) login_manager.anonymous_user = Anon stream_submission = db.Table('stream_submission', db.Column('stream_id', db.Integer(), db.ForeignKey('stream.id')), db.Column('submission_id', db.String(6), db.ForeignKey('submission.submission_id'))) streamer_subscriptions = db.Table('streamer_subscriptions', db.Column('streamer_id', db.Integer(), db.ForeignKey('streamer.id')), db.Column('subscriber_id', db.Integer(), db.ForeignKey('subscriber.id'))) class Submission(db.Model): submission_id = db.Column(db.String(6), primary_key=True) recording_available = db.Column(db.Boolean()) def __repr__(self): return '<Submission {}>'.format(self.submission_id)