Refactor everything and implement Discord client

master
Nikola Forró 7 years ago
parent 3e23b2d65c
commit 60cf61d81a

290
bot.py

@ -1,254 +1,60 @@
import asyncio
import configparser
import functools
import logging
import os
import random
import re
import string
import dateutil.parser
import irc.bot
import requests
from commands import Commands
from clients.discord import DiscordClient
from clients.twitch import TwitchClient
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<user>.+)\s+-->\s+
Sweet!\s+Thanks\s+for\s+the\s+quote!\s+
\#(?P<id>\d+):\s+
(?P<text>.+)\s+
\[(?P<game>.+)\]\s+
\[(?P<date>.+)\]$''', re.VERBOSE)
# $username --> Successfully edited Quote #$id: $response
QUOTE_EDITED_PATTERN = re.compile(r'''^
(?P<user>.+)\s+-->\s+
Successfully\s+edited\s+Quote\s+
\#(?P<id>\d+):\s+
(?P<text>.+)\s+
\[(?P<game>.+)\]\s+
\[(?P<date>.+)\]$''', re.VERBOSE)
# $username --> Successfully deleted Quote #$id.
QUOTE_REMOVED_PATTERN = re.compile(r'''^
(?P<user>.+)\s+-->\s+
Successfully\s+deleted\s+Quote\s+
\#(?P<id>\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<q>")?(?P<filter>.+)(?(q)")$'), self.find_quote),
(re.compile(r'^!sync(\s+(?P<since>.+))?$'), self.sync),
(re.compile(r'^!yt\s+(?P<q>")?(?P<query>.+)(?(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.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')
for channel in config['IRC'].get('channels').split(','):
channel = '#{0}'.format(channel)
log.info('Joining %s', channel)
connection.join(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(None, 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))
TIMEOUT = 0.2
def main():
bot = TwitchBot()
bot.start()
config = configparser.ConfigParser()
config.read('settings.cfg')
level = logging.DEBUG if bool(int(os.getenv('DEBUG', 0))) else logging.INFO
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter('CMD: %(levelname)s: %(message)s'))
commands_logger = logging.getLogger('commands')
commands_logger.addHandler(handler)
commands_logger.setLevel(level)
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter('DISCORD: %(levelname)s: %(message)s'))
discord_logger = logging.getLogger('discord')
discord_logger.addHandler(handler)
discord_logger.setLevel(level)
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter('TWITCH: %(levelname)s: %(message)s'))
twitch_logger = logging.getLogger('irc.client')
twitch_logger.addHandler(handler)
twitch_logger.setLevel(level)
commands = Commands(config, commands_logger)
discord_client = DiscordClient(config, discord_logger, commands)
async def run_twitch_client():
twitch_client = TwitchClient(config, twitch_logger, commands)
twitch_client.connect_()
while True:
twitch_client.process_data()
await asyncio.sleep(TIMEOUT)
asyncio.ensure_future(run_twitch_client())
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(discord_client.start_())
except:
loop.run_until_complete(discord_client.logout())
finally:
loop.close()
if __name__ == "__main__":

@ -0,0 +1,56 @@
import re
import discord
from commands import CommandError
class DiscordClient(discord.Client):
def __init__(self, config, logger, commands):
self.config = config
self.logger = logger
self.commands = commands
self.supported_commands = [
(re.compile(r'^!(bella(gram|pics)|insta(gram|bella))$'), self._do_bellagram),
(re.compile(r'^!yt\s+(?P<q>")?(?P<query>.+)(?(q)")$'), self._do_yt),
]
super(DiscordClient, self).__init__()
async def start_(self):
token = self.config['Discord'].get('token')
await self.start(token)
async def on_ready(self):
self.logger.info('Logged in as {0}'.format(self.user.name))
async def on_message(self, message):
for pattern, action in self.supported_commands:
m = pattern.match(message.content)
if m:
await action(message, **m.groupdict())
async def _do_bellagram(self, message, **kwargs):
try:
bellagram = self.commands.bellagram()
except CommandError as e:
await self.send_message(message.channel, 'Sorry {0}, {1}'.format(message.author.mention, e))
else:
embed = discord.Embed(title=bellagram['title'], url=bellagram['url'], color=0x8545bc)
embed.set_image(url=bellagram['display_url'])
embed.set_author(name=bellagram['owner'], url=bellagram['owner_url'],
icon_url=bellagram['owner_pic_url'])
await self.send_message(message.channel, embed=embed)
async def _do_yt(self, message, query, **kwargs):
try:
result = self.commands.query_youtube(query)
except CommandError as e:
await self.send_message(message.channel, 'Sorry {0}, {1}'.format(message.author.mention, e))
else:
embed = discord.Embed(title=result['title'], url=result['url'],
description=result['description'], color=0xff0000)
embed.set_thumbnail(url=result['thumbnail_url'])
embed.set_author(name=result['channel_title'], url=result['channel_url'],
icon_url=result['channel_thumbnail_url'])
await self.send_message(message.channel, embed=embed)

