Implement !clip command

master
Nikola Forró 6 years ago
parent 15a659cfd1
commit d30e01ba9f

@ -8,6 +8,8 @@ using [HTTP API](https://gitea.brno.mraveniste.cc/turbotraktor/ladylilia.com/src
* `!bellagram`, `!bellapics`, `!instabella`, `!instagram` - posts a link to a random Instagram picture of Bella
* `!yt QUERY` - queries Lady Lilia's Youtube channel and posts a link to the most relevant result
- `QUERY` can contain `|` (logical **or**) and `-` (logical **not**) operators, for example: `!yt oblivion -nehrim`
* `!clip PATTERN` - searches for Twitch clips matching `PATTERN` and in case multiple matches are found, returns one of them randomly
- `PATTERN` has to be at least 3 characters long and it can be enclosed in double quotes in case it contains spaces
### Twitch-only commands

@ -37,6 +37,7 @@ class DiscordClient(discord.Client):
self.supported_commands = [
(re.compile(r'^(?P<prefix>!|\?)(bella(gram|pics)|insta(gram|bella))$'), self._do_bellagram),
(re.compile(r'^(?P<prefix>!|\?)yt\s+(?P<q>")?(?P<query>.+)(?(q)")$'), self._do_yt),
(re.compile(r'^(?P<prefix>!|\?)clip\s+(?P<q>")?(?P<filter>.+)(?(q)")$'), self._do_clip),
]
super(DiscordClient, self).__init__()
@ -90,3 +91,18 @@ class DiscordClient(discord.Client):
await self.send_message(message.channel, embed=embed)
elif prefix == '?':
await self.send_message(message.channel, result['url'])
@cooldown(retries=3, timeout=5*60, failure=_cooldown_failure)
async def _do_clip(self, user, message, filter, prefix, **kwargs):
try:
result = self.commands.find_clip(filter)
except CommandError as e:
await self.send_message(message.channel, 'Sorry {0}, {1}'.format(message.author.mention, e))
else:
if prefix == '!':
embed = discord.Embed(title=result['title'], url=result['url'], color=0xff5733)
embed.set_thumbnail(url=result['thumbnail_medium'])
embed.set_author(name=result['curator_display_name'], icon_url=result['curator_logo'])
await self.send_message(message.channel, embed=embed)
elif prefix == '?':
await self.send_message(message.channel, result['url'])

@ -48,6 +48,7 @@ class TwitchClient(irc.bot.SingleServerIRCBot):
(re.compile(r'^!syncquotes$'), self._do_syncquotes),
(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),
(re.compile(r'^!clip\s+(?P<q>")?(?P<filter>.+)(?(q)")$'), self._do_clip),
]
server = self.config['IRC'].get('server')
port = self.config['IRC'].getint('port')
@ -143,9 +144,9 @@ class TwitchClient(irc.bot.SingleServerIRCBot):
respond('Sorry @{0}, you are not allowed to do this'.format(tags['display-name']))
return
try:
messages = self.commands.get_twitch_messages()
messages = self.commands.get_quote_messages()
except CommandError as e:
self.logger.error('Failed to get Twitch messages: %s', e)
self.logger.error('Failed to get quote messages: %s', e)
else:
for message in messages:
for pattern, action in self.patterns:
@ -168,3 +169,11 @@ class TwitchClient(irc.bot.SingleServerIRCBot):
send_response('Sorry @{0}, {1}'.format(tags['display-name'], e))
else:
send_response('{0}: {1}'.format(result['title'], result['url']))
def _do_clip(self, tags, send_response, filter, **kwargs):
try:
result = self.commands.find_clip(filter)
except CommandError as e:
send_response('Sorry @{0}, {1}'.format(tags['display-name'], e))
else:
send_response('{0}: {1}'.format(result['title'], result['url']))

@ -4,10 +4,12 @@ import dateutil.parser
import requests
from services.instagram import Instagram, InstagramError
from services.twitch import Twitch, TwitchError
from services.youtube import Youtube, YoutubeError
CLIPS_BASE_URL = 'https://clips.twitch.tv'
class CommandError(Exception):
pass
@ -44,15 +46,15 @@ class Commands(object):
except requests.exceptions.HTTPError as e:
raise CommandError(e)
def get_twitch_messages(self):
api_url = self.config['Twitch Logs'].get('api_url')
params = dict(commenter='bellateeny', term='quote')
def get_quote_messages(self):
try:
r = requests.get('{0}/search'.format(api_url), params=params)
r.raise_for_status()
except requests.exceptions.HTTPError:
messages = self._get_messages(dict(
commenter='bellateeny',
term='quote'))
except requests.exceptions.HTTPError as e:
raise CommandError(e)
return [m.get('message_body', '') for m in r.json()]
else:
return [m.get('message_body', '') for m in messages]
def last_quote(self):
try:
@ -96,6 +98,21 @@ class Commands(object):
else:
return result
def find_clip(self, filter):
if len(filter) < 3:
raise CommandError('the search phrase is too short')
try:
clips = self._get_clips(dict(
filter=filter,
sort_order='random',
page_size=1)).json()
clip = clips.pop(0)
clip['url'] = '{0}/{1}'.format(CLIPS_BASE_URL, clip['slug'])
except (requests.exceptions.HTTPError, IndexError):
raise CommandError('no clips found')
else:
return clip
def _get_quotes(self, params):
api_url = self.config['Quotes'].get('api_url')
r = requests.get('{0}/quotes'.format(api_url), params=params)
@ -118,6 +135,18 @@ class Commands(object):
r.raise_for_status()
return r
def _get_messages(self, params):
api_url = self.config['Twitch'].get('cache_api_url')
r = requests.get('{0}/search'.format(api_url), params=params)
r.raise_for_status()
return r
def _get_clips(self, params):
api_url = self.config['Twitch'].get('cache_api_url')
r = requests.get('{0}/clips'.format(api_url), params=params)
r.raise_for_status()
return r
def _collect_bellagrams(self):
username = self.config['Instagram'].get('username')
keywords = self.config['Instagram'].get('keywords').split(',')

