Add YouTube webhook and react to notifications

master
Nikola Forró 4 years ago
parent 2a7e910d46
commit fe23a237c5

@ -36,7 +36,8 @@ config.read('settings.cfg')
storage_path = config.get('General', 'storage_path') storage_path = config.get('General', 'storage_path')
token = config.get('Discord', 'token') token = config.get('Discord', 'token')
channel_id = config.getint('Discord', 'channel_id') twitter_channel_id = config.getint('Discord', 'twitter_channel_id')
youtube_channel_id = config.getint('Discord', 'youtube_channel_id')
consumer_key = config.get('Twitter', 'consumer_key') consumer_key = config.get('Twitter', 'consumer_key')
consumer_secret = config.get('Twitter', 'consumer_secret') consumer_secret = config.get('Twitter', 'consumer_secret')
@ -53,7 +54,21 @@ class Bot(discord.Client):
self.youtube = YouTube(yt_api_key, yt_channel_id) self.youtube = YouTube(yt_api_key, yt_channel_id)
super().__init__() super().__init__()
def get_api(self, user_id): async def process_event(self, service, data):
action = getattr(self, 'process_{0}_event'.format(service))
await action(data)
async def on_message(self, message):
for pattern in self.commands:
m = pattern.match(message.content)
if not m:
continue
d = m.groupdict()
command = d.pop('command')
action = getattr(self, 'perform_{0}'.format(command))
await action(message, **d)
def get_twitter_api(self, user_id):
handler = tweepy.OAuthHandler(consumer_key, consumer_secret) handler = tweepy.OAuthHandler(consumer_key, consumer_secret)
with open(pathlib.Path(storage_path, 'tokens.json'), 'r') as f: with open(pathlib.Path(storage_path, 'tokens.json'), 'r') as f:
tokens = json.load(f) tokens = json.load(f)
@ -63,7 +78,7 @@ class Bot(discord.Client):
handler.set_access_token(*access_token) handler.set_access_token(*access_token)
return tweepy.API(handler) return tweepy.API(handler)
def make_embed(self, tweet): def make_twitter_embed(self, tweet):
tweet_url = '{0}{1}'.format(TWITTER_STATUS_URL, tweet.get('id_str')) tweet_url = '{0}{1}'.format(TWITTER_STATUS_URL, tweet.get('id_str'))
author_url = '{0}{1}'.format(TWITTER_USER_URL, tweet.get('user', {}).get('id_str')) author_url = '{0}{1}'.format(TWITTER_USER_URL, tweet.get('user', {}).get('id_str'))
author_handle = '@{0}'.format(tweet.get('user', {}).get('screen_name')) author_handle = '@{0}'.format(tweet.get('user', {}).get('screen_name'))
@ -84,7 +99,7 @@ class Bot(discord.Client):
) )
return embed return embed
async def process_event(self, data): async def process_twitter_event(self, data):
tweets = data.get('tweet_create_events', []) tweets = data.get('tweet_create_events', [])
if not tweets: if not tweets:
return return
@ -94,24 +109,45 @@ class Bot(discord.Client):
user_id = data.get('for_user_id') user_id = data.get('for_user_id')
if not user_id: if not user_id:
return return
api = self.get_api(user_id) api = self.get_twitter_api(user_id)
if not api: if not api:
return return
await self.wait_until_ready() await self.wait_until_ready()
channel = self.get_channel(channel_id) channel = self.get_channel(twitter_channel_id)
if channel: if channel:
for tweet in tweets: for tweet in tweets:
await channel.send(embed=self.make_embed(tweet)) await channel.send(embed=self.make_twitter_embed(tweet))
async def on_message(self, message): async def process_youtube_event(self, data):
for pattern in self.commands: entry = data.get('feed', {}).get('entry', {})
m = pattern.match(message.content) if entry.get('yt:channelId') != yt_channel_id:
if not m: return
continue video_id = entry.get('yt:videoId')
d = m.groupdict() if not video_id:
command = d.pop('command') return
action = getattr(self, 'perform_{0}'.format(command)) try:
await action(message, **d) video = self.youtube.get_video(video_id)
except YouTubeError as e:
return
channel = self.get_channel(youtube_channel_id)
if channel:
# TODO: setup timer for livestreams
await channel.send('{title}\n{link}'.format(**video))
def make_youtube_embed(self, playlist):
embed = discord.Embed(
title=playlist.get('title'),
description=playlist.get('description'),
url=playlist.get('link'),
color=YOUTUBE_COLOR
)
embed.set_thumbnail(url=playlist.get('thumbnail_url'))
embed.set_author(
name=self.youtube.channel.get('title'),
url=self.youtube.channel.get('link'),
icon_url=self.youtube.channel.get('thumbnail_url')
)
return embed
async def perform_reaction(self, message, query, **kwargs): async def perform_reaction(self, message, query, **kwargs):
try: try:
@ -123,12 +159,7 @@ class Bot(discord.Client):
await message.channel.send('Sorry {0}, nothing found'.format(message.author.mention)) await message.channel.send('Sorry {0}, nothing found'.format(message.author.mention))
return return
if result.get('kind') == 'playlist': if result.get('kind') == 'playlist':
embed = discord.Embed(title=result.get('title'), url=result.get('link'), await message.channel.send(embed=self.make_youtube_embed(result))
description=result.get('description'), color=YOUTUBE_COLOR)
embed.set_thumbnail(url=result.get('thumbnail_url'))
embed.set_author(name=self.youtube.channel.get('title'), url=self.youtube.channel.get('link'),
icon_url=self.youtube.channel.get('thumbnail_url'))
await message.channel.send(embed=embed)
else: else:
await message.channel.send('{title}\n{link}'.format(**result)) await message.channel.send('{title}\n{link}'.format(**result))
@ -144,7 +175,7 @@ def main():
await asyncio.sleep(TIMEOUT) await asyncio.sleep(TIMEOUT)
else: else:
try: try:
await bot.process_event(data) await bot.process_event(*data)
except Exception: except Exception:
queue.put(data) queue.put(data)
queue.task_done() queue.task_done()