@ -0,0 +1,170 @@
import functools
import re
import string
import irc.bot
from commands import CommandError
# $username --> Sweet! Thanks for the quote! #$id: $response
QUOTE_ADDED_PATTERN = re.compile(r'''^
(?P<user>.+)\s+-->\s+
Sweet!\s+Thanks\s+for\s+the\s+quote!\s+
\#(?P<id>\d+):\s+
(?P<text>.+)\s+
\[(?P<game>.+)\]\s+
\[(?P<date>.+)\]$''', re.VERBOSE)
# $username --> Successfully edited Quote #$id: $response
QUOTE_EDITED_PATTERN = re.compile(r'''^
(?P<user>.+)\s+-->\s+
Successfully\s+edited\s+Quote\s+
\#(?P<id>\d+):\s+
(?P<text>.+)\s+
\[(?P<game>.+)\]\s+
\[(?P<date>.+)\]$''', re.VERBOSE)
# $username --> Successfully deleted Quote #$id.
QUOTE_REMOVED_PATTERN = re.compile(r'''^
(?P<user>.+)\s+-->\s+
Successfully\s+deleted\s+Quote\s+
\#(?P<id>\d+)\.$''', re.VERBOSE)
class TwitchClient(irc.bot.SingleServerIRCBot):
def __init__(self, config, logger, commands):
self.config = config
self.logger = logger
self.commands = commands
self.patterns = [
(QUOTE_ADDED_PATTERN, self._add_quote),
(QUOTE_EDITED_PATTERN, self._edit_quote),
(QUOTE_REMOVED_PATTERN, self._remove_quote),
]
self.supported_commands = [
(re.compile(r'^!lastquote$'), self._do_lastquote),
(re.compile(r'^!findquote\s+(?P<q>")?(?P<filter>.+)(?(q)")$'), self._do_findquote),
(re.compile(r'^!sync(\s+(?P<since>.+))?$'), self._do_sync),
(re.compile(r'^!(bella(gram|pics)|insta(gram|bella))$'), self._do_bellagram),
(re.compile(r'^!yt\s+(?P<q>")?(?P<query>.+)(?(q)")$'), self._do_yt),
]
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(TwitchClient, self).__init__([(server, port, token)], nickname, nickname)
def connect_(self):
self._connect()
def process_data(self):
self.reactor.process_once()
def on_welcome(self, connection, event):
connection.cap('REQ', ':twitch.tv/membership')
connection.cap('REQ', ':twitch.tv/tags')
connection.cap('REQ', ':twitch.tv/commands')
for channel in self.config['IRC'].get('channels').split(','):
channel = '#{0}'.format(channel)
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 c in string.printable])
message = message.rstrip()
send_response = functools.partial(self._send_response, connection, event)
for pattern, action in self.patterns + self.supported_commands:
m = pattern.match(message)
if m:
action(tags, send_response, **m.groupdict())
def _add_quote(self, tags, send_response, user, id, text, game, date, **kwargs):
if text[0] == text[-1] == '"':
text = text[1:-1]
self.logger.info('Adding quote %s: %s', id, text)
try:
self.commands.add_quote(id, text, game, date)
except CommandError as e:
self.logger.error('Failed to add quote: %s', e)
def _edit_quote(self, tags, send_response, user, id, text, game, date, **kwargs):
if text[0] == text[-1] == '"':
text = text[1:-1]
self.logger.info('Editing quote %s: %s', id, text)
try:
self.commands.edit_quote(id, text, game, date)
except CommandError as e:
self.logger.error('Failed to add quote: %s', e)
def _remove_quote(self, tags, send_response, user, id, **kwargs):
self.logger.info('Removing quote %s', id)
try:
self.commands.remove_quote(id)
except CommandError as e:
self.logger.error('Failed to remove quote: %s', e)
def _do_lastquote(self, tags, send_response, **kwargs):
try:
quote = self.commands.last_quote()
except CommandError as e:
send_response('Sorry @{0}, {1}'.format(tags['display-name'], e))
else:
send_response('!quote {0}'.format(quote['id']))
def _do_findquote(self, tags, send_response, filter, **kwargs):
try:
quote = self.commands.find_quote(filter)
except CommandError as e:
send_response('Sorry @{0}, {1}'.format(tags['display-name'], e))
else:
send_response('!quote {0}'.format(quote['id']))
def _do_sync(self, tags, send_response, since=None, **kwargs):
master_user_id = self.config['Twitch'].getint('master_user_id')
if int(tags['user-id']) != self.master_user_id:
respond('Sorry @{0}, you are not allowed to do this'.format(tags['display-name']))
return
try:
messages = self.commands.get_twitch_messages(since)
except CommandError as e:
self.logger.error('Failed to get Twitch messages: %s', e)
else:
for message in messages:
for pattern, action in self.patterns:
m = pattern.match(message)
if m:
action(tags, send_response, **m.groupdict())
def _do_bellagram(self, tags, send_response, **kwargs):
try:
bellagram = self.commands.bellagram()
except CommandError as e:
send_response('Sorry @{0}, {1}'.format(tags['display-name'], e))
else:
send_response(bellagram['url'])
def _do_yt(self, tags, send_response, query, **kwargs):
try:
result = self.commands.query_youtube(query)
except CommandError as e:
send_response('Sorry @{0}, {1}'.format(tags['display-name'], e))
else:
send_response('{0}: {1}'.format(result['title'], result['url']))

