mirror of
https://github.com/oddluck/limnoria-plugins.git
synced 2025-04-26 04:51:09 -05:00
YouTube: use search API
This commit is contained in:
parent
d992a7dd2c
commit
d09455ec86
@ -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>`
|
||||||
|
@ -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:
|
||||||
|
@ -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:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user