@ -9,6 +9,7 @@ import pathlib
import flask import flask
import persistqueue import persistqueue
import tweepy import tweepy
import xmltodict
from twitivity import Twitivity, TwitivityError from twitivity import Twitivity, TwitivityError
@ -35,8 +36,10 @@ callback_url = config.get('Twitter', 'callback_url')
webhook_url = config.get('Twitter', 'webhook_url') webhook_url = config.get('Twitter', 'webhook_url')
environment_name = config.get('Twitter', 'environment_name') environment_name = config.get('Twitter', 'environment_name')
yt_channel_id = config.get('YouTube', 'channel_id')
def update_tokens(user_id, access_token):
def update_twitter_tokens(user_id, access_token):
path = pathlib.Path(storage_path, 'tokens.json') path = pathlib.Path(storage_path, 'tokens.json')
path.touch(exist_ok=True) path.touch(exist_ok=True)
with open(path, 'r+') as f: with open(path, 'r+') as f:
@ -50,13 +53,7 @@ def update_tokens(user_id, access_token):
f.truncate() f.truncate()
def push_event(data): def setup_twitter_webhook(api):
queue = persistqueue.SQLiteQueue(storage_path)
queue.put(data)
queue.task_done()
def setup_webhook(api):
twitivity = Twitivity(api, environment_name) twitivity = Twitivity(api, environment_name)
try: try:
webhook_id = twitivity.check_webhook() webhook_id = twitivity.check_webhook()
@ -79,6 +76,12 @@ def setup_webhook(api):
return True return True
def push_event(data):
queue = persistqueue.SQLiteQueue(storage_path)
queue.put(data)
queue.task_done()
@app.route('/twitter/auth') @app.route('/twitter/auth')
def twitter_auth(): def twitter_auth():
handler = tweepy.OAuthHandler(consumer_key, consumer_secret, callback_url) handler = tweepy.OAuthHandler(consumer_key, consumer_secret, callback_url)
@ -101,8 +104,8 @@ def twitter_callback():
user = api.verify_credentials(include_entities=False, skip_status=True, include_email=False) user = api.verify_credentials(include_entities=False, skip_status=True, include_email=False)
if not user: if not user:
return 'Authentication failure', 500 return 'Authentication failure', 500
update_tokens(user.id_str, [handler.access_token, handler.access_token_secret]) update_twitter_tokens(user.id_str, [handler.access_token, handler.access_token_secret])
if not setup_webhook(api): if not setup_twitter_webhook(api):
return 'Webhook setup failure', 500 return 'Webhook setup failure', 500
return 'Success', 200 return 'Success', 200
@ -130,8 +133,28 @@ def twitter_webhook_post():
return False return False
if not verify(): if not verify():
return 'Invalid request signature', 403 return 'Invalid request signature', 403
push_event(flask.request.get_json()) push_event(('twitter', flask.request.get_json()))
return '', 200 return '', 204
@app.route('/youtube/webhook', methods=['GET'])
def youtube_webhook_get():
mode = flask.request.args.get('hub.mode')
topic = flask.request.args.get('hub.topic')
challenge = flask.request.args.get('hub.challenge')
if (topic != 'https://www.youtube.com/xml/feeds/videos.xml?channel_id={0}'.format(yt_channel_id) or
mode not in ('subscribe', 'unsubscribe') or not challenge):
return 'Invalid request', 403
return challenge, 200
@app.route('/youtube/webhook', methods=['POST'])
def youtube_webhook_post():
data = flask.request.get_data()
if not data:
return '', 204
push_event(('youtube', xmltodict.parse(data)))
return '', 204
if __name__ == '__main__': if __name__ == '__main__':

