import configparser import functools import logging import os import random import re import string import dateutil.parser import irc.bot import requests from instagram import Instagram from twitch import Twitch from youtube import Youtube config = configparser.ConfigParser() config.read('settings.cfg') log = logging.getLogger('irc.client') log.addHandler(logging.StreamHandler()) log.setLevel(logging.DEBUG if bool(int(os.getenv('DEBUG', 0))) else logging.INFO) # $username --> Sweet! Thanks for the quote! #$id: $response QUOTE_ADDED_PATTERN = re.compile(r'''^ (?P.+)\s+-->\s+ Sweet!\s+Thanks\s+for\s+the\s+quote!\s+ \#(?P\d+):\s+ (?P.+)\s+ \[(?P.+)\]\s+ \[(?P.+)\]$''', re.VERBOSE) # $username --> Successfully edited Quote #$id: $response QUOTE_EDITED_PATTERN = re.compile(r'''^ (?P.+)\s+-->\s+ Successfully\s+edited\s+Quote\s+ \#(?P\d+):\s+ (?P.+)\s+ \[(?P.+)\]\s+ \[(?P.+)\]$''', re.VERBOSE) # $username --> Successfully deleted Quote #$id. QUOTE_REMOVED_PATTERN = re.compile(r'''^ (?P.+)\s+-->\s+ Successfully\s+deleted\s+Quote\s+ \#(?P\d+)\.$''', re.VERBOSE) class TwitchBot(irc.bot.SingleServerIRCBot): def __init__(self): self.patterns = [ (QUOTE_ADDED_PATTERN, self.add_quote), (QUOTE_EDITED_PATTERN, self.edit_quote), (QUOTE_REMOVED_PATTERN, self.remove_quote), ] self.commands = [ (re.compile(r'^!(bella(gram|pics)|insta(gram|bella))$'), self.bellagram), (re.compile(r'^!lastquote$'), self.last_quote), (re.compile(r'^!findquote\s+(?P")?(?P.+)(?(q)")$'), self.find_quote), (re.compile(r'^!sync(\s+(?P.+))?$'), self.sync), (re.compile(r'^!yt\s+(?P")?(?P.+)(?(q)")$'), self.query_youtube), ] self.server = config['IRC'].get('server', 'irc.chat.twitch.tv') self.port = config['IRC'].getint('port', 6667) self.nickname = config['IRC'].get('nickname') self.channel = '#{0}'.format(config['IRC'].get('channel')) self.token = config['Twitch'].get('token') self.master_user_id = config['Twitch'].getint('master_user_id') self.api_url = config['Quotes'].get('api_url') self.api_key = config['Quotes'].get('api_key') self.bellagrams = self._get_bellagrams() log.info('Connecting to %s:%d', self.server, self.port) super(TwitchBot, self).__init__([(self.server, self.port, self.token)], self.nickname, self.nickname) def _get_bellagrams(self): username = config['Instagram'].get('username') keywords = config['Instagram'].get('keywords').split(',') media = Instagram(username, log).get_media() if not media: return None return [m for m in media if [k for k in keywords if k.lower() in m['text'].lower()]] def _respond(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 on_welcome(self, connection, event): connection.cap('REQ', ':twitch.tv/membership') connection.cap('REQ', ':twitch.tv/tags') connection.cap('REQ', ':twitch.tv/commands') log.info('Joining %s', self.channel) connection.join(self.channel) def on_join(self, connection, event): log.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 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 c in string.printable]) message = message.rstrip() respond = functools.partial(self._respond, connection, event) for pattern, action in self.patterns + self.commands: m = pattern.match(message) if m: action(tags, respond, **m.groupdict()) def get(self, params): r = requests.get('{0}/quotes'.format(self.api_url), params=params) r.raise_for_status() return r def post(self, data): r = requests.post('{0}/quotes'.format(self.api_url), data=data, headers={'X-Quotes-API-Key': self.api_key}) r.raise_for_status() return r def delete(self, id): r = requests.delete('{0}/quotes/{1}'.format(self.api_url, id), headers={'X-Quotes-API-Key': self.api_key}) r.raise_for_status() return r def bellagram(self, tags, respond, **kwargs): if not self.bellagrams: respond('Sorry @{0}, couldn\'t get any media from Instagram'.format(tags['display-name'])) else: respond(random.choice(self.bellagrams)['url']) def last_quote(self, tags, respond, **kwargs): try: quotes = self.get(dict( sort_by='id', sort_order='desc', page_size=1)).json() quote = quotes[0] except (requests.exceptions.HTTPError, IndexError): respond('Sorry @{0}, no quotes found'.format(tags['display-name'])) else: respond('!quote {0}'.format(quote['id'])) def find_quote(self, tags, respond, filter, **kwargs): if len(filter) < 3: respond('Sorry @{0}, the search phrase is too short'.format(tags['display-name'])) return try: quotes = self.get(dict( filter=filter, sort_order='random', page_size=1)).json() quote = quotes[0] except (requests.exceptions.HTTPError, IndexError): respond('Sorry @{0}, no quotes found'.format(tags['display-name'])) else: respond('!quote {0}'.format(quote['id'])) def sync(self, tags, respond, since=None, **kwargs): if int(tags['user-id']) != self.master_user_id: respond('Sorry @{0}, you are not allowed to do this'.format(tags['display-name'])) return if since is None: try: quotes = self.get(dict( sort_by='id', sort_order='desc', page_size=1)).json() quote = quotes[0] except requests.exceptions.HTTPError as e: log.error('Failed to get quotes: %s', str(e)) return except IndexError: log.error('No quotes available') return else: since = quote['date'] api_url = config['Twitch'].get('api_url') client_id = config['Twitch'].get('client_id') user_id = config['Twitch'].getint('target_user_id') since = dateutil.parser.parse(since).date() messages = Twitch(api_url, client_id, log).get_messages(user_id, since) if not messages: return for message in messages: for pattern, action in self.patterns: m = pattern.match(message) if m: action(connection, None, **m.groupdict()) respond('@{0}: sync completed'.format(tags['display-name'])) def query_youtube(self, tags, respond, query, **kwargs): api_key = config['Youtube'].get('api_key') channel_id = config['Youtube'].get('channel_id') yt = Youtube(api_key) items = yt.search(channel_id, query, playlists=True, limit=1) if not items: items = yt.search(channel_id, query, playlists=False, limit=1) if not items: respond('Sorry @{0}, couldn\'t find anything on Youtube'.format(tags['display-name'])) else: respond('{0}: {1}'.format(items[0]['title'], items[0]['url'])) def add_quote(self, tags, respond, user, id, text, game, date, **kwargs): if text[0] == text[-1] == '"': text = text[1:-1] log.info('Adding quote %s: %s', id, text) try: self.post(dict( id=int(id), date=dateutil.parser.parse(date, dayfirst=True).date().isoformat(), game=game, text=text)) except requests.exceptions.HTTPError as e: log.error('Failed to add quote: %s', str(e)) def edit_quote(self, tags, respond, user, id, text, game, date, **kwargs): if text[0] == text[-1] == '"': text = text[1:-1] log.info('Editing quote %s: %s', id, text) try: self.post(dict( id=int(id), date=dateutil.parser.parse(date, dayfirst=True).date().isoformat(), game=game, text=text)) except requests.exceptions.HTTPError as e: log.error('Failed to edit quote: %s', str(e)) def remove_quote(self, tags, respond, user, id, **kwargs): log.info('Removing quote %s', id) try: self.delete(int(id)) except requests.exceptions.HTTPError as e: log.error('Failed to remove quote: %s', str(e)) def main(): bot = TwitchBot() bot.start() if __name__ == "__main__": main()