@ -0,0 +1,149 @@
import random
import dateutil.parser
import requests
from services.instagram import Instagram, InstagramError
from services.twitch import Twitch, TwitchError
from services.youtube import Youtube, YoutubeError
class CommandError(Exception):
pass
class Commands(object):
def __init__(self, config, logger):
self.config = config
self.logger = logger
self._bellagrams = self._collect_bellagrams()
def add_quote(self, id, text, game, date):
try:
self._post_quotes(dict(
id=int(id),
date=dateutil.parser.parse(date, dayfirst=True).date().isoformat(),
game=game,
text=text))
except requests.exceptions.HTTPError as e:
raise CommandError(e)
def edit_quote(self, id, text, game, date):
try:
self._post_quotes(dict(
id=int(id),
date=dateutil.parser.parse(date, dayfirst=True).date().isoformat(),
game=game,
text=text))
except requests.exceptions.HTTPError as e:
raise CommandError(e)
def remove_quote(self, id):
try:
self._delete_quotes(int(id))
except requests.exceptions.HTTPError as e:
raise CommandError(e)
def get_twitch_messages(self, since):
if since is None:
try:
quotes = self._get_quotes(dict(
sort_by='id',
sort_order='desc',
page_size=1)).json()
quote = quotes.pop(0)
except requests.exceptions.HTTPError:
raise CommandError(e)
except IndexError:
raise CommandError(e)
else:
since = quote['date']
api_url = self.config['Twitch'].get('api_url')
client_id = self.config['Twitch'].get('client_id')
user_id = self.config['Twitch'].getint('target_user_id')
since = dateutil.parser.parse(since).date()
twitch = Twitch(api_url, client_id)
try:
return twitch.get_messages(user_id, since)
except TwitchError as e:
raise CommandError(e)
def last_quote(self):
try:
quotes = self._get_quotes(dict(
sort_by='id',
sort_order='desc',
page_size=1)).json()
quote = quotes.pop(0)
except (requests.exceptions.HTTPError, IndexError):
raise CommandError('no quotes found')
else:
return quote
def find_quote(self, filter):
if len(filter) < 3:
raise CommandError('the search phrase is too short')
try:
quotes = self._get_quotes(dict(
filter=filter,
sort_order='random',
page_size=1)).json()
quote = quotes.pop(0)
except (requests.exceptions.HTTPError, IndexError):
raise CommandError('no quotes found')
else:
return quote
def bellagram(self):
if not self._bellagrams:
raise CommandError('couldn\'t get any media from Instagram')
return random.choice(self._bellagrams)
def query_youtube(self, query):
api_key = self.config['Youtube'].get('api_key')
channel_id = self.config['Youtube'].get('channel_id')
yt = Youtube(api_key)
try:
results = yt.search(channel_id, query, playlists=True, limit=1)
if not results:
results = yt.search(channel_id, query, playlists=False, limit=1)
result = results.pop(0)
except (YoutubeError, IndexError):
raise CommandError('couldn\'t find anything on Youtube')
else:
return result
def _get_quotes(self, params):
api_url = self.config['Quotes'].get('api_url')
r = requests.get('{0}/quotes'.format(api_url), params=params)
r.raise_for_status()
return r
def _post_quotes(self, data):
api_url = self.config['Quotes'].get('api_url')
api_key = self.config['Quotes'].get('api_key')
r = requests.post('{0}/quotes'.format(api_url), data=data,
headers={'X-Quotes-API-Key': api_key})
r.raise_for_status()
return r
def _delete_quotes(self, id):
api_url = self.config['Quotes'].get('api_url')
api_key = self.config['Quotes'].get('api_key')
r = requests.delete('{0}/quotes/{1}'.format(api_url, id),
headers={'X-Quotes-API-Key': api_key})
r.raise_for_status()
return r
def _collect_bellagrams(self):
username = self.config['Instagram'].get('username')
keywords = self.config['Instagram'].get('keywords').split(',')
instagram = Instagram(username)
try:
media = instagram.get_media()
media = [m for m in media if [k for k in keywords \
if m['type'] == 'Image' and k.lower() in m['title'].lower()]]
except (InstagramError, IndexError):
return None
else:
return media

