diff --git a/README.md b/README.md index 59c0c68..cf213b8 100644 --- a/README.md +++ b/README.md @@ -41,12 +41,20 @@ Example output: `imgurTemplate` - This is the template used when showing information about an [imgur](https://imgur.com) link. -Default value: `^{%if section %} [{{section}}] {%endif %}{%if title %}{{title}} :: {% endif %}{{type}} {{width}}x{{height}} {{file_size}} :: {{view_count}} views :: {%if nsfw == None %}not sure if safe for work{% elif nsfw == True %}not safe for work!{% else %}safe for work{% endif %}` +Default value: `^{%if section %} [{{section}}] {% endif -%}{%- if title -%} {{title}} :: {% endif %}{{type}} {{width}}x{{height}} {{file_size}} :: {{view_count}} views :: {%if nsfw == None %}not sure if safe for work{% elif nsfw == True %}not safe for work!{% else %}safe for work{% endif %}` Example output: ^ [pics] He really knows nothing... :: image/jpeg 700x1575 178.8KiB :: 809 views :: safe for work +`imgurAlbumTemplate` - This is the template used when showing information about an imgur album link. + +Default value: `^{%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 %}` + +Example output: + + ^ [compsci] Regex Fractals :: 33 images :: 21,453 views :: safe for work + Notes on the imgur handler: - You'll need a [register an application with imgur](https://api.imgur.com/oauth2/addclient) diff --git a/config.py b/config.py index 1cdf00f..55148fc 100644 --- a/config.py +++ b/config.py @@ -75,9 +75,12 @@ conf.registerGlobalValue(SpiffyTitles, 'imgurClientSecret', registry.String("", _("""imgur client secret"""))) conf.registerGlobalValue(SpiffyTitles, 'imgurTemplate', - registry.String("^ {%if section %}[{{section}}] {%endif %}{%if title %}{{title}} :: {% endif %}{{type}} {{width}}x{{height}} {{file_size}} :: {{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"""))) + registry.String("^{%if section %} [{{section}}] {% endif -%}{%- if title -%} {{title}} :: {% endif %}{{type}} {{width}}x{{height}} {{file_size}} :: {{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"""))) - +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"""))) + + diff --git a/plugin.py b/plugin.py index b226155..988fc1a 100644 --- a/plugin.py +++ b/plugin.py @@ -24,7 +24,7 @@ from jinja2 import Template try: from supybot.i18n import PluginInternationalization - _ = PluginInternationalization('SpiffyTitles') + _ = PluginInternationalization("SpiffyTitles") except ImportError: # Placeholder that allows to run the plugin on a bot # without the i18n module @@ -33,7 +33,7 @@ except ImportError: class SpiffyTitles(callbacks.Plugin): """Displays link titles when posted in a channel""" threaded = True - callBefore = ['Web'] + callBefore = ["Web"] link_cache = {} handlers = {} @@ -41,7 +41,7 @@ class SpiffyTitles(callbacks.Plugin): self.__parent = super(SpiffyTitles, self) self.__parent.__init__(irc) - self.link_throttle_in_seconds = self.registryValue('cooldownInSeconds') + self.link_throttle_in_seconds = self.registryValue("cooldownInSeconds") """ Check if imgur client id or secret are set, and if so initialize @@ -51,8 +51,11 @@ class SpiffyTitles(callbacks.Plugin): imgur_client_secret = self.registryValue("imgurClientSecret") if imgur_client_id and imgur_client_secret: - # Set handler - self.handlers["i.imgur.com"] = self.handler_imgur + # Images mostly + self.handlers["i.imgur.com"] = self.handler_imgur_image + + # Albums, galleries, etc + self.handlers["imgur.com"] = self.handler_imgur # Initialize API client try: @@ -65,10 +68,7 @@ class SpiffyTitles(callbacks.Plugin): except ImportError, e: self.log.error("SpiffyTitles ImportError: %s" % str(e)) - self.handlers["youtube.com"] = self.handler_youtube - self.handlers["www.youtube.com"] = self.handler_youtube - self.handlers["youtu.be"] = self.handler_youtube - self.handlers["m.youtube.com"] = self.handler_youtube + self.add_youtube_handlers() def doPrivmsg(self, irc, msg): """ @@ -92,45 +92,53 @@ class SpiffyTitles(callbacks.Plugin): info = urlparse(url) - if info: - domain = info.netloc - is_ignored = self.is_ignored_domain(domain) - - if is_ignored: - self.log.info("SpiffyTitles: ignoring url due to pattern match: %s" % (url)) - return - - # Check if we've seen this link lately - if url in self.link_cache: - link_timestamp = self.link_cache[url] - - seconds = (now - link_timestamp).total_seconds() - throttled = seconds < self.link_throttle_in_seconds - else: - throttled = False - - if throttled: - self.log.info("SpiffyTitles: %s ignored; throttle: it has been %s seconds since last post" % (url, seconds)) - return - - # Update link cache now that we know it's not an ignored link - self.link_cache[url] = now - - try: - handler = self.handlers[domain] - title = handler(url, info, irc) - except KeyError: - title = self.handler_default(url, info, irc) - else: - self.log.error("SpiffyTitles: unable to determine domain from url %s" % (url)) - title = self.handler_default(url, irc) + domain = info.netloc + is_ignored = self.is_ignored_domain(domain) - if title is not None: + if is_ignored: + self.log.info("SpiffyTitles: ignoring url due to pattern match: %s" % (url)) + return + + # Check if we"ve seen this link lately + if url in self.link_cache: + link_timestamp = self.link_cache[url] + + seconds = (now - link_timestamp).total_seconds() + throttled = seconds < self.link_throttle_in_seconds + else: + throttled = False + + if throttled: + self.log.info("SpiffyTitles: %s ignored; throttle: it has been %s seconds since last post" % (url, seconds)) + return + + # Update link cache now that we know it"s not an ignored link + self.link_cache[url] = now + + if domain in self.handlers: + handler = self.handlers[domain] + title = handler(url, info) + else: + title = self.handler_default(url, info) + + if title is not None and title: self.log.info("SpiffyTitles: title found: %s" % (title)) formatted_title = self.get_formatted_title(title) irc.reply(formatted_title) + else: + self.log.error("SpiffyTitles: could not get a title for %s" % (url)) + + def add_youtube_handlers(self): + """ + Adds handlers for Youtube videos. The handler is matched based on the + domain used in the URL. + """ + self.handlers["youtube.com"] = self.handler_youtube + self.handlers["www.youtube.com"] = self.handler_youtube + self.handlers["youtu.be"] = self.handler_youtube + self.handlers["m.youtube.com"] = self.handler_youtube def is_channel_allowed(self, channel): """ @@ -183,7 +191,7 @@ class SpiffyTitles(callbacks.Plugin): except re.Error: self.log.error("SpiffyTitles: invalid regular expression: %s" % (pattern)) - def get_video_id_from_url(self, url, info, irc): + def get_video_id_from_url(self, url, info): """ Get YouTube video ID from URL """ @@ -206,13 +214,13 @@ class SpiffyTitles(callbacks.Plugin): except IndexError, e: self.log.error("SpiffyTitles: error getting video id from %s (%s)" % (url, str(e))) - def handler_youtube(self, url, domain, irc): + def handler_youtube(self, url, domain): """ Uses the Youtube API to provide additional meta data about Youtube Video links posted. """ self.log.info("SpiffyTitles: calling Youtube handler for %s" % (url)) - video_id = self.get_video_id_from_url(url, domain, irc) + video_id = self.get_video_id_from_url(url, domain) yt_template = Template(self.registryValue("youtubeTitleTemplate")) title = "" @@ -234,13 +242,13 @@ 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']) + title = data["title"] + rating = str(round(data["rating"], 2)) + view_count = "{:,}".format(int(data["viewCount"])) + duration_seconds = int(data["duration"]) """ - #23 - If duration is zero, then it's a LIVE video + #23 - If duration is zero, then it"s a LIVE video """ if duration_seconds > 0: m, s = divmod(duration_seconds, 60) @@ -277,9 +285,9 @@ class SpiffyTitles(callbacks.Plugin): else: self.log.info("SpiffyTitles: falling back to default handler") - return self.handler_default(url, domain, irc) + return self.handler_default(url, domain) - def handler_default(self, url, domain, irc): + def handler_default(self, url, domain): """ Default handler for websites """ @@ -295,22 +303,82 @@ class SpiffyTitles(callbacks.Plugin): return title_template - def handler_imgur(self, url, info, irc): + def handler_imgur(self, url, info): """ - Queries imgur API for additional information about imgur links + Queries imgur API for additional information about imgur links. + + This handler is for any imgur.com domain. """ + is_album = info.path.startswith("/a/") + is_gallery = info.path.startswith("/gallery/") + result = None + if is_album: + result = self.handler_imgur_album(url, info) + else: + result = self.handler_default(url, info) + + return result + + def handler_imgur_album(self, url, info): """ + Handles retrieving information about albums from the imgur API. + + imgur provides the following information about albums: https://api.imgur.com/models/album + """ + from imgurpython.helpers.error import ImgurClientRateLimitError + from imgurpython.helpers.error import ImgurClientError + + album_id = info.path.split("/a/")[1] + + """ If there is a query string appended, remove it """ + if "?" in album_id: + album_id = album_id.split("?")[0] + + if album_id: + self.log.info("SpiffyTitles: found imgur album id %s" % (album_id)) + + try: + album = self.imgur_client.get_album(album_id) + + if album: + imgur_album_template = Template(self.registryValue("imgurAlbumTemplate")) + compiled_template = imgur_album_template.render({ + "title": album.title, + "section": album.section, + "view_count": "{:,}".format(album.views), + "image_count": "{:,}".format(album.images_count), + "nsfw": album.nsfw + }) + + return compiled_template + else: + self.log.error("SpiffyTitles: imgur album API returned unexpected results!") + + except ImgurClientRateLimitError as e: + self.log.error("SpiffyTitles: imgur rate limit error: %s" % (e.error_message)) + except ImgurClientError as e: + self.log.error("SpiffyTitles: imgur client error: %s" % (e.error_message)) + else: + self.log.info("SpiffyTitles: unable to determine album id for %s" % (url)) + + def handler_imgur_image(self, url, info): + """ + 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 + forward slash and then split by period to get the image id. """ from imgurpython.helpers.error import ImgurClientRateLimitError from imgurpython.helpers.error import ImgurClientError path = info.path.lstrip("/") image_id = path.split(".")[0] - - self.log.info("SpiffyTitles: image id found: %s" % (image_id)) + title = None if image_id: try: @@ -325,41 +393,35 @@ class SpiffyTitles(callbacks.Plugin): "nsfw": image.nsfw, "width": image.width, "height": image.height, - "view_count": '{:,}'.format(image.views), + "view_count": "{:,}".format(image.views), "file_size": readable_file_size, "section": image.section }) - return compiled_template + title = compiled_template else: self.log.error("SpiffyTitles: imgur API returned unexpected results!") - - # Fall back to default handler - self.handler_default(url, info, irc) - except ImgurClientRateLimitError as e: self.log.error("SpiffyTitles: imgur rate limit error: %s" % (e.error_message)) - - # Fall back to default handler - self.handler_default(url, info, irc) - except ImgurClientError as e: self.log.error("SpiffyTitles: imgur client error: %s" % (e.error_message)) - - # Fall back to default handler - self.handler_default(url, info, irc) else: self.log.error("SpiffyTitles: error retrieving image id for %s" % (url)) + + if title is not None: + return title + else: + return self.handler_default(url, info) - def get_readable_file_size(self, num, suffix='B'): + def get_readable_file_size(self, num, suffix="B"): """ Returns human readable file size """ - for unit in ['','Ki','Mi','Gi','Ti','Pi','Ei','Zi']: + for unit in ["","Ki","Mi","Gi","Ti","Pi","Ei","Zi"]: if abs(num) < 1024.0: return "%3.1f%s%s" % (num, unit, suffix) num /= 1024.0 - return "%.1f%s%s" % (num, 'Yi', suffix) + return "%.1f%s%s" % (num, "Yi", suffix) def get_formatted_title(self, title): """ @@ -411,9 +473,9 @@ class SpiffyTitles(callbacks.Plugin): } request = requests.get(url, headers=headers) - ok = request.status_code == requests.codes.ok + self.log.info("SpiffyTitles: requesting %s" % (url)) - if ok: + if request.status_code == requests.codes.ok: # Check the content type which comes in the format: "text/html; charset=UTF-8" content_type = request.headers.get("content-type").split(";")[0].strip() acceptable_types = self.registryValue("mimeTypes") @@ -424,7 +486,11 @@ class SpiffyTitles(callbacks.Plugin): if mime_type_acceptable: text = request.content - return text + if text: + return text + else: + self.log.info("SpiffyTitles: empty content from %s" % (url)) + else: self.log.debug("SpiffyTitles: unacceptable mime type %s for url %s" % (content_type, url)) else: @@ -441,11 +507,17 @@ class SpiffyTitles(callbacks.Plugin): self.log.error("SpiffyTitles InvalidURL: %s" % (str(e))) def get_user_agent(self): + """ + Returns a random user agent from the ones available + """ agents = self.registryValue("userAgents") return random.choice(agents) def get_url_from_message(self, input): + """ + Find the first string that looks like a URL from the message + """ url_re = self.registryValue("urlRegularExpression") match = re.search(url_re, input) @@ -454,5 +526,4 @@ class SpiffyTitles(callbacks.Plugin): Class = SpiffyTitles - # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: