From 19df36b6382ae1f248f7078ec3cd1f4491fd7e04 Mon Sep 17 00:00:00 2001 From: PrgmrBill Date: Wed, 22 Apr 2015 21:02:32 -0400 Subject: [PATCH] SpiffyTitles: Fixes #30 - Upgrades SpiffyTitles Youtube handler to v3 API --- README.md | 9 +++++- config.py | 6 ++-- plugin.py | 89 +++++++++++++++++++++++++++++++++++++++++-------------- test.py | 6 +--- 4 files changed, 79 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 50210cf..382bb9b 100644 --- a/README.md +++ b/README.md @@ -40,9 +40,16 @@ Example output: ### Youtube handler ### +Note: as of April 20 2015 version 2 of the Youtube API was deprecated. As a result, this feature now +requires a [developer key](https://code.google.com/apis/youtube/dashboard/gwt/index.html#settings). + +- Obtain a [developer key](https://code.google.com/apis/youtube/dashboard/gwt/index.html#settings) +- Set the key: `!config supybot.plugins.SpiffyTitles.youtubeDeveloperKey your_developer_key_here` +- Reload: `!reload SpiffyTitles` + `youtubeTitleTemplate` - This is the template used when showing the title of a YouTube video -Default value: `^ {{title}} :: Duration: {{duration}} :: Views: {{view_count}} :: Rating: {{rating}}` +Default value: `^ {{title}} :: Duration: {{duration}} :: Views: {{view_count}}` Example output: diff --git a/config.py b/config.py index 55148fc..8baaed8 100644 --- a/config.py +++ b/config.py @@ -41,7 +41,7 @@ conf.registerGlobalValue(SpiffyTitles, 'defaultTitleTemplate', # YouTube template conf.registerGlobalValue(SpiffyTitles, 'youtubeTitleTemplate', - registry.String("^ {{title}} :: Duration: {{duration}} :: Views: {{view_count}} :: Rating: {{rating}}", _("""Template used for YouTube title responses"""))) + registry.String("^ {{title}} :: Duration: {{duration}} :: Views: {{view_count}}", _("""Template used for YouTube title responses"""))) # User agents conf.registerGlobalValue(SpiffyTitles, 'userAgents', @@ -80,7 +80,9 @@ conf.registerGlobalValue(SpiffyTitles, 'imgurTemplate', conf.registerGlobalValue(SpiffyTitles, 'imgurAlbumTemplate', registry.String("^{%if section %} [{{section}}] {% endif -%}{%- if title -%} {{title}} :: {% endif %}{{image_count}} images :: {{view_count}} views :: {%if nsfw == None %}not sure if safe for work{% elif nsfw == True %}not safe for work!{% else %}safe for work{% endif %}", _("""imgur template"""))) - +# Youtube API +conf.registerGlobalValue(SpiffyTitles, 'youtubeDeveloperKey', + registry.String("", _("""Youtube developer key - required for Youtube handler."""))) diff --git a/plugin.py b/plugin.py index 8bef25c..2a54e35 100644 --- a/plugin.py +++ b/plugin.py @@ -21,6 +21,8 @@ import json import cgi import datetime from jinja2 import Template +from urllib import urlencode +from datetime import timedelta try: from supybot.i18n import PluginInternationalization @@ -42,6 +44,7 @@ class SpiffyTitles(callbacks.Plugin): self.__parent.__init__(irc) self.link_throttle_in_seconds = self.registryValue("cooldownInSeconds") + self.youtube_developer_key = self.registryValue("youtubeDeveloperKey") """ Check if imgur client id or secret are set, and if so initialize @@ -55,7 +58,7 @@ class SpiffyTitles(callbacks.Plugin): self.handlers["i.imgur.com"] = self.handler_imgur_image # Albums, galleries, etc - self.handlers["imgur.com"] = self.handler_imgur + #self.handlers["imgur.com"] = self.handler_imgur # Initialize API client try: @@ -68,7 +71,8 @@ class SpiffyTitles(callbacks.Plugin): except ImportError, e: self.log.error("SpiffyTitles ImportError: %s" % str(e)) - self.add_youtube_handlers() + if self.youtube_developer_key: + self.add_youtube_handlers() def doPrivmsg(self, irc, msg): """ @@ -79,7 +83,7 @@ class SpiffyTitles(callbacks.Plugin): is_ctcp = ircmsgs.isCtcp(msg) message = msg.args[1] now = datetime.datetime.now() - + if is_channel and not is_ctcp: channel_is_allowed = self.is_channel_allowed(channel) url = self.get_url_from_message(message) @@ -225,7 +229,14 @@ class SpiffyTitles(callbacks.Plugin): title = "" if video_id: - api_url = "https://gdata.youtube.com/feeds/api/videos/%s?v=2&alt=jsonc" % (video_id) + options = { + "part": "snippet,statistics,contentDetails", + "maxResults": 1, + "key": self.youtube_developer_key, + "id": video_id + } + encoded_options = urlencode(options) + api_url = "https://www.googleapis.com/youtube/v3/videos?%s" % (encoded_options) agent = self.get_user_agent() headers = { "User-Agent": agent @@ -241,11 +252,12 @@ class SpiffyTitles(callbacks.Plugin): if response: try: - data = response["data"] - title = data["title"] - rating = str(round(data["rating"], 2)) - view_count = "{:,}".format(int(data["viewCount"])) - duration_seconds = int(data["duration"]) + items = response["items"] + video = items[0] + title = video["snippet"]["title"] + statistics = video["statistics"] + view_count = "{:,}".format(int(statistics["viewCount"])) + duration_seconds = self.get_total_seconds_from_duration(video["contentDetails"]["duration"]) """ #23 - If duration is zero, then it"s a LIVE video @@ -264,15 +276,14 @@ class SpiffyTitles(callbacks.Plugin): compiled_template = yt_template.render({ "title": title, - "rating": rating, "duration": duration, "view_count": view_count }) title = compiled_template - except IndexError: - self.log.error("SpiffyTitles: IndexError parsing Youtube API JSON response") + except IndexError, e: + self.log.error("SpiffyTitles: IndexError parsing Youtube API JSON response: %s" % (str(e))) else: self.log.error("SpiffyTitles: Error parsing Youtube API JSON response") else: @@ -287,6 +298,21 @@ class SpiffyTitles(callbacks.Plugin): return self.handler_default(url, domain) + def get_total_seconds_from_duration(self, input): + """ + Duration comes in a format like this: PT4M41S which translates to + 4 minutes and 41 seconds. This method returns the total seconds + so that the duration can be parsed as usual. + """ + pattern = regex = re.compile('(?P-?)P(?:(?P\d+)Y)?(?:(?P\d+)M)?(?:(?P\d+)D)?(?:T(?:(?P\d+)H)?(?:(?P\d+)M)?(?:(?P\d+)S)?)?') + duration = regex.match(input).groupdict(0) + + delta = timedelta(hours=int(duration['hours']), + minutes=int(duration['minutes']), + seconds=int(duration['seconds'])) + + return delta.total_seconds() + def handler_default(self, url, domain): """ Default handler for websites @@ -303,6 +329,15 @@ class SpiffyTitles(callbacks.Plugin): return title_template + def is_valid_imgur_id(self, input): + """ + Tests if input matches the typical imgur id, which seems to be alphanumeric. Images, galleries, + and albums all share their format in their identifier. + """ + match = re.match(r"[a-z0-9]+", input, re.IGNORECASE) + + return match is not None + def handler_imgur(self, url, info): """ Queries imgur API for additional information about imgur links. @@ -311,10 +346,13 @@ class SpiffyTitles(callbacks.Plugin): """ is_album = info.path.startswith("/a/") is_gallery = info.path.startswith("/gallery/") + is_image_page = not is_album and not is_gallery and re.match(r"^\/[a-zA-Z0-9]+", info.path) result = None if is_album: result = self.handler_imgur_album(url, info) + elif is_image_page: + result = self.handler_imgur_image(url, info) else: result = self.handler_default(url, info) @@ -335,7 +373,7 @@ class SpiffyTitles(callbacks.Plugin): if "?" in album_id: album_id = album_id.split("?")[0] - if album_id: + if self.is_valid_imgur_id(album_id): self.log.info("SpiffyTitles: found imgur album id %s" % (album_id)) try: @@ -366,21 +404,26 @@ class SpiffyTitles(callbacks.Plugin): """ Handles retrieving information about images from the imgur API. - This handler is only run when the domain is i.imgur.com which is usually - just images, except in the case of gifv - which is a HTML file which has - a title. The latter case is why there are fallbacks here. - - The path comes in this form: /image_id.extension so strip off the left - forward slash and then split by period to get the image id. + Used for both direct images and imgur.com/some_image_id_here type links, as + they're both single images. """ from imgurpython.helpers.error import ImgurClientRateLimitError from imgurpython.helpers.error import ImgurClientError - - path = info.path.lstrip("/") - image_id = path.split(".")[0] title = None - if image_id: + """ + If there is a period in the path, it's a direct link to an image. If not, then + it's a imgur.com/some_image_id_here type link + """ + if "." in info.path: + path = info.path.lstrip("/") + image_id = path.split(".")[0] + else: + image_id = info.path.lstrip("/") + + if self.is_valid_imgur_id(image_id): + self.log.info("SpiffyTitles: found image id %s" % (image_id)) + try: image = self.imgur_client.get_image(image_id) diff --git a/test.py b/test.py index 6619290..a74745e 100644 --- a/test.py +++ b/test.py @@ -15,10 +15,6 @@ class SpiffyTitlesTestCase(ChannelPluginTestCase): ChannelPluginTestCase.setUp(self) self.assertNotError('reload SpiffyTitles') - - def test_is_url(self): - actual = self.is_url("http://google.com") - - self.assertTrue(actual) + # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: