import configparser import datetime import functools import logging import os import re import unicodedata import irc.bot import requests class ReplayBot(irc.bot.SingleServerIRCBot): def __init__(self): self.logger = logging.getLogger('irc.client') handler = logging.StreamHandler() handler.setFormatter(logging.Formatter('%(levelname)s: %(message)s')) self.logger.addHandler(handler) level = logging.DEBUG if bool(int(os.getenv('DEBUG', 0))) else logging.INFO self.logger.setLevel(level) self.config = configparser.ConfigParser() self.config.read('settings.cfg') self.supported_commands = [ (re.compile(r'^!load\s+(?P\d+)$'), self._do_load), (re.compile(r'^!start$'), self._do_start), (re.compile(r'^!stop$'), self._do_stop), ] self.messages = [] self.output_channel = '#{0}'.format(self.config['IRC'].get('output_channel')) control_channel = self.config['IRC'].get('control_channel') self.control_channel = '#{0}'.format(control_channel) if control_channel else None self.control_user = self.config['IRC'].getint('control_user') server = self.config['IRC'].get('server') port = self.config['IRC'].getint('port') nickname = self.config['IRC'].get('nickname') token = self.config['Twitch'].get('token') self.logger.info('Connecting to %s:%d', server, port) super(ReplayBot, self).__init__([(server, port, token)], nickname, nickname) def on_welcome(self, connection, event): connection.cap('REQ', ':twitch.tv/membership') connection.cap('REQ', ':twitch.tv/tags') connection.cap('REQ', ':twitch.tv/commands') channels = set([self.output_channel]) if self.control_channel: channels.add(self.control_channel) for channel in channels: self.logger.info('Joining %s', channel) connection.join(channel) def on_join(self, connection, event): self.logger.info('Joined %s', event.target) def on_pubmsg(self, connection, event): self._process_message(connection, event) def on_whisper(self, connection, event): self._process_message(connection, event) def _send_response(self, connection, event, msg): if event.target.startswith('#'): connection.privmsg(event.target, msg) else: connection.privmsg('#jtv', '/w {0} {1}'.format(event.source.nick, msg)) def _process_message(self, connection, event): tags = {t['key']: t['value'] for t in event.tags} message = ''.join([c for c in event.arguments[0] if not unicodedata.category(c).startswith('C')]) message = message.rstrip() send_response = functools.partial(self._send_response, connection, event) if int(tags['user-id']) != self.control_user: send_response('Sorry @{0}, you are not allowed to do this'.format(tags['display-name'])) return for pattern, action in self.supported_commands: m = pattern.match(message) if m: action(tags, send_response, **m.groupdict()) def _do_load(self, tags, send_response, id, **kwargs): client_id = self.config['Twitch'].get('client_id') def get_comments(cursor): url = 'https://api.twitch.tv/v5/videos/{0}/comments'.format(id) params = dict(client_id=client_id, cursor=cursor) r = requests.get(url, params=params) r.raise_for_status() return r.json() self.messages = [] cursor = '' while True: data = get_comments(cursor) for comment in data.get('comments', []): self.messages.append(( datetime.timedelta(seconds=float(comment['content_offset_seconds'])), comment['commenter']['display_name'], comment['message']['body'])) cursor = data.get('_next') if not cursor: break if self.messages: send_response('@{0}: loaded {1} messages, first message at {2}'.format( tags['display-name'], len(self.messages), str(self.messages[0][0]))) else: send_response('@{0}: failed to load messages'.format(tags['display-name'])) def _do_start(self, tags, send_response, **kwargs): now = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc) for offset, user, msg in self.messages: def cb(user=user, msg=msg): return self._post_message(user, msg) self.reactor.scheduler.execute_at(now + offset, cb) def _do_stop(self, tags, send_response, **kwargs): self.reactor.scheduler.queue.clear() def _post_message(self, user, msg): self.connection.privmsg(self.output_channel, '[{0}]: {1}'.format(user, msg)) def main(): bot = ReplayBot() bot.start() if __name__ == "__main__": main()