YouTube: use search API

This commit is contained in:
oddluck 2020-02-27 18:34:59 +00:00
parent d992a7dd2c
commit d09455ec86
3 changed files with 42 additions and 97 deletions

View File

@ -1,3 +1,3 @@
Search for YouTube videos and return link + info. Search powered by Google. Search for YouTube videos and return link + info.
`config plugins.youtube.developerkey <your_key_here>` `config plugins.youtube.developerkey <your_key_here>`

View File

@ -54,8 +54,14 @@ YouTube = conf.registerPlugin('YouTube')
conf.registerGlobalValue(YouTube, 'developerKey', conf.registerGlobalValue(YouTube, 'developerKey',
registry.String("", _("""Google API key. Required."""))) registry.String("", _("""Google API key. Required.""")))
conf.registerGlobalValue(YouTube, 'sortOrder',
registry.String("relevance", _("""Method used to order API responses: date, rating, relevance, title, viewCount""")))
conf.registerGlobalValue(YouTube, 'safeSearch',
registry.String("none", _("""Safe search filtering: none, moderate, strict""")))
conf.registerChannelValue(YouTube, 'logo', conf.registerChannelValue(YouTube, 'logo',
registry.String("\x02\x030,4 ► \x031,0YouTube", _("""Logo used with $yt_logo in template"""))) registry.String("\x030,4 ► \x031,0YouTube", _("""Logo used with $yt_logo in template""")))
conf.registerChannelValue(YouTube, 'template', conf.registerChannelValue(YouTube, 'template',
registry.String("{{logo}} :: {{link}} :: {{title}} :: Duration: {{duration}} :: Views: {{views}} :: Uploader: {{uploader}} :: Uploaded: {{published}} :: {{likes}} likes :: {{dislikes}} dislikes :: {{favorites}} favorites :: {{comments}} comments", _("""Template used for search result replies"""))) registry.String("{{logo}} :: {{link}} :: {{title}} :: Duration: {{duration}} :: Views: {{views}} :: Uploader: {{uploader}} :: Uploaded: {{published}} :: {{likes}} likes :: {{dislikes}} dislikes :: {{favorites}} favorites :: {{comments}} comments", _("""Template used for search result replies""")))
@ -63,5 +69,4 @@ conf.registerChannelValue(YouTube, 'template',
conf.registerChannelValue(YouTube, 'useBold', conf.registerChannelValue(YouTube, 'useBold',
registry.Boolean(True, _("""Use bold in replies"""))) registry.Boolean(True, _("""Use bold in replies""")))
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:

View File

@ -38,9 +38,7 @@ import supybot.log as log
import requests import requests
import pendulum import pendulum
from jinja2 import Template from jinja2 import Template
from fake_useragent import UserAgent from urllib.parse import urlencode
from bs4 import BeautifulSoup
from urllib.parse import urlencode, urlparse, parse_qsl, quote_plus
try: try:
from supybot.i18n import PluginInternationalization from supybot.i18n import PluginInternationalization
@ -55,62 +53,36 @@ class YouTube(callbacks.Plugin):
threaded = True threaded = True
def dosearch(self, query): def dosearch(self, query):
apikey = self.registryValue('developerKey')
safe_search = self.registryValue("safeSearch")
sort_order = self.registryValue("sortOrder")
video_id = None
opts = {"q": query,
"part": "snippet",
"maxResults": "1",
"order": sort_order,
"key": apikey,
"safeSearch": safe_search,
"type": "video"}
api_url = "https://www.googleapis.com/youtube/v3/search?{0}".format(urlencode(opts))
try: try:
url = None log.debug("YouTube: requesting %s" % (api_url))
searchurl = "https://www.google.com/search?&q=" request = requests.get(api_url, timeout=10)
searchurl += quote_plus("{0} site:youtube.com".format(query)) request.raise_for_status()
ua = UserAgent(fallback="Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0") request = request.json()
header = {'User-Agent':str(ua.random)} video_id = request["items"][0]["id"]["videoId"]
data = requests.get(searchurl, headers=header, timeout=10)
data.raise_for_status()
soup = BeautifulSoup(data.text)
elements = soup.select('.r a')
for i in range(len(elements)):
if 'watch?v=' in elements[i]['href']:
url = elements[i]['href']
break
except Exception: except Exception:
log.error("YouTube: YouTube API HTTP %s: %s" % (request.status_code, request.text))
pass pass
return url return video_id
def get_video_id_from_url(self, url):
"""
Get YouTube video ID from URL
"""
info = urlparse(url)
try:
path = info.path
domain = info.netloc
video_id = ""
if domain == "youtu.be":
video_id = path.split("/")[1]
else:
parsed = parse_qsl(info.query)
params = dict(parsed)
if "v" in params:
video_id = params["v"]
if video_id:
return video_id
else:
log.error("SpiffyTitles: error getting video id from %s" % (url))
return None
except IndexError as e:
log.error("SpiffyTitles: error getting video id from %s (%s)" % (url, str(e)))
def get_duration_from_seconds(self, duration_seconds): def get_duration_from_seconds(self, duration_seconds):
m, s = divmod(duration_seconds, 60) m, s = divmod(duration_seconds, 60)
h, m = divmod(m, 60) h, m = divmod(m, 60)
duration = "%02d:%02d" % (m, s) duration = "%02d:%02d" % (m, s)
""" Only include hour if the video is at least 1 hour long """ """ Only include hour if the video is at least 1 hour long """
if h > 0: if h > 0:
duration = "%02d:%s" % (h, duration) duration = "%02d:%s" % (h, duration)
return duration return duration
def get_total_seconds_from_duration(self, input): def get_total_seconds_from_duration(self, input):
@ -134,7 +106,6 @@ class YouTube(callbacks.Plugin):
yt_logo = "{0}\x0F\x02".format(self.registryValue("logo", dynamic.channel)) yt_logo = "{0}\x0F\x02".format(self.registryValue("logo", dynamic.channel))
else: else:
yt_logo = "{0}\x0F".format(self.registryValue("logo", dynamic.channel)) yt_logo = "{0}\x0F".format(self.registryValue("logo", dynamic.channel))
return yt_logo return yt_logo
def yt(self, irc, msg, args, query): def yt(self, irc, msg, args, query):
@ -142,39 +113,25 @@ class YouTube(callbacks.Plugin):
Search for YouTube videos Search for YouTube videos
""" """
apikey = self.registryValue('developerKey') apikey = self.registryValue('developerKey')
url = self.dosearch(query) channel = msg.channel
if url and 'youtube.com' in utils.web.getDomain(url): yt_template = Template(self.registryValue("template", channel))
video_id = self.get_video_id_from_url(url) response = None
else: title = None
video_id = None video_id = self.dosearch(query)
irc.reply("No results found for {0}".format(query))
return
channel = msg.args[0]
result = None
yt_template = Template(self.registryValue("template", dynamic.channel))
title = ""
if video_id: if video_id:
log.debug("YouTube: got video id: %s" % video_id) log.debug("YouTube: got video id: %s" % video_id)
options = { opts = {
"part": "snippet,statistics,contentDetails", "part": "snippet,statistics,contentDetails",
"maxResults": 1, "maxResults": 1,
"key": apikey, "key": apikey,
"id": video_id "id": video_id}
} opts = urlencode(opts)
encoded_options = urlencode(options) api_url = "https://www.googleapis.com/youtube/v3/videos?%s" % (opts)
api_url = "https://www.googleapis.com/youtube/v3/videos?%s" % (encoded_options)
log.debug("YouTube: requesting %s" % (api_url)) log.debug("YouTube: requesting %s" % (api_url))
request = requests.get(api_url, timeout=10) request = requests.get(api_url, timeout=10)
ok = request.status_code == requests.codes.ok ok = request.status_code == requests.codes.ok
if ok: if ok:
response = request.json() response = request.json()
if response: if response:
try: try:
if response["pageInfo"]["totalResults"] > 0: if response["pageInfo"]["totalResults"] > 0:
@ -188,41 +145,27 @@ class YouTube(callbacks.Plugin):
dislike_count = 0 dislike_count = 0
comment_count = 0 comment_count = 0
favorite_count = 0 favorite_count = 0
if "viewCount" in statistics: if "viewCount" in statistics:
view_count = "{:,}".format(int(statistics["viewCount"])) view_count = "{:,}".format(int(statistics["viewCount"]))
if "likeCount" in statistics: if "likeCount" in statistics:
like_count = "{:,}".format(int(statistics["likeCount"])) like_count = "{:,}".format(int(statistics["likeCount"]))
if "dislikeCount" in statistics: if "dislikeCount" in statistics:
dislike_count = "{:,}".format(int(statistics["dislikeCount"])) dislike_count = "{:,}".format(int(statistics["dislikeCount"]))
if "favoriteCount" in statistics: if "favoriteCount" in statistics:
favorite_count = "{:,}".format(int(statistics["favoriteCount"])) favorite_count = "{:,}".format(int(statistics["favoriteCount"]))
if "commentCount" in statistics: if "commentCount" in statistics:
comment_count = "{:,}".format(int(statistics["commentCount"])) comment_count = "{:,}".format(int(statistics["commentCount"]))
channel_title = snippet["channelTitle"] channel_title = snippet["channelTitle"]
video_duration = video["contentDetails"]["duration"] video_duration = video["contentDetails"]["duration"]
duration_seconds = self.get_total_seconds_from_duration(video_duration) duration_seconds = self.get_total_seconds_from_duration(video_duration)
"""
#23 - If duration is zero, then it"s a LIVE video
"""
if duration_seconds > 0: if duration_seconds > 0:
duration = self.get_duration_from_seconds(duration_seconds) duration = self.get_duration_from_seconds(duration_seconds)
else: else:
duration = "LIVE" duration = "LIVE"
published = snippet['publishedAt'] published = snippet['publishedAt']
published = self.get_published_date(published) published = self.get_published_date(published)
yt_logo = self.get_youtube_logo() yt_logo = self.get_youtube_logo()
link = "https://youtu.be/%s" % (video_id) link = "https://youtu.be/%s" % (video_id)
compiled_template = yt_template.render({ compiled_template = yt_template.render({
"title": title, "title": title,
"duration": duration, "duration": duration,
@ -236,25 +179,22 @@ class YouTube(callbacks.Plugin):
"published": published, "published": published,
"logo": yt_logo "logo": yt_logo
}) })
title = compiled_template title = compiled_template
else: else:
log.debug("YouTube: video appears to be private; no results!") log.debug("YouTube: video appears to be private; no results!")
except IndexError as e: except IndexError as e:
log.error("YouTube: IndexError parsing Youtube API JSON response: %s" % log.error("YouTube: IndexError parsing Youtube API JSON response: %s" % (str(e)))
(str(e)))
else: else:
log.error("YouTube: Error parsing Youtube API JSON response") log.error("YouTube: Error parsing Youtube API JSON response")
else: else:
log.error("YouTube: YouTube API HTTP %s: %s" % log.error("YouTube: YouTube API HTTP %s: %s" % (request.status_code, request.text))
(request.status_code, request.text))
if title: if title:
use_bold = self.registryValue("useBold", dynamic.channel)
if use_bold:
title = ircutils.bold(title)
irc.reply(title, prefixNick=False) irc.reply(title, prefixNick=False)
yt = wrap(yt, ['text']) yt = wrap(yt, ['text'])
Class = YouTube Class = YouTube
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: