You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

137 lines
5.4 KiB

import configparser
import datetime
import functools
import logging
import os
import re
import unicodedata
import dateutil.parser
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<id>\d+)$'), self._do_load),
(re.compile(r'^!start(\s+(?P<at>.+))?$'), 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)
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):
if int(tags['user-id']) != self.control_user:
send_response('Sorry @{0}, you are not allowed to do this'.format(tags['display-name']))
return
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, at=None, **kwargs):
if int(tags['user-id']) != self.control_user:
send_response('Sorry @{0}, you are not allowed to do this'.format(tags['display-name']))
return
if at is None:
t = datetime.datetime.now().astimezone(tz=None)
else:
t = dateutil.parser.parse(at).astimezone(tz=None)
for offset, user, msg in self.messages:
def cb(user=user, msg=msg):
return self._post_message(user, msg)
self.reactor.scheduler.execute_at(t + offset, cb)
def _do_stop(self, tags, send_response, **kwargs):
if int(tags['user-id']) != self.control_user:
send_response('Sorry @{0}, you are not allowed to do this'.format(tags['display-name']))
return
with self.reactor.mutex:
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()