@ -1,83 +0,0 @@
import dateutil.parser
import requests
class TwitchError(Exception):
pass
class Twitch(object):
def __init__(self, api_url, client_id):
self.api_url = api_url
self.client_id = client_id
def _get_videos(self, channel_id):
def request(offset, limit):
url = '{0}/channels/{1}/videos'.format(self.api_url, channel_id)
params = dict(client_id=self.client_id, offset=offset, limit=limit)
r = requests.get(url, params=params)
r.raise_for_status()
return r.json()
result = []
data = request(0, 1)
total = data.get('_total', 0)
limit = 100
for offset in range(0, total, limit):
data = request(offset, limit)
for vid in data.get('videos', []):
result.append(dict(
id=int(vid['_id'].lstrip('v')),
title=vid['title'],
date=dateutil.parser.parse(vid['recorded_at']).date()))
return result
def _get_comments(self, video_id):
def request(cursor):
url = '{0}/videos/{1}/comments'.format(self.api_url, video_id)
params = dict(client_id=self.client_id, cursor=cursor)
r = requests.get(url, params=params)
r.raise_for_status()
return r.json()
result = []
cursor = ''
while True:
data = request(cursor)
for comment in data.get('comments', []):
result.append(comment['message']['body'])
cursor = data.get('_next')
if not cursor:
break
return result
def _get_stream_info(self, channel_id):
def request():
url = '{0}/streams/{1}'.format(self.api_url, channel_id)
params = dict(client_id=self.client_id)
r = requests.get(url, params=params)
r.raise_for_status()
return r.json()
data = request()
if data['stream'] is None:
return None
return dict(
id=int(data['stream']['_id']),
game=data['stream']['game'],
viewers=int(data['stream']['viewers']))
def get_messages(self, channel_id, since):
try:
videos = self._get_videos(channel_id)
result = []
for video in [v for v in videos if v['date'] >= since]:
comments = self._get_comments(video['id'])
result.extend(comments)
except requests.exceptions.HTTPError as e:
raise TwitchError('Failed to retrieve VOD messages: {0}'.format(e))
else:
return result
def get_stream_info(self, channel_id):
try:
return self._get_stream_info(channel_id)
except requests.exceptions.HTTPError as e:
raise TwitchError('Failed to get stream info: {0}'.format(e))

@ -64,7 +64,7 @@ class Youtube(object):
try:
return self._search(channel_id, query, playlists, limit)
except googleapiclient.errors.HttpError as e:
raise YoutubeError('Failed to query Youtube API: {}'.format(e))
raise YoutubeError('Failed to query Youtube API: {0}'.format(e))
def find_best_match(self, channel_ids, query):
results = []
@ -73,7 +73,7 @@ class Youtube(object):
results.extend(self._search(channel_id, query, playlists=True, limit=1))
results.extend(self._search(channel_id, query, playlists=False, limit=1))
except googleapiclient.errors.HttpError as e:
raise YoutubeError('Failed to query Youtube API: {}'.format(e))
raise YoutubeError('Failed to query Youtube API: {0}'.format(e))
if not results:
return None
tokens = [t for t in query.split('|') if not t.strip().startswith('-')] or ['']

@ -8,11 +8,8 @@ channels = lilialil
token = __DISCORD_TOKEN__
[Twitch]
api_url = https://api.twitch.tv/v5
client_id = __TWITCH_CLIENT_ID__
cache_api_url = https://ladylilia.com/twitch-cache/api
token = oauth:__TWITCH_OAUTH_TOKEN__
# lilialil
channel_id = 92737529
# nikola_f
master_user_id = 210957066
@ -28,6 +25,3 @@ channel_ids = UC5970RJMoEcRNZl0MNp8tlQ,UCHNjavmkFUf2n0uzWSgj16w
[Quotes]
api_url = https://ladylilia.com/quotes/api
api_key = __QUOTES_API_KEY__
[Twitch Logs]
api_url = https://ladylilia.com/twitch-logs/api

Loading…
Cancel
Save