parent
3e23b2d65c
commit
60cf61d81a
@ -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
|
Loading…
Reference in new issue