@ -1,5 +1,6 @@
import html import html
import dateutil.parser
import fuzzywuzzy.fuzz import fuzzywuzzy.fuzz
import fuzzywuzzy.process import fuzzywuzzy.process
import googleapiclient import googleapiclient
@ -19,26 +20,31 @@ class YouTube(object):
if key in thumbnails: if key in thumbnails:
return thumbnails[key].get('url') return thumbnails[key].get('url')
def process_item(self, item):
id = item.get('id', '')
kind = item.get('kind', 'youtube#').split('youtube#')[1]
if kind == 'searchResult':
id = item.get('id', {}).get('videoId', '')
kind = item.get('id', {}).get('kind', 'youtube#').split('youtube#')[1]
if kind == 'playlist':
link = '{0}/view_play_list?p={1}'.format(BASE_URL, id)
else:
link = '{0}/watch?v={1}'.format(BASE_URL, id)
scheduled_start = item.get('liveStreamingDetails', {}).get('scheduledStartTime')
if scheduled_start:
scheduled_start = dateutil.parser.parse(scheduled_start)
return dict(
kind=kind,
link=link,
title=html.unescape(item.get('snippet', {}).get('title', '')),
description=html.unescape(item.get('snippet', {}).get('description', '')),
thumbnail_url=self.get_thumbnail_url(item.get('snippet', {}).get('thumbnails', {})),
live_broadcast=item.get('snippet', {}).get('liveBroadcastContent', 'none'),
scheduled_start=scheduled_start
)
def process_items(self, items): def process_items(self, items):
result = [] return [self.process_item(i) for i in items]
for item in items:
id = item.get('id', '')
kind = item.get('kind', 'youtube#').split('youtube#')[1]
if kind == 'searchResult':
id = item.get('id', {}).get('videoId', '')
kind = item.get('id', {}).get('kind', 'youtube#').split('youtube#')[1]
if kind == 'playlist':
link = '{0}/view_play_list?p={1}'.format(BASE_URL, id)
else:
link = '{0}/watch?v={1}'.format(BASE_URL, id)
result.append(dict(
kind=kind,
link=link,
title=html.unescape(item.get('snippet', {}).get('title', '')),
description=html.unescape(item.get('snippet', {}).get('description', '')),
thumbnail_url=self.get_thumbnail_url(item.get('snippet', {}).get('thumbnails', {}))
))
return result
def get_channel(self, channel_id): def get_channel(self, channel_id):
r = self.client.channels().list(id=channel_id, maxResults=1, part='id,snippet').execute() r = self.client.channels().list(id=channel_id, maxResults=1, part='id,snippet').execute()
@ -50,6 +56,11 @@ class YouTube(object):
thumbnail_url=self.get_thumbnail_url(channel.get('snippet', {}).get('thumbnails', {})) thumbnail_url=self.get_thumbnail_url(channel.get('snippet', {}).get('thumbnails', {}))
) )
def get_video(self, video_id):
r = self.client.videos().list(id=video_id, maxResults=1, part='id,snippet,liveStreamingDetails').execute()
video = r.get('items', [{}]).pop()
return self.process_item(video)
def get_playlists(self): def get_playlists(self):
token = '' token = ''
result = [] result = []

Loading…
Cancel
Save