@ -5,6 +5,6 @@ services:
build:
context: .
volumes:
- /twitch-bot/settings.cfg:/bot/settings.cfg
- /bot/settings.cfg:/bot/settings.cfg
environment:
- DEBUG=0

@ -1,3 +1,4 @@
discord.py
google-api-python-client
irc
python-dateutil

@ -12,15 +12,21 @@ QUERY_HASH = '42323d64886122307be10013ad2dcc44'
SHARED_DATA = re.compile(r'window\._sharedData = (\{.*\});</script>')
class Instagram(object):
class InstagramError(Exception):
pass
def __init__(self, username, log=None):
class Instagram(object):
def __init__(self, username):
self.username = username
self.log = log
shared_data = self._get_shared_data()
try:
self.user_id = shared_data['entry_data']['ProfilePage'][0]['graphql']['user']['id']
graphql = shared_data['entry_data']['ProfilePage'][0]['graphql']
self.user_id = graphql['user']['id']
self.rhx_gis = shared_data['rhx_gis']
self.owner = dict(
name=graphql['user']['username'],
profile_pic_url=graphql['user']['profile_pic_url'])
except (IndexError, KeyError, TypeError):
self.user_id = None
self.rhx_gis = None
@ -59,9 +65,12 @@ class Instagram(object):
for edge in data['edges']:
result.append(dict(
type=edge['node']['__typename'].split('Graph')[1],
text=edge['node']['edge_media_to_caption']['edges'][0]['node']['text'],
title=edge['node']['edge_media_to_caption']['edges'][0]['node']['text'],
url='{0}/p/{1}'.format(BASE_URL, edge['node']['shortcode']),
display_url=edge['node']['display_url']))
display_url=edge['node']['display_url'],
owner=self.owner['name'],
owner_url='{0}/{1}'.format(BASE_URL, self.username),
owner_pic_url=self.owner['profile_pic_url']))
cursor = data['page_info']['end_cursor']
if not cursor:
break
@ -76,8 +85,6 @@ class Instagram(object):
try:
result = self._get_media()
except requests.exceptions.HTTPError as e:
if self.log:
self.log.error('Failed to retrieve media: %s', str(e))
return None
raise InstagramError('Failed to retrieve media: {0}'.format(e))
else:
return result

@ -2,11 +2,14 @@ import dateutil.parser
import requests
class TwitchError(Exception):
pass
class Twitch(object):
def __init__(self, api_url, client_id, log=None):
def __init__(self, api_url, client_id):
self.api_url = api_url
self.client_id = client_id
self.log = log
def _get_videos(self, user_id):
def request(offset, limit):
@ -66,14 +69,10 @@ class Twitch(object):
videos = self._get_videos(user_id)
result = []
for video in [v for v in videos if v['date'] >= since]:
if self.log:
self.log.info('Processing VOD %d (%s)', video['id'], video['title'])
comments = self._get_comments(video['id'])
result.extend(comments)
except requests.exceptions.HTTPError as e:
if self.log:
self.log.error('Failed to retrieve VOD messages: %s', str(e))
return None
raise TwitchError('Failed to retrieve VOD messages: {0}'.format(e))
else:
return result
@ -81,6 +80,4 @@ class Twitch(object):
try:
return self._get_stream_info(user_id)
except requests.exceptions.HTTPError as e:
if self.log:
self.log.error('Failed to get stream info: %s', str(e))
return None
raise TwitchError('Failed to get stream info: {0}'.format(e))

@ -5,12 +5,24 @@ import googleapiclient.discovery
BASE_URL = 'https://www.youtube.com'
class YoutubeError(Exception):
pass
class Youtube(object):
def __init__(self, api_key, log=None):
def __init__(self, api_key):
self.client = googleapiclient.discovery.build('youtube', 'v3', developerKey=api_key)
self.log = log
def _search(self, channel_id, query, playlists, limit):
def get_thumbnail_url(thumbnails):
for key in ['high', 'medium', 'default']:
if key in thumbnails:
return thumbnails[key]['url']
resp = self.client.channels().list(
id=channel_id,
maxResults=1,
part='snippet').execute()
channel = resp['items'][0]
result = []
count = limit
token = ''
@ -32,7 +44,12 @@ class Youtube(object):
result.append(dict(
kind=kind,
url=url,
title=item['snippet']['title']))
title=item['snippet']['title'],
description=item['snippet']['description'],
thumbnail_url=get_thumbnail_url(item['snippet']['thumbnails']),
channel_title=channel['snippet']['title'],
channel_url='{0}/c/{1}'.format(BASE_URL, channel['snippet']['customUrl']),
channel_thumbnail_url=get_thumbnail_url(channel['snippet']['thumbnails'])))
count -= resp['pageInfo']['resultsPerPage']
if count <= 0:
break
@ -45,6 +62,4 @@ class Youtube(object):
try:
return self._search(channel_id, query, playlists, limit)
except googleapiclient.errors.HttpError as e:
if self.log:
self.log.error('Failed to query Youtube API: %s', str(e))
return None
raise YoutubeError('Failed to query Youtube API: {}'.format(e))

@ -4,14 +4,8 @@ port = 6667
nickname = spooky_lurker
channels = lilialil
[Instagram]
username = lilialovesgames
keywords = bella,teeny,kitty,cat,😹,😻,🐱,🐈
[Youtube]
api_key = __GOOGLE_API_KEY__
# ladylilia
channel_id = UC5970RJMoEcRNZl0MNp8tlQ
[Discord]
token = __DISCORD_TOKEN__
[Twitch]
api_url = https://api.twitch.tv/v5
@ -22,6 +16,15 @@ target_user_id = 92737529
# nikola_f
master_user_id = 210957066
[Instagram]
username = lilialovesgames
keywords = #bellameeze,bella,teeny,kitty,cat,😹,😻,🐱,🐈
[Youtube]
api_key = __GOOGLE_API_KEY__
# ladylilia
channel_id = UC5970RJMoEcRNZl0MNp8tlQ
[Quotes]
api_url = https://ladylilia.com/quotes/api
api_key = __QUOTES_API_KEY__

Loading…
Cancel
Save