diff --git a/Markovgen/README.md b/Markovgen/README.md deleted file mode 100644 index 18a3664..0000000 --- a/Markovgen/README.md +++ /dev/null @@ -1,17 +0,0 @@ -Generate markov replies to channel messages. - -Forked from: https://github.com/ProgVal/Supybot-plugins/tree/master/Markovgen - -Additions: - -plugins.Markovgen.ignorePattern (set regex patterns for the bot to ignore) - -plugins.Markovgen.stripPattern (set regex patterns for the bot to strip) - -plugins.Markovgen.stripURL (determine if the bot will strip URLs) - -plugins.Markovgen.ignoreNicks (list of nicks to ignore) - -plugins.Markovgen.ignoreCommands (ignore commands sent to the bot) - -plugins.Markovgen.stripFormatting (strip bold, underline, and color from messages) diff --git a/Markovgen/__init__.py b/Markovgen/__init__.py deleted file mode 100644 index 9523426..0000000 --- a/Markovgen/__init__.py +++ /dev/null @@ -1,72 +0,0 @@ -### -# Copyright (c) 2014, Valentin Lorentz -# Copyright (c) 2020, oddluck -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, -# this list of conditions, and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions, and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# * Neither the name of the author of this software nor the name of -# contributors to this software may be used to endorse or promote products -# derived from this software without specific prior written consent. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. -### - -""" -Markovgen: reply to messages with markov generated text -""" - -import supybot -import supybot.world as world - -# Use this for the version of this plugin. You may wish to put a CVS keyword -# in here if you're keeping the plugin in CVS or some similar system. -__version__ = "2020.02.24+git" - -# XXX Replace this with an appropriate author or supybot.Author instance. - -__author__ = getattr(supybot.authors, 'progval', - supybot.Author('Valentin Lorentz', 'ProgVal', 'progval@gmail.com')) -__maintainer__ = getattr(supybot.authors, 'oddluck', - supybot.Author('oddluck', 'oddluck', 'oddluck@riseup.net')) - -# This is a dictionary mapping supybot.Author instances to lists of -# contributions. -__contributors__ = {} - -# This is a url where the most recent plugin package can be downloaded. -__url__ = 'https://github.com/oddluck/limnoria-plugins/' - -from . import config -from . import plugin -from imp import reload -# In case we're being reloaded. -reload(config) -reload(plugin) -# Add more reloads here if you add third-party modules and want them to be -# reloaded when this plugin is reloaded. Don't forget to import them as well! - -if world.testing: - from . import test - -Class = plugin.Class -configure = config.configure - - -# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: diff --git a/Markovgen/config.py b/Markovgen/config.py deleted file mode 100644 index d172aea..0000000 --- a/Markovgen/config.py +++ /dev/null @@ -1,88 +0,0 @@ -### -# Copyright (c) 2014, Valentin Lorentz -# Copyright (c) 2020, oddluck -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, -# this list of conditions, and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions, and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# * Neither the name of the author of this software nor the name of -# contributors to this software may be used to endorse or promote products -# derived from this software without specific prior written consent. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. -### - -import supybot.conf as conf -import supybot.registry as registry -try: - from supybot.i18n import PluginInternationalization - _ = PluginInternationalization('Markovgen') -except: - # Placeholder that allows to run the plugin on a bot - # without the i18n module - _ = lambda x:x - -def configure(advanced): - # This will be called by supybot to configure this module. advanced is - # a bool that specifies whether the user identified themself as an advanced - # user or not. You should effect your configuration by manipulating the - # registry as appropriate. - from supybot.questions import expect, anything, something, yn - conf.registerPlugin('Markovgen', True) - - -Markovgen = conf.registerPlugin('Markovgen') -# This is where your configuration variables (if any) should go. For example: -# conf.registerGlobalValue(Markovgen, 'someConfigVariableName', -# registry.Boolean(False, _("""Help for someConfigVariableName."""))) -conf.registerChannelValue(Markovgen, 'enable', - registry.Boolean(False, _("""Determines whether the plugin is enabled - on a channel. This defaults to False to avoid useless resources - consumption."""))) -conf.registerChannelValue(Markovgen, 'probability', - registry.Probability(0, _("""Determine the probability the bot has to - reply to a message."""))) -conf.registerChannelValue(Markovgen, 'stripRelayedNick', - registry.Boolean(True, _("""Determines whether the bot will strip - strings like at the beginning of messages."""))) -conf.registerChannelValue(Markovgen, 'ignorePattern', - registry.Regexp("", _("""Mesages matching this pattern will be ignored."""))) -conf.registerChannelValue(Markovgen, 'stripPattern', - registry.Regexp("", _("""Text matching this pattern will be stripped."""))) -conf.registerChannelValue(Markovgen, 'stripURL', - registry.Boolean(True, _("""Determines whether the bot will strip - URLs from messages."""))) -conf.registerChannelValue(Markovgen, 'ignoreNicks', - registry.SpaceSeparatedListOfStrings([], _("""A list of nicks to be ignored by the bot"""))) -conf.registerChannelValue(Markovgen, 'stripFormatting', - registry.Boolean(True, _("""Determines whether the bot will strip - bold, underline, and colors from messages."""))) -conf.registerChannelValue(Markovgen, 'ignoreCommands', - registry.Boolean(True, _("""Determines whether the bot will ignore commands."""))) - -conf.registerGroup(Markovgen, 'onNick') -conf.registerChannelValue(Markovgen.onNick, 'probability', - registry.Probability(0, _("""Determine the probability the bot has to - reply to a message containing its nick."""))) -conf.registerChannelValue(Markovgen.onNick, 'replaceNick', - registry.Boolean(True, _("""Determine whether the bot will replace its - nick by the original author's when replying to a message containing - its nick."""))) - -# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: diff --git a/Markovgen/plugin.py b/Markovgen/plugin.py deleted file mode 100644 index e8dd51d..0000000 --- a/Markovgen/plugin.py +++ /dev/null @@ -1,254 +0,0 @@ -### -# Copyright (c) 2014, Valentin Lorentz -# Copyright (c) 2020, oddluck -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, -# this list of conditions, and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions, and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# * Neither the name of the author of this software nor the name of -# contributors to this software may be used to endorse or promote products -# derived from this software without specific prior written consent. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. -### - -import os -import re -import sys -import glob -import random -import functools - -import supybot.conf as conf -import supybot.world as world -import supybot.utils as utils -from supybot.commands import * -import supybot.plugins as plugins -import supybot.ircutils as ircutils -import supybot.callbacks as callbacks -import supybot.log as log -try: - from supybot.i18n import PluginInternationalization - _ = PluginInternationalization('Markovgen') -except ImportError: - # Placeholder that allows to run the plugin on a bot - # without the i18n module - _ = lambda x:x - -try: - import markovgen -except ImportError: - raise callbacks.Error('Cannot load markovgen library. Make sure you ' - 'installed it (%s -m pip install markovgen).' - % sys.executable) -from imp import reload as r -r(markovgen) - -MATCH_MESSAGE_STRIPNICK = re.compile('^(<[^ ]+> )?(?P.*)$') - -CHANNELLOGER_REGEXP_BASE = re.compile('^[^ ]* (<[^ ]+> )?(?P.*)$') -CHANNELLOGER_REGEXP_STRIPNICK = re.compile('^[^ ]* (<[^ ]+> )?(<[^ ]+> )?(?P.*)$') - -def get_channelloger_extracter(stripRelayedNick): - @markovgen.mixed_encoding_extracting - def channelloger_extracter(x): - regexp = CHANNELLOGER_REGEXP_STRIPNICK if stripRelayedNick else \ - CHANNELLOGER_REGEXP_BASE - m = regexp.match(x) - if m: - return m.group('message') - return channelloger_extracter - -def get_extracter(name): - regexp = re.compile(markovgen.REGEXPS[name]) - @markovgen.mixed_encoding_extracting - def extracter(x): - msg = regexp.match(x) - if msg: - return msg.group('message') - return extracter - -def rec_list_files(path): - return (os.path.join(dp, f) - for dp, dn, filenames in os.walk(path) - for f in filenames) - -class Markovgen(callbacks.Plugin): - """Add the help for "@plugin help Markovgen" here - This should describe *how* to use this plugin.""" - threaded = True - - def __init__(self, irc): - super(Markovgen, self).__init__(irc) - self._markovs = {} - - def _load_from_channellogger(self, irc, channel, m): - cb = irc.getCallback('ChannelLogger') - if not cb: - return - extracter = get_channelloger_extracter( - self.registryValue('stripRelayedNick', channel)) - for irc in world.ircs: - for filename in glob.glob(cb.getLogDir(irc, channel) + '/*.log'): - with open(filename, 'rb') as fd: - m.feed_from_file(fd, extracter) - - def _load_from_data(self, irc, channel, m): - base_path = os.path.join(conf.supybot.directories.data(), 'Markovgen', channel) - if not os.path.isdir(base_path): - return - for extracter_name in os.listdir(base_path): - extracter = get_extracter(extracter_name) - path = os.path.join(base_path, extracter_name) - path = glob.escape(path) - filenames = rec_list_files(path) - for filename in filenames: - with open(filename, 'rb') as fd: - m.feed_from_file(fd, extracter) - - - def _get_markov(self, irc, channel): - if channel not in self._markovs: - m = markovgen.Markov() - self._markovs[channel] = m - self._load_from_channellogger(irc, channel, m) - self._load_from_data(irc, channel, m) - else: - m = self._markovs[channel] - return m - - def doPrivmsg(self, irc, msg): - (channel, message) = msg.args - if not irc.isChannel(channel): - return - if not self.registryValue('enable', channel): - return - if self.registryValue('ignoreCommands', channel) and callbacks.addressed(irc.nick, msg): - return - match = False - ignore = self.registryValue("ignorePattern", channel) - strip = self.registryValue("stripPattern", channel) - if ignore: - match = re.search(ignore, message) - if match: - log.debug("Markovgen: %s matches ignorePattern for %s" % (message, channel)) - return - if msg.nick.lower() in self.registryValue('ignoreNicks', channel): - log.debug("Markovgen: nick %s in ignoreNicks for %s" % (msg.nick, channel)) - return - m = self._get_markov(irc, channel) - if self.registryValue('stripFormatting', channel): - message = ircutils.stripFormatting(message) - if strip: - match = re.findall(strip, message) - if match: - for x in match: - message = message.replace(x, '') - message = re.sub('\s+', ' ', message) - log.debug("Markovgen: %s matches stripPattern for %s. New message text: %s" % (x, channel, message)) - if self.registryValue('stripURL', channel): - new_message = re.sub(r'(?i)\b((?:[a-z][\w-]+:(?:/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:\'".,<>?«»“”‘’]))', '', message) - new_message = re.sub('\s+', ' ', new_message) - if new_message != message: - log.debug("Markovgen: url(s) stripped from message for %s. New message text: %s" % (channel, new_message)) - message = new_message - if self.registryValue('stripRelayedNick', channel): - message = MATCH_MESSAGE_STRIPNICK.match(message).group('message') - m.feed(message) - tokenized_message = (w.strip(':;,.!?') - for w in message.lower().split()) - if irc.nick.lower() in tokenized_message: - if random.random() < self.registryValue('onNick.probability', channel): - def replace_nick(s): - return re.sub(re.escape(irc.nick), msg.nick, s, re.IGNORECASE) - self._answer(irc, message, m, False, - postprocessing=replace_nick) - else: - if random.random() < self.registryValue('probability', channel): - self._answer(irc, message, m, False) - - @wrap(['channel', optional('text')]) - def gen(self, irc, msg, args, channel, message): - """[] [] - - Generates a random message based on the logs of a channel - and a seed""" - if not self.registryValue('enable', channel): - irc.error(_('Markovgen is disabled for this channel.'), - Raise=True) - m = self._get_markov(irc, channel) - if message: - m.feed(message) - self._answer(irc, message or '', m, True) - - - def _answer(self, irc, message, m, allow_duplicate, - postprocessing=lambda x: x): - words = message.split() - if len(words) == 0: - possibilities = list(m.available_seeds()) - elif len(words) == 1: - word = words[0] - seeds = list(m.available_seeds()) - possibilities = [x for x in seeds if word in x] - else: - message_tuples = set(zip(words, words[1:])) - if not message_tuples: - return - seeds = list(m.available_seeds()) - possibilities = [x for x in seeds if x in message_tuples] - seed = list(random.choice(possibilities)) - backward_seed = list(reversed(seed)) - forward = m.generate_markov_text(seed=seed, backward=False) - backward = m.generate_markov_text(seed=backward_seed, - backward=True) - try: - answer = '%s %s' % (backward, forward.split(' ', 2)[2]) - except IndexError: - answer = backward - if allow_duplicate or message != answer: - irc.reply(postprocessing(answer), prefixNick=False) - - @wrap(['channel']) - def doge(self, irc, msg, args, channel): - """takes no arguments - - Generates a doge.""" - if not self.registryValue('enable', channel): - irc.error(_('Markovgen is disabled for this channel.'), - Raise=True) - r = re.compile('^[a-zA-Zéèàù]{5,}$') - def pred(x): - if not r.match(x): - return None - else: - return x - m = self._get_markov(irc, channel) - words = m.words - words = filter(bool, map(pred, words)) - words = [x.strip(',?;.:/!') for x in m.words if pred(x)] - w2 = random.choice(words) - w1 = random.choice(['such', 'many', 'very']) - irc.reply('%s %s' % (w1, w2)) - - -Class = Markovgen - - -# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/Markovgen/requirements.txt b/Markovgen/requirements.txt deleted file mode 100644 index 9e67dfe..0000000 --- a/Markovgen/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -markovgen \ No newline at end of file diff --git a/SpiffyTitles/__init__.py b/SpiffyTitles/__init__.py index d2a651e..9bde0b0 100644 --- a/SpiffyTitles/__init__.py +++ b/SpiffyTitles/__init__.py @@ -40,20 +40,24 @@ import supybot.world as world __version__ = "2020.02.24+git" # XXX Replace this with an appropriate author or supybot.Author instance. -__author__ = supybot.Author('butterscotchstallion', 'butterscotchstallion', '') -__maintainer__ = getattr(supybot.authors, 'oddluck', - supybot.Author('oddluck', 'oddluck', 'oddluck@riseup.net')) +__author__ = supybot.Author("butterscotchstallion", "butterscotchstallion", "") +__maintainer__ = getattr( + supybot.authors, + "oddluck", + supybot.Author("oddluck", "oddluck", "oddluck@riseup.net"), +) # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} # This is a url where the most recent plugin package can be downloaded. -__url__ = 'https://github.com/oddluck/limnoria-plugins/' +__url__ = "https://github.com/oddluck/limnoria-plugins/" from . import config from . import plugin from imp import reload + # In case we're being reloaded. reload(plugin) # Add more reloads here if you add third-party modules and want them to be diff --git a/SpiffyTitles/config.py b/SpiffyTitles/config.py index afd7c46..519e8dc 100644 --- a/SpiffyTitles/config.py +++ b/SpiffyTitles/config.py @@ -30,9 +30,11 @@ import supybot.conf as conf import supybot.registry as registry + try: from supybot.i18n import PluginInternationalization - _ = PluginInternationalization('SpiffyTitles') + + _ = PluginInternationalization("SpiffyTitles") except: # Placeholder that allows to run the plugin on a bot # without the i18n module @@ -45,269 +47,601 @@ def configure(advanced): # user or not. You should effect your configuration by manipulating the # registry as appropriate. from supybot.questions import expect, anything, something, yn - conf.registerPlugin('SpiffyTitles', True) + + conf.registerPlugin("SpiffyTitles", True) -SpiffyTitles = conf.registerPlugin('SpiffyTitles') +SpiffyTitles = conf.registerPlugin("SpiffyTitles") -conf.registerGlobalValue(SpiffyTitles, 'maxRetries', - registry.Integer(3, _("""Maximum retries upon failure"""))) +conf.registerGlobalValue( + SpiffyTitles, + "maxRetries", + registry.Integer(3, _("""Maximum retries upon failure""")), +) -conf.registerGlobalValue(SpiffyTitles, 'wallClockTimeoutInSeconds', - registry.Integer(8, _("""Timeout for getting a title. If you set this too high, the bot will time out."""))) +conf.registerGlobalValue( + SpiffyTitles, + "wallClockTimeoutInSeconds", + registry.Integer( + 8, + _( + """ + Timeout for getting a title. If you set this too high, + the bot will time out.""" + ), + ), +) # Language -conf.registerGlobalValue(SpiffyTitles, 'language', - registry.String("en-US", _("""Language code"""))) +conf.registerGlobalValue( + SpiffyTitles, "language", registry.String("en-US", _("""Language code""")) +) # URL regex -conf.registerGlobalValue(SpiffyTitles, 'urlRegularExpression', - registry.String(r"(https?:\/\/(?:www\.|(?!www))[^\s\.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,})", _("""This regular expression will be used to match URLs"""))) +conf.registerGlobalValue( + SpiffyTitles, + "urlRegularExpression", + registry.String( + r"(https?:\/\/(?:www\.|(?!www))[^\s\.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,})", + _("""This regular expression will be used to match URLs"""), + ), +) # Bold -conf.registerChannelValue(SpiffyTitles, 'useBold', - registry.Boolean(False, _("""Use bold in titles"""))) +conf.registerChannelValue( + SpiffyTitles, "useBold", registry.Boolean(False, _("""Use bold in titles""")) +) # User agents -conf.registerGlobalValue(SpiffyTitles, 'userAgents', - registry.CommaSeparatedListOfStrings(["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:73.0) Gecko/20100101 Firefox/73.0", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0"], _("""Reported user agent when fetching links"""))) +conf.registerGlobalValue( + SpiffyTitles, + "userAgents", + registry.CommaSeparatedListOfStrings( + [ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/79.0.3945.130 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:73.0) " + "Gecko/20100101 Firefox/73.0", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/80.0.3987.122 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) " + "Gecko/20100101 Firefox/74.0", + ], + _("""Reported user agent when fetching links"""), + ), +) # Bad link text -conf.registerChannelValue(SpiffyTitles, 'badLinkText', - registry.String("Nice link idiot.", _("""Title to return for bad/unsnarfable links."""))) +conf.registerChannelValue( + SpiffyTitles, + "badLinkText", + registry.String( + "Error retrieving title. Check the log for more details.", + _("""Title to return for bad/unsnarfable links."""), + ), +) # Mime types -conf.registerGlobalValue(SpiffyTitles, 'mimeTypes', - registry.CommaSeparatedListOfStrings(["text/html"], _("""Acceptable mime types for displaying titles"""))) +conf.registerGlobalValue( + SpiffyTitles, + "mimeTypes", + registry.CommaSeparatedListOfStrings( + ["text/html"], _("""Acceptable mime types for displaying titles""") + ), +) # Ignored domain pattern -conf.registerChannelValue(SpiffyTitles, 'ignoredDomainPattern', - registry.Regexp("", _("""Domains matching this patterns will be ignored"""))) +conf.registerChannelValue( + SpiffyTitles, + "ignoredDomainPattern", + registry.Regexp("", _("""Domains matching this patterns will be ignored""")), +) # Whitelist domain pattern -conf.registerChannelValue(SpiffyTitles, 'whitelistDomainPattern', - registry.Regexp("", _("""Domains not matching this patterns will be ignored"""))) +conf.registerChannelValue( + SpiffyTitles, + "whitelistDomainPattern", + registry.Regexp("", _("""Domains not matching this patterns will be ignored""")), +) # Channel whitelist -conf.registerGlobalValue(SpiffyTitles, 'channelWhitelist', - registry.CommaSeparatedListOfStrings([], _("""Only show titles on these channels, or all if empty"""))) +conf.registerGlobalValue( + SpiffyTitles, + "channelWhitelist", + registry.CommaSeparatedListOfStrings( + [], _("""Only show titles on these channels, or all if empty""") + ), +) # Channel blacklist -conf.registerGlobalValue(SpiffyTitles, 'channelBlacklist', - registry.CommaSeparatedListOfStrings([], _("""Never show titles on these channels"""))) +conf.registerGlobalValue( + SpiffyTitles, + "channelBlacklist", + registry.CommaSeparatedListOfStrings( + [], _("""Never show titles on these channels""") + ), +) # Link cache lifetime -conf.registerGlobalValue(SpiffyTitles, 'linkCacheLifetimeInSeconds', - registry.Integer(60, _("""Link cache lifetime in seconds"""))) +conf.registerGlobalValue( + SpiffyTitles, + "linkCacheLifetimeInSeconds", + registry.Integer(60, _("""Link cache lifetime in seconds""")), +) -conf.registerChannelValue(SpiffyTitles, 'onDemandTitleError', - registry.String("Error retrieving title.", _("""This error message is used when there is a problem getting an on-demand title"""))) +conf.registerChannelValue( + SpiffyTitles, + "onDemandTitleError", + registry.String( + "Error retrieving title.", + _( + """ + This error message is used when there is a problem getting + an on-demand title + """ + ), + ), +) -conf.registerGlobalValue(SpiffyTitles, 'linkMessageIgnorePattern', - registry.Regexp("", _("""Messages matching this pattern will be ignored."""))) +conf.registerGlobalValue( + SpiffyTitles, + "linkMessageIgnorePattern", + registry.Regexp("", _("""Messages matching this pattern will be ignored.""")), +) -conf.registerChannelValue(SpiffyTitles, 'ignoreActionLinks', - registry.Boolean(True, _("""Ignores URLs that appear in an action such as /me"""))) +conf.registerChannelValue( + SpiffyTitles, + "ignoreActionLinks", + registry.Boolean(True, _("""Ignores URLs that appear in an action such as /me""")), +) -conf.registerChannelValue(SpiffyTitles, 'requireCapability', - registry.String("", _("""If defined, SpiffyTitles will only acknowledge links from users with this capability. Useful for hostile environments."""))) +conf.registerChannelValue( + SpiffyTitles, + "requireCapability", + registry.String( + "", + _( + """ + If defined, SpiffyTitles will only acknowledge links from users with this + capability. Useful for hostile environments. + """ + ), + ), +) -conf.registerChannelValue(SpiffyTitles, 'ignoredTitlePattern', - registry.Regexp("", _("""Titles matching this pattern will be ignored."""))) +conf.registerChannelValue( + SpiffyTitles, + "ignoredTitlePattern", + registry.Regexp("", _("""Titles matching this pattern will be ignored.""")), +) +# default configs +conf.registerGroup(SpiffyTitles, "default") -#default configs -conf.registerGroup(SpiffyTitles, 'default') - -conf.registerChannelValue(SpiffyTitles.default, 'enabled', - registry.Boolean(True, _("""Whether to add additional information about regular links"""))) +conf.registerChannelValue( + SpiffyTitles.default, + "enabled", + registry.Boolean( + True, _("""Whether to add additional information about regular links""") + ), +) # default title template - show a warning if redirects to a different domain -conf.registerChannelValue(SpiffyTitles.default, 'template', - registry.String("{% if redirect %}(REDIRECT) {% endif %}^ {{title}}", _("""Template used for default title responses"""))) +conf.registerChannelValue( + SpiffyTitles.default, + "template", + registry.String( + "{% if redirect %}(REDIRECT) {% endif %}^ {{title}}", + _("""Template used for default title responses"""), + ), +) +# default file template - show a warning if redirects to a different domain +conf.registerChannelValue( + SpiffyTitles.default, + "fileTemplate", + registry.String( + "{% if type %}[{{type}}] {% endif %}" + "{% if size %}({{size}}){% endif %}", + _("""Template used for default title responses"""), + ), +) +# Wikipedia configs +conf.registerGroup(SpiffyTitles, "wikipedia") +# wikipedia enabler +conf.registerChannelValue( + SpiffyTitles.wikipedia, + "enabled", + registry.Boolean(True, _("""Whether to fetch extracts for Wikipedia articles.""")), +) -#Wikipedia configs -conf.registerGroup(SpiffyTitles, 'wikipedia') +conf.registerChannelValue( + SpiffyTitles.wikipedia, + "apiParams", + registry.SpaceSeparatedListOfStrings( + [], + _( + """ + Add/override API parameters with a space-separated list of key=value pairs. + """ + ), + ), +) -#wikipedia enabler -conf.registerChannelValue(SpiffyTitles.wikipedia, 'enabled', - registry.Boolean(True, _("""Whether to fetch extracts for Wikipedia articles."""))) +conf.registerChannelValue( + SpiffyTitles.wikipedia, + "titleParam", + registry.String( + "titles", + _("""The query parameter that will hold the page title from the URL."""), + ), +) -conf.registerChannelValue(SpiffyTitles.wikipedia, 'apiParams', - registry.SpaceSeparatedListOfStrings([], _("""Add or override API query parameters with a space-separated list of key=value pairs."""))) +# Ideally, links to specific article sections would produce the relevant output for +# that section. This is not currently implemented. +conf.registerChannelValue( + SpiffyTitles.wikipedia, + "ignoreSectionLinks", + registry.Boolean(True, _("""Ignore links to specific article sections.""")), +) -conf.registerChannelValue(SpiffyTitles.wikipedia, 'titleParam', - registry.String("titles", _("""The query parameter that will hold the page title from the URL."""))) - -# Ideally, links to specific article sections would produce the relevant output for that section. This is not currently implemented. -conf.registerChannelValue(SpiffyTitles.wikipedia, 'ignoreSectionLinks', - registry.Boolean(True, _("""Ignore links to specific article sections."""))) - -conf.registerChannelValue(SpiffyTitles.wikipedia, 'maxChars', - registry.Integer(400, _("""Extract will be cut to this length (including '...')."""))) +conf.registerChannelValue( + SpiffyTitles.wikipedia, + "maxChars", + registry.Integer( + 400, _("""Extract will be cut to this length (including '...').""") + ), +) # Remove parenthesized text from output. -conf.registerChannelValue(SpiffyTitles.wikipedia, 'removeParentheses', - registry.Boolean(True, _("""Remove parenthesized text from output."""))) +conf.registerChannelValue( + SpiffyTitles.wikipedia, + "removeParentheses", + registry.Boolean(True, _("""Remove parenthesized text from output.""")), +) -conf.registerChannelValue(SpiffyTitles.wikipedia, 'extractTemplate', - registry.String("^ {{extract}}", _("""Wikipedia template."""))) +conf.registerChannelValue( + SpiffyTitles.wikipedia, + "extractTemplate", + registry.String("^ {{extract}}", _("""Wikipedia template.""")), +) +# Reddit configs +conf.registerGroup(SpiffyTitles, "reddit") -#Reddit configs -conf.registerGroup(SpiffyTitles, 'reddit') +# Reddit enabler +conf.registerChannelValue( + SpiffyTitles.reddit, + "enabled", + registry.Boolean(True, _("""Whether to add additional info about Reddit links.""")), +) -#Reddit enabler -conf.registerChannelValue(SpiffyTitles.reddit, 'enabled', - registry.Boolean(True, _("""Whether to add additional info about Reddit links."""))) +# Reddit templates +conf.registerChannelValue( + SpiffyTitles.reddit, + "linkThreadTemplate", + registry.String( + "/r/{{subreddit}}{% if title %} :: {{title}}{% endif %} :: {{score}} " + "points ({{percent}}) :: {{comments}} comments :: Posted {{age}} by " + "{{author}}{% if url %} :: {{url}} ({{domain}}){% endif %}", + _("""Template used for Reddit link thread title responses"""), + ), +) -#Reddit templates -conf.registerChannelValue(SpiffyTitles.reddit, 'linkThreadTemplate', - registry.String(u"/r/{{subreddit}}{% if title %} :: {{title}}{% endif %} :: {{score}} points ({{percent}}) :: {{comments}} comments :: Posted {{age}} by {{author}}{% if url %} :: {{url}} ({{domain}}){% endif %}", _("""Template used for Reddit link thread title responses"""))) +conf.registerChannelValue( + SpiffyTitles.reddit, + "textThreadTemplate", + registry.String( + "/r/{{subreddit}}{% if title %} :: {{title}}{% endif %}{% if extract %} " + ":: {{extract}}{% endif %} :: {{score}} points ({{percent}}) :: {{comments}} " + "comments :: Posted {{age}} by {{author}}", + _("""Template used for Reddit text thread title responses"""), + ), +) -conf.registerChannelValue(SpiffyTitles.reddit, 'textThreadTemplate', - registry.String(u"/r/{{subreddit}}{% if title %} :: {{title}}{% endif %}{% if extract %} :: {{extract}}{% endif %} :: {{score}} points ({{percent}}) :: {{comments}} comments :: Posted {{age}} by {{author}}", _("""Template used for Reddit text thread title responses"""))) +conf.registerChannelValue( + SpiffyTitles.reddit, + "commentTemplate", + registry.String( + "/r/{{subreddit}}{% if extract %} :: {{extract}}{% endif %} :: " + '{{score}} points :: Posted {{age}} by {{author}} on "{{title}}"', + _("""Template used for Reddit comment title responses"""), + ), +) -conf.registerChannelValue(SpiffyTitles.reddit, 'commentTemplate', - registry.String(u"/r/{{subreddit}}{% if extract %} :: {{extract}}{% endif %} :: {{score}} points :: Posted {{age}} by {{author}} on \"{{title}}\"", _("""Template used for Reddit comment title responses"""))) +conf.registerChannelValue( + SpiffyTitles.reddit, + "userTemplate", + registry.String( + "/u/{{user}}{% if gold %} :: (GOLD{% if mod %}, MOD{% endif %}){% endif %} " + ":: Joined: {{created}} :: Link karma: {{link_karma}} :: Comment karma: " + "{{comment_karma}}", + _("""Template used for Reddit user page title responses"""), + ), +) -conf.registerChannelValue(SpiffyTitles.reddit, 'userTemplate', - registry.String(u"/u/{{user}}{% if gold %} :: (GOLD{% if mod %}, MOD{% endif %}){% endif %} :: Joined: {{created}} :: Link karma: {{link_karma}} :: Comment karma: {{comment_karma}}", _("""Template used for Reddit user page title responses"""))) - -#Reddit max characters -conf.registerChannelValue(SpiffyTitles.reddit, 'maxChars', - registry.Integer(400, _("""Length of response (title/extract will be cut to fit)."""))) +# Reddit max characters +conf.registerChannelValue( + SpiffyTitles.reddit, + "maxChars", + registry.Integer( + 400, _("""Length of response (title/extract will be cut to fit).""") + ), +) +# YouTube configs +conf.registerGroup(SpiffyTitles, "youtube") -#YouTube configs -conf.registerGroup(SpiffyTitles, 'youtube') - -#youtube enabler -conf.registerChannelValue(SpiffyTitles.youtube, 'enabled', - registry.Boolean(True, _("""Whether to add additional information about YouTube videos"""))) +# youtube enabler +conf.registerChannelValue( + SpiffyTitles.youtube, + "enabled", + registry.Boolean( + True, _("""Whether to add additional information about YouTube videos""") + ), +) # Youtube API -conf.registerGlobalValue(SpiffyTitles.youtube, 'developerKey', - registry.String("", _("""Youtube developer key - required for Youtube handler."""), private=True)) +conf.registerGlobalValue( + SpiffyTitles.youtube, + "developerKey", + registry.String( + "", _("""Youtube developer key - required for Youtube handler."""), private=True + ), +) # YouTube Logo -conf.registerChannelValue(SpiffyTitles.youtube, 'logo', - registry.String("\x030,4 ► \x031,0YouTube", _("""Logo used with {{yt_logo}} in template"""))) +conf.registerChannelValue( + SpiffyTitles.youtube, + "logo", + registry.String( + "\x030,4 ► \x031,0YouTube", _("""Logo used with {{yt_logo}} in template""") + ), +) # YouTube template -conf.registerChannelValue(SpiffyTitles.youtube, 'template', - registry.String("^ {{yt_logo}} :: {{title}} {%if timestamp%} @ {{timestamp}}{% endif %} :: Duration: {{duration}} :: Views: {{view_count}} :: Uploader: {{channel_title}} :: Uploaded: {{published}} :: {{like_count}} likes :: {{dislike_count}} dislikes :: {{favorite_count}} favorites :: {{comment_count}} comments", _("""Template used for YouTube title responses"""))) +conf.registerChannelValue( + SpiffyTitles.youtube, + "template", + registry.String( + "^ {{yt_logo}} :: {{title}} {%if timestamp%} @ {{timestamp}}{% endif %} " + ":: Duration: {{duration}} :: Views: {{view_count}} :: Uploader: " + "{{channel_title}} :: Uploaded: {{published}} :: {{like_count}} likes :: " + "{{dislike_count}} dislikes :: {{favorite_count}} favorites :: " + "{{comment_count}} comments", + _("""Template used for YouTube title responses"""), + ), +) +# imgur configs +conf.registerGroup(SpiffyTitles, "imgur") -#imgur configs -conf.registerGroup(SpiffyTitles, 'imgur') - -#imgur enabler -conf.registerChannelValue(SpiffyTitles.imgur, 'enabled', - registry.Boolean(True, _("""Whether to add additional information about imgur links"""))) +# imgur enabler +conf.registerChannelValue( + SpiffyTitles.imgur, + "enabled", + registry.Boolean( + True, _("""Whether to add additional information about imgur links""") + ), +) # imgur API -conf.registerGlobalValue(SpiffyTitles.imgur, 'clientID', - registry.String("", _("""imgur client ID"""), private=True)) +conf.registerGlobalValue( + SpiffyTitles.imgur, + "clientID", + registry.String("", _("""imgur client ID"""), private=True), +) -conf.registerGlobalValue(SpiffyTitles.imgur, 'clientSecret', - registry.String("", _("""imgur client secret"""), private=True)) +conf.registerGlobalValue( + SpiffyTitles.imgur, + "clientSecret", + registry.String("", _("""imgur client secret"""), private=True), +) -conf.registerChannelValue(SpiffyTitles.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.registerChannelValue( + SpiffyTitles.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.registerChannelValue(SpiffyTitles.imgur, 'albumTemplate', - 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"""))) +conf.registerChannelValue( + SpiffyTitles.imgur, + "albumTemplate", + 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"""), + ), +) +# dailymotion configs +conf.registerGroup(SpiffyTitles, "dailymotion") -#dailymotion configs -conf.registerGroup(SpiffyTitles, 'dailymotion') - -#dailymotion enabler -conf.registerChannelValue(SpiffyTitles.dailymotion, 'enabled', - registry.Boolean(True, _("""Enable additional information about dailymotion videos"""))) +# dailymotion enabler +conf.registerChannelValue( + SpiffyTitles.dailymotion, + "enabled", + registry.Boolean( + True, _("""Enable additional information about dailymotion videos""") + ), +) # dailymotion template -conf.registerChannelValue(SpiffyTitles.dailymotion, 'template', - registry.String("^ [{{ownerscreenname}}] {{title}} :: Duration: {{duration}} :: {{views_total}} views", _("""Template used for Vimeo title responses"""))) +conf.registerChannelValue( + SpiffyTitles.dailymotion, + "template", + registry.String( + "^ [{{ownerscreenname}}] {{title}} :: Duration: {{duration}} :: " + "{{views_total}} views", + _("""Template used for Vimeo title responses"""), + ), +) +# Twitch configs +conf.registerGroup(SpiffyTitles, "twitch") -#Twitch configs -conf.registerGroup(SpiffyTitles, 'twitch') +# twitch enabler +conf.registerChannelValue( + SpiffyTitles.twitch, + "enabled", + registry.Boolean( + True, _("""Whether to add additional information about Twitch links""") + ), +) -#twitch enabler -conf.registerChannelValue(SpiffyTitles.twitch, 'enabled', - registry.Boolean(True, _("""Whether to add additional information about Twitch links"""))) - -#Twitch API Key -conf.registerGlobalValue(SpiffyTitles.twitch, 'clientID', - registry.String('', _("""Twitch API Client_ID"""))) +# Twitch API Key +conf.registerGlobalValue( + SpiffyTitles.twitch, "clientID", registry.String("", _("""Twitch API Client_ID""")) +) # Twitch Logo -conf.registerChannelValue(SpiffyTitles.twitch, 'logo', - registry.String("\x030,6💬twitch", _("""Logo used with {{twitch_logo}} in template"""))) +conf.registerChannelValue( + SpiffyTitles.twitch, + "logo", + registry.String( + "\x030,6💬twitch", _("""Logo used with {{twitch_logo}} in template""") + ), +) -conf.registerChannelValue(SpiffyTitles.twitch, 'channelTemplate', - registry.String("^ {{twitch_logo}} :: {{display_name}} {%if description%}:: {{description}} {%endif%}:: Viewers: {{view_count}}", _("""twitch.tv channel template"""))) +conf.registerChannelValue( + SpiffyTitles.twitch, + "channelTemplate", + registry.String( + "^ {{twitch_logo}} :: {{display_name}} {%if description%}:: " + "{{description}} {%endif%}:: Viewers: {{view_count}}", + _("""twitch.tv channel template"""), + ), +) -conf.registerChannelValue(SpiffyTitles.twitch, 'streamTemplate', - registry.String("^ {{twitch_logo}} :: {{display_name}} :: (LIVE) {%if game_name%}[{{game_name}}] {%endif%}{%if title%}:: {{title}} {%endif%}:: Created: {{created_at}} :: Viewers: {{view_count}} {%if description%}:: {{description}}{%endif%}", _("""twitch.tv stream template"""))) +conf.registerChannelValue( + SpiffyTitles.twitch, + "streamTemplate", + registry.String( + "^ {{twitch_logo}} :: {{display_name}} :: (LIVE) " + "{%if game_name%}[{{game_name}}] {%endif%}{%if title%}:: {{title}} " + "{%endif%}:: Created: {{created_at}} :: Viewers: {{view_count}} " + "{%if description%}:: {{description}}{%endif%}", + _("""twitch.tv stream template"""), + ), +) -conf.registerChannelValue(SpiffyTitles.twitch, 'videoTemplate', - registry.String("^ {{twitch_logo}} :: {{display_name}} {%if title%}:: {{title}} {%endif%}}:: Duration: {{duration}} :: Created: {{created_at}} {%if description%}:: {{description}} {%endif%}:: Viewers: {{view_count}}", _("""twitch.tv video template"""))) +conf.registerChannelValue( + SpiffyTitles.twitch, + "videoTemplate", + registry.String( + "^ {{twitch_logo}} :: {{display_name}} {%if title%}:: {{title}} {%endif%}}:: " + "Duration: {{duration}} :: Created: {{created_at}} {%if description%}:: " + "{{description}} {%endif%}:: Viewers: {{view_count}}", + _("""twitch.tv video template"""), + ), +) -conf.registerChannelValue(SpiffyTitles.twitch, 'clipTemplate', - registry.String("^ {{twitch_logo}} :: {{display_name}} {%if game_name%}:: [{{game_name}}] {%endif%}{%if title%}:: {{title}} {%endif%}:: Created: {{created_at}} :: Viewers: {{view_count}} {%if description%}:: {{description}}{%endif%}", _("""twitch.tv clip template"""))) +conf.registerChannelValue( + SpiffyTitles.twitch, + "clipTemplate", + registry.String( + "^ {{twitch_logo}} :: {{display_name}} {%if game_name%}:: [{{game_name}}] " + "{%endif%}{%if title%}:: {{title}} {%endif%}:: Created: {{created_at}} :: " + "Viewers: {{view_count}} {%if description%}:: {{description}}{%endif%}", + _("""twitch.tv clip template"""), + ), +) +# vimeo configs +conf.registerGroup(SpiffyTitles, "vimeo") -#vimeo configs -conf.registerGroup(SpiffyTitles, 'vimeo') - -#vimeo enabler -conf.registerChannelValue(SpiffyTitles.vimeo, 'enabled', - registry.Boolean(True, _("""Enable additional information about Vimeo videos"""))) +# vimeo enabler +conf.registerChannelValue( + SpiffyTitles.vimeo, + "enabled", + registry.Boolean(True, _("""Enable additional information about Vimeo videos""")), +) # Vimeo template -conf.registerChannelValue(SpiffyTitles.vimeo, 'template', - registry.String("^ {{title}} :: Duration: {{duration}} :: {{stats_number_of_plays}} plays :: {{stats_number_of_comments}} comments", _("""Template used for Vimeo title responses"""))) +conf.registerChannelValue( + SpiffyTitles.vimeo, + "template", + registry.String( + "^ {{title}} :: Duration: {{duration}} :: {{stats_number_of_plays}} plays " + ":: {{stats_number_of_comments}} comments", + _("""Template used for Vimeo title responses"""), + ), +) +# IMDB configs +conf.registerGroup(SpiffyTitles, "imdb") -#IMDB configs -conf.registerGroup(SpiffyTitles, 'imdb') +# imdb enabler +conf.registerChannelValue( + SpiffyTitles.imdb, + "enabled", + registry.Boolean( + True, _("""Whether to add additional information about IMDB links""") + ), +) -#imdb enabler -conf.registerChannelValue(SpiffyTitles.imdb, 'enabled', - registry.Boolean(True, _("""Whether to add additional information about IMDB links"""))) - -#OMDB API Key -conf.registerGlobalValue(SpiffyTitles.imdb, 'omdbAPI', - registry.String('', _("""OMDB API Key"""))) +# OMDB API Key +conf.registerGlobalValue( + SpiffyTitles.imdb, "omdbAPI", registry.String("", _("""OMDB API Key""")) +) # IMDB Logo -conf.registerChannelValue(SpiffyTitles.imdb, 'logo', - registry.String("\x031,8 IMDb ", _("""Logo used with {{imdb_logo}} in template"""))) +conf.registerChannelValue( + SpiffyTitles.imdb, + "logo", + registry.String("\x031,8 IMDb ", _("""Logo used with {{imdb_logo}} in template""")), +) # IMDB template -conf.registerChannelValue(SpiffyTitles.imdb, 'template', - registry.String("^ {{imdb_logo}} :: {{title}} ({{year}}, {{country}}, [{{rated}}], {{genre}}, {{runtime}}) :: IMDb: {{imdb_rating}} | MC: {{metascore}} | RT: {{tomatoMeter}} :: {{plot}} :: Director: {{director}} :: Cast: {{actors}} :: Writer: {{writer}}", _("""IMDB title template"""))) +conf.registerChannelValue( + SpiffyTitles.imdb, + "template", + registry.String( + "^ {{imdb_logo}} :: {{title}} ({{year}}, {{country}}, [{{rated}}], {{genre}}, " + "{{runtime}}) :: IMDb: {{imdb_rating}} | MC: {{metascore}} | RT: " + "{{tomatoMeter}} :: {{plot}} :: Director: {{director}} :: Cast: {{actors}} " + ":: Writer: {{writer}}", + _("""IMDB title template"""), + ), +) +# coub configs +conf.registerGroup(SpiffyTitles, "coub") -#coub configs -conf.registerGroup(SpiffyTitles, 'coub') - -#coub enabler -conf.registerChannelValue(SpiffyTitles.coub, 'enabled', - registry.Boolean(True, _("""Whether to add additional information about coub links"""))) +# coub enabler +conf.registerChannelValue( + SpiffyTitles.coub, + "enabled", + registry.Boolean( + True, _("""Whether to add additional information about coub links""") + ), +) # coub template -conf.registerChannelValue(SpiffyTitles.coub, 'template', - registry.String("^ {%if not_safe_for_work %}NSFW{% endif %} [{{channel.title}}] {{title}} :: {{views_count}} views :: {{likes_count}} likes :: {{recoubs_count}} recoubs", _("""Uses Coub API to get additional information about coub.com links"""))) +conf.registerChannelValue( + SpiffyTitles.coub, + "template", + registry.String( + "^ {%if not_safe_for_work %}NSFW{% endif %} [{{channel.title}}] {{title}} :: " + "{{views_count}} views :: {{likes_count}} likes :: {{recoubs_count}} recoubs", + _("""Uses Coub API to get additional information about coub.com links"""), + ), +) diff --git a/SpiffyTitles/plugin.py b/SpiffyTitles/plugin.py index 1138f23..782378b 100644 --- a/SpiffyTitles/plugin.py +++ b/SpiffyTitles/plugin.py @@ -27,30 +27,28 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. ### - from supybot.commands import * import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.callbacks as callbacks import supybot.utils as utils +import supybot.ircdb as ircdb +import supybot.log as log import re -import requests -import urllib import sys -from urllib.parse import urlencode, urlparse, parse_qsl -from bs4 import BeautifulSoup import random import time import json -from jinja2 import Template -import timeout_decorator import unicodedata -import supybot.ircdb as ircdb -import supybot.log as log +from urllib.parse import urlparse, parse_qsl +from bs4 import BeautifulSoup +from jinja2 import Template +import requests import pendulum try: from supybot.i18n import PluginInternationalization + _ = PluginInternationalization("SpiffyTitles") except ImportError: # Placeholder that allows to run the plugin on a bot @@ -60,6 +58,7 @@ except ImportError: class SpiffyTitles(callbacks.Plugin): """Displays link titles when posted in a channel""" + threaded = True callBefore = ["Web"] link_cache = {} @@ -71,10 +70,8 @@ class SpiffyTitles(callbacks.Plugin): def __init__(self, irc): self.__parent = super(SpiffyTitles, self) self.__parent.__init__(irc) - self.wall_clock_timeout = self.registryValue("wallClockTimeoutInSeconds") self.default_handler_enabled = self.registryValue("default.enabled") - self.add_handlers() def add_handlers(self): @@ -142,50 +139,52 @@ class SpiffyTitles(callbacks.Plugin): """ Handles dailymotion links """ - dailymotion_handler_enabled = self.registryValue("dailymotion.enabled", - channel=channel) + dailymotion_handler_enabled = self.registryValue( + "dailymotion.enabled", channel=channel + ) log.debug("SpiffyTitles: calling dailymotion handler for %s" % url) title = None video_id = None - """ Get video ID """ if dailymotion_handler_enabled and "/video/" in info.path: video_id = info.path.lstrip("/video/").split("_")[0] elif dailymotion_handler_enabled and "dai.ly" in url: video_id = url.split("/")[-1].split("?")[0] - if video_id is not None: fields = "id,title,owner.screenname,duration,views_total" - api_url = "https://api.dailymotion.com/video/%s?fields=%s" % (video_id, fields) + api_url = "https://api.dailymotion.com/video/%s?fields=%s" % ( + video_id, + fields, + ) log.debug("SpiffyTitles: looking up dailymotion info: %s", api_url) headers = self.get_headers() request = requests.get(api_url, headers=headers, timeout=10) - ok = request.status_code == requests.codes.ok - if ok: - response = json.loads(request.content) - + response = json.loads(request.content.decode()) if response is not None and "title" in response: video = response - dailymotion_template = \ - Template(self.registryValue("dailymotion.template", - channel=channel)) + dailymotion_template = Template( + self.registryValue("dailymotion.template", channel=channel) + ) video["views_total"] = "{:,}".format(int(video["views_total"])) - video["duration"] = self.get_duration_from_seconds(video["duration"]) + video["duration"] = self.get_duration_from_seconds( + video["duration"] + ) video["ownerscreenname"] = video["owner.screenname"] - title = dailymotion_template.render(video) else: - log.debug("SpiffyTitles: received unexpected payload from video: %s" % - api_url) + log.debug( + "SpiffyTitles: received unexpected payload from video: %s" + % api_url + ) else: - log.error("SpiffyTitles: dailymotion handler returned %s: %s" % - (request.status_code, request.content.decode()[:200])) - + log.error( + "SpiffyTitles: dailymotion handler returned %s: %s" + % (request.status_code, request.content.decode()[:200]) + ) if title is None: log.debug("SpiffyTitles: could not get dailymotion info for %s" % url) - return self.handler_default(url, channel) else: return title @@ -198,30 +197,24 @@ class SpiffyTitles(callbacks.Plugin): log.debug("SpiffyTitles: calling vimeo handler for %s" % url) title = None video_id = None - """ Get video ID """ if vimeo_handler_enabled: - result = re.search(r'^(http(s)://)?(www\.)?(vimeo\.com/)?(\d+)', url) - + result = re.search(r"^(http(s)://)?(www\.)?(vimeo\.com/)?(\d+)", url) if result is not None: video_id = result.group(5) - if video_id is not None: api_url = "https://vimeo.com/api/v2/video/%s.json" % video_id log.debug("SpiffyTitles: looking up vimeo info: %s", api_url) headers = self.get_headers() request = requests.get(api_url, headers=headers, timeout=10) - ok = request.status_code == requests.codes.ok - if ok: - response = json.loads(request.content) - + response = json.loads(request.content.decode()) if response is not None and "title" in response[0]: video = response[0] - vimeo_template = Template(self.registryValue("vimeo.template", - channel=channel)) - + vimeo_template = Template( + self.registryValue("vimeo.template", channel=channel) + ) """ Some videos do not have this information available """ @@ -230,26 +223,29 @@ class SpiffyTitles(callbacks.Plugin): video["stats_number_of_plays"] = "{:,}".format(int_plays) else: video["stats_number_of_plays"] = 0 - if "stats_number_of_comments" in video: int_comments = int(video["stats_number_of_comments"]) - video["stats_number_of_comments"] = "{:,}".format(int_comments) + video["stats_number_of_comments"] = "{:,}".format( + int_comments + ) else: video["stats_number_of_comments"] = 0 - - video["duration"] = self.get_duration_from_seconds(video["duration"]) - + video["duration"] = self.get_duration_from_seconds( + video["duration"] + ) title = vimeo_template.render(video) else: - log.debug("SpiffyTitles: received unexpected payload from video: %s" % - api_url) + log.debug( + "SpiffyTitles: received unexpected payload from video: %s" + % api_url + ) else: - log.error("SpiffyTitles: vimeo handler returned %s: %s" % - (request.status_code, request.content.decode()[:200])) - + log.error( + "SpiffyTitles: vimeo handler returned %s: %s" + % (request.status_code, request.content.decode()[:200]) + ) if title is None: log.debug("SpiffyTitles: could not get vimeo info for %s" % url) - return self.handler_default(url, channel) else: return title @@ -261,41 +257,33 @@ class SpiffyTitles(callbacks.Plugin): coub_handler_enabled = self.registryValue("coub.enabled", channel=channel) log.debug("SpiffyTitles: calling coub handler for %s" % url) title = None - """ Get video ID """ if coub_handler_enabled and "/view/" in url: video_id = url.split("/view/")[1] - """ Remove any query strings """ if "?" in video_id: video_id = video_id.split("?")[0] - api_url = "http://coub.com/api/v2/coubs/%s" % video_id headers = self.get_headers() request = requests.get(api_url, headers=headers, timeout=10) - ok = request.status_code == requests.codes.ok - if ok: - response = json.loads(request.content) - + response = json.loads(request.content.decode()) if response: video = response coub_template = Template(self.registryValue("coub.template")) - video["likes_count"] = "{:,}".format(int(video["likes_count"])) video["recoubs_count"] = "{:,}".format(int(video["recoubs_count"])) video["views_count"] = "{:,}".format(int(video["views_count"])) - title = coub_template.render(video) else: - log.error("SpiffyTitles: coub handler returned %s: %s" % - (request.status_code, request.content.decode()[:200])) - + log.error( + "SpiffyTitles: coub handler returned %s: %s" + % (request.status_code, request.content.decode()[:200]) + ) if title is None: if coub_handler_enabled: log.debug("SpiffyTitles: %s does not appear to be a video link!" % url) - return self.handler_default(url, channel) else: return title @@ -309,23 +297,27 @@ class SpiffyTitles(callbacks.Plugin): imgur_client_id = self.registryValue("imgur.clientID") imgur_client_secret = self.registryValue("imgur.clientSecret") imgur_handler_enabled = self.registryValue("imgur.enabled", channel=channel) - if imgur_handler_enabled and imgur_client_id and imgur_client_secret: log.debug("SpiffyTitles: enabling imgur handler") - # Initialize API client try: from imgurpython import ImgurClient from imgurpython.helpers.error import ImgurClientError try: - self.imgur_client = ImgurClient(imgur_client_id, imgur_client_secret) + self.imgur_client = ImgurClient( + imgur_client_id, imgur_client_secret + ) except ImgurClientError as e: - log.error("SpiffyTitles: imgur client error: %s" % (e.error_message)) + log.error( + "SpiffyTitles: imgur client error: %s" % (e.error_message) + ) except ImportError as e: log.error("SpiffyTitles ImportError: %s" % str(e)) else: - log.debug("SpiffyTitles: imgur handler disabled or empty client id/secret") + log.debug( + "SpiffyTitles: imgur handler disabled or empty client id/secret" + ) def doPrivmsg(self, irc, msg): """ @@ -340,66 +332,64 @@ class SpiffyTitles(callbacks.Plugin): bot_nick = irc.nick origin_nick = msg.nick is_message_from_self = origin_nick.lower() == bot_nick.lower() - requires_capability = len(str(self.registryValue("requireCapability", - channel=msg.args[0]))) > 0 - + requires_capability = ( + len(str(self.registryValue("requireCapability", channel=msg.args[0]))) > 0 + ) if is_message_from_self: return - """ Check if we require a capability to acknowledge this link """ if requires_capability: user_has_capability = self.user_has_capability(msg) - if not user_has_capability: return - """ Configuration option determines whether we should ignore links that appear within an action """ if is_ctcp and ignore_actions: return - url = self.get_url_from_message(message) - if is_channel: channel_is_allowed = self.is_channel_allowed(channel) - #url = self.get_url_from_message(message) + # url = self.get_url_from_message(message) ignore_match = self.message_matches_ignore_pattern(message) - if ignore_match: - log.debug("SpiffyTitles: ignoring message due to linkMessagePattern match") + log.debug( + "SpiffyTitles: ignoring message due to linkMessagePattern match" + ) return - if url: # Check if channel is allowed based on white/black list restrictions if not channel_is_allowed: - log.debug("SpiffyTitles: not responding to link in %s due to black/white list \ - restrictions" % (channel)) + log.debug( + "SpiffyTitles: not responding to link in %s due to black/white " + "list restrictions" % (channel) + ) return - info = urlparse(url) domain = info.netloc is_ignored = self.is_ignored_domain(domain, channel) - if is_ignored: - log.debug("SpiffyTitles: URL ignored due to domain blacklist match: %s" % url) + log.debug( + "SpiffyTitles: URL ignored due to domain blacklist match: %s" + % url + ) return - is_whitelisted_domain = self.is_whitelisted_domain(domain, channel) - whitelist_pattern = self.registryValue("whitelistDomainPattern", channel=channel) + whitelist_pattern = self.registryValue( + "whitelistDomainPattern", channel=channel + ) if whitelist_pattern and not is_whitelisted_domain: - log.debug("SpiffyTitles: URL ignored due to domain whitelist mismatch: %s" % - url) + log.debug( + "SpiffyTitles: URL ignored due to domain whitelist mismatch: %s" + % url + ) return - title = self.get_title_by_url(url, channel, origin_nick) - if title is not None and title: ignore_match = self.title_matches_ignore_pattern(title, channel) - if ignore_match: return else: @@ -409,8 +399,11 @@ class SpiffyTitles(callbacks.Plugin): if self.default_handler_enabled: log.debug("SpiffyTitles: could not get a title for %s" % (url)) else: - log.debug("SpiffyTitles: could not get a title for %s but default \ - handler is disabled" % (url)) + log.debug( + "SpiffyTitles: could not get a title for %s but default \ + handler is disabled" + % (url) + ) def get_title_by_url(self, url, channel, origin_nick=None): """ @@ -419,13 +412,11 @@ class SpiffyTitles(callbacks.Plugin): info = urlparse(url) domain = info.netloc title = None - """ Check if we have this link cached according to the cache lifetime. If so, serve link from the cache instead of calling handlers. """ cached_link = self.get_link_from_cache(url, channel) - if cached_link: title = cached_link["title"] else: @@ -433,33 +424,32 @@ class SpiffyTitles(callbacks.Plugin): handler = self.handlers[domain] title = handler(url, info, channel) else: - base_domain = self.get_base_domain('http://' + domain) + base_domain = self.get_base_domain("http://" + domain) if base_domain in self.handlers: handler = self.handlers[base_domain] title = handler(url, info, channel) else: if self.default_handler_enabled: title = self.handler_default(url, channel) - if title and not cached_link: title = self.get_formatted_title(title, channel) - # Update link cache log.debug("SpiffyTitles: caching %s" % (url)) now = pendulum.now() if channel not in self.link_cache: self.link_cache[channel] = [] - self.link_cache[channel].append({ - "url": url, - "timestamp": now, - "title": title, - "from": origin_nick, - "channel": channel - }) + self.link_cache[channel].append( + { + "url": url, + "timestamp": now, + "title": title, + "from": origin_nick, + "channel": channel, + } + ) elif title and cached_link: self.link_cache[channel].append(cached_link) log.debug("SpiffyTitles: serving link from cache: %s" % (url)) - return title def t(self, irc, msg, args, query): @@ -471,55 +461,48 @@ class SpiffyTitles(callbacks.Plugin): url = self.get_url_from_message(message) title = None error_message = self.registryValue("onDemandTitleError", channel=channel) - err = '' - + err = "" try: if url: title = self.get_title_by_url(query, channel) - except Exception as e: + except: pass - if title: irc.reply(title) else: irc.reply(error_message + " {}".format(err)) - t = wrap(t, ['text']) + t = wrap(t, ["text"]) def get_link_from_cache(self, url, channel): """ Looks for a URL in the link cache and returns info about if it's not stale according to the configured cache lifetime, or None. - If linkCacheLifetimeInSeconds is 0, then cache is disabled and we can immediately return """ - cache_lifetime_in_seconds = int(self.registryValue("linkCacheLifetimeInSeconds")) - + cache_lifetime_in_seconds = int( + self.registryValue("linkCacheLifetimeInSeconds") + ) if cache_lifetime_in_seconds == 0: return - # No cache yet if len(self.link_cache) == 0: return - cached_link = None now = pendulum.now() stale = False seconds = 0 - if channel in self.link_cache: for i in range(len(self.link_cache[channel])): if self.link_cache[channel][i]["url"] == url: cached_link = self.link_cache[channel].pop(i) break - # Found link, check timestamp if cached_link: seconds = (now - cached_link["timestamp"]).total_seconds() if seconds >= cache_lifetime_in_seconds: stale = True - if stale: log.debug("SpiffyTitles: %s was sent %s seconds ago" % (url, seconds)) else: @@ -536,19 +519,15 @@ class SpiffyTitles(callbacks.Plugin): black_list = self.filter_empty(self.registryValue("channelBlacklist")) white_list_empty = len(white_list) == 0 black_list_empty = len(black_list) == 0 - - # Most basic case, which is that both white and blacklist are empty. Any channel is allowed. + # Most basic case: both white and blacklist are empty. Any channel is allowed. if white_list_empty and black_list_empty: is_allowed = True - # If there is a white list, blacklist is ignored. if white_list: is_allowed = channel in white_list - # Finally, check blacklist if not white_list and black_list: is_allowed = channel not in black_list - return is_allowed def filter_empty(self, input): @@ -562,18 +541,14 @@ class SpiffyTitles(callbacks.Plugin): Checks domain against a regular expression """ pattern = self.registryValue("ignoredDomainPattern", channel=channel) - if pattern: log.debug("SpiffyTitles: matching %s against %s" % (domain, str(pattern))) - try: pattern_search_result = re.search(pattern, domain) - if pattern_search_result is not None: match = pattern_search_result.group() - return match - except re.Error: + except re.error: log.error("SpiffyTitles: invalid regular expression: %s" % (pattern)) def is_whitelisted_domain(self, domain, channel): @@ -581,18 +556,14 @@ class SpiffyTitles(callbacks.Plugin): Checks domain against a regular expression """ pattern = self.registryValue("whitelistDomainPattern", channel=channel) - if pattern: log.debug("SpiffyTitles: matching %s against %s" % (domain, str(pattern))) - try: pattern_search_result = re.search(pattern, domain) - if pattern_search_result is not None: match = pattern_search_result.group() - return match - except re.Error: + except re.error: log.error("SpiffyTitles: invalid regular expression: %s" % (pattern)) def get_video_id_from_url(self, url, info): @@ -603,64 +574,55 @@ class SpiffyTitles(callbacks.Plugin): 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)) - except IndexError as e: - log.error("SpiffyTitles: error getting video id from %s (%s)" % (url, str(e))) + log.error( + "SpiffyTitles: error getting video id from %s (%s)" % (url, str(e)) + ) def handler_youtube(self, url, domain, channel): """ Uses the Youtube API to provide additional meta data about Youtube Video links posted. """ - youtube_handler_enabled = self.registryValue("youtube.enabled", channel=channel) + youtube_handler_enabled = self.registryValue("youtube.enabled", channel) developer_key = self.registryValue("youtube.developerKey") - if not youtube_handler_enabled: return None - if not developer_key: - log.info("SpiffyTitles: no Youtube developer key set! Check the documentation \ - for instructions.") + log.info( + "SpiffyTitles: no Youtube developer key set! Check the documentation " + "for instructions." + ) return None - log.debug("SpiffyTitles: calling Youtube handler for %s" % (url)) video_id = self.get_video_id_from_url(url, domain) - yt_template = Template(self.registryValue("youtube.template", channel=channel)) + yt_template = Template(self.registryValue("youtube.template", channel)) title = "" - if video_id: options = { "part": "snippet,statistics,contentDetails", "maxResults": 1, "key": developer_key, - "id": video_id + "id": video_id, } - encoded_options = urlencode(options) - api_url = "https://www.googleapis.com/youtube/v3/videos?%s" % (encoded_options) + api_url = "https://www.googleapis.com/youtube/v3/videos" headers = self.get_headers() - log.debug("SpiffyTitles: requesting %s" % (api_url)) - - request = requests.get(api_url, headers=headers, timeout=10) + request = requests.get(api_url, headers=headers, params=options, timeout=10) ok = request.status_code == requests.codes.ok - if ok: - response = json.loads(request.content) - + response = json.loads(request.content.decode()) if response: try: if response["pageInfo"]["totalResults"] > 0: @@ -674,73 +636,77 @@ class SpiffyTitles(callbacks.Plugin): dislike_count = 0 comment_count = 0 favorite_count = 0 - if "viewCount" in statistics: view_count = "{:,}".format(int(statistics["viewCount"])) - if "likeCount" in statistics: like_count = "{:,}".format(int(statistics["likeCount"])) - if "dislikeCount" in statistics: - dislike_count = "{:,}".format(int(statistics["dislikeCount"])) - + dislike_count = "{:,}".format( + int(statistics["dislikeCount"]) + ) if "favoriteCount" in statistics: - favorite_count = "{:,}".format(int(statistics["favoriteCount"])) - + favorite_count = "{:,}".format( + int(statistics["favoriteCount"]) + ) if "commentCount" in statistics: - comment_count = "{:,}".format(int(statistics["commentCount"])) - + comment_count = "{:,}".format( + int(statistics["commentCount"]) + ) channel_title = snippet["channelTitle"] 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: - duration = self.get_duration_from_seconds(duration_seconds) + duration = self.get_duration_from_seconds( + duration_seconds + ) else: duration = "LIVE" - - published = snippet['publishedAt'] + published = snippet["publishedAt"] published = self.get_published_date(published) - timestamp = self.get_timestamp_from_youtube_url(url) - yt_logo = self.get_youtube_logo() - - compiled_template = yt_template.render({ - "title": title, - "duration": duration, - "timestamp": timestamp, - "view_count": view_count, - "like_count": like_count, - "dislike_count": dislike_count, - "comment_count": comment_count, - "favorite_count": favorite_count, - "channel_title": channel_title, - "published": published, - "yt_logo": yt_logo - }) - + yt_logo = self.get_youtube_logo(channel) + compiled_template = yt_template.render( + { + "title": title, + "duration": duration, + "timestamp": timestamp, + "view_count": view_count, + "like_count": like_count, + "dislike_count": dislike_count, + "comment_count": comment_count, + "favorite_count": favorite_count, + "channel_title": channel_title, + "published": published, + "yt_logo": yt_logo, + } + ) title = compiled_template else: - log.debug("SpiffyTitles: video appears to be private; no results!") - + log.debug( + "SpiffyTitles: video appears to be private; no results!" + ) except IndexError as e: - log.error("SpiffyTitles: IndexError parsing Youtube API JSON response: %s" % - (str(e))) + log.error( + "SpiffyTitles: IndexError. Youtube API JSON response: %s" + % (str(e)) + ) else: log.error("SpiffyTitles: Error parsing Youtube API JSON response") else: - log.error("SpiffyTitles: Youtube API HTTP %s: %s" % - (request.status_code, request.content.decode()[:200])) - + log.error( + "SpiffyTitles: Youtube API HTTP %s: %s" + % (request.status_code, request.content.decode()[:200]) + ) # If we found a title, return that. otherwise, use default handler if title: return title else: log.debug("SpiffyTitles: falling back to default handler") - return self.handler_default(url, channel) def get_published_date(self, date): @@ -752,40 +718,36 @@ class SpiffyTitles(callbacks.Plugin): def get_duration_from_seconds(self, duration_seconds): m, s = divmod(duration_seconds, 60) h, m = divmod(m, 60) - duration = "%02d:%02d" % (m, s) - """ Only include hour if the video is at least 1 hour long """ if h > 0: duration = "%02d:%s" % (h, duration) - return duration - def get_youtube_logo(self): - use_bold = self.registryValue("useBold", dynamic.channel) + def get_youtube_logo(self, channel): + use_bold = self.registryValue("useBold", channel) if use_bold: - yt_logo = "{0}\x0F\x02".format(self.registryValue("youtube.logo", dynamic.channel)) + yt_logo = "{0}\x0F\x02".format(self.registryValue("youtube.logo", channel)) else: - yt_logo = "{0}\x0F".format(self.registryValue("youtube.logo", dynamic.channel)) - + yt_logo = "{0}\x0F".format(self.registryValue("youtube.logo", channel)) return yt_logo - def get_twitch_logo(self): - use_bold = self.registryValue("useBold", dynamic.channel) + def get_twitch_logo(self, channel): + use_bold = self.registryValue("useBold", channel) if use_bold: - twitch_logo = "{0}\x0F\x02".format(self.registryValue("twitch.logo", dynamic.channel)) + twitch_logo = "{0}\x0F\x02".format( + self.registryValue("twitch.logo", channel) + ) else: - twitch_logo = "{0}\x0F".format(self.registryValue("twitch.logo", dynamic.channel)) - + twitch_logo = "{0}\x0F".format(self.registryValue("twitch.logo", channel)) return twitch_logo - def get_imdb_logo(self): - use_bold = self.registryValue("useBold", dynamic.channel) + def get_imdb_logo(self, channel): + use_bold = self.registryValue("useBold", channel) if use_bold: - imdb_logo = "{0}\x0F\x02".format(self.registryValue("imdb.logo", dynamic.channel)) + imdb_logo = "{0}\x0F\x02".format(self.registryValue("imdb.logo", channel)) else: - imdb_logo = "{0}\x0F".format(self.registryValue("imdb.logo", dynamic.channel)) - + imdb_logo = "{0}\x0F".format(self.registryValue("imdb.logo", channel)) return imdb_logo def get_total_seconds_from_duration(self, input): @@ -803,14 +765,12 @@ class SpiffyTitles(callbacks.Plugin): """ pattern = r"[?&]t=([^&]+)" match = re.search(pattern, url) - if match: timestamp = match.group(1).upper() try: seconds = float(timestamp) except ValueError: seconds = self.get_total_seconds_from_duration("PT" + timestamp) - if seconds > 0: return self.get_duration_from_seconds(seconds) else: @@ -823,18 +783,21 @@ class SpiffyTitles(callbacks.Plugin): default_handler_enabled = self.registryValue("default.enabled", channel=channel) if default_handler_enabled: log.debug("SpiffyTitles: calling default handler for %s" % (url)) - default_template = Template(self.registryValue("default.template", channel=channel)) - (html, is_redirect) = self.get_source_by_url(url) - + default_template = Template( + self.registryValue("default.template", channel=channel) + ) + (html, is_redirect) = self.get_source_by_url(url, channel) if html is not None and html: title = self.get_title_from_html(html) - if title is not None: - title_template = default_template.render(title=title, redirect=is_redirect) - + title_template = default_template.render( + title=title, redirect=is_redirect + ) return title_template else: - log.debug("SpiffyTitles: default handler fired but doing nothing because disabled") + log.debug( + "SpiffyTitles: default handler fired but doing nothing because disabled" + ) def handler_twitch(self, url, domain, channel): """ @@ -849,95 +812,122 @@ class SpiffyTitles(callbacks.Plugin): self.log.debug("SpiffyTitles: calling twitch handler for %s" % (url)) patterns = { "video": { - "pattern": r"^http(s)?:\/\/(www\.|go\.|player\.)?twitch\.tv\/(videos/|\?video=v)(?P[0-9]+)", - "url": "https://api.twitch.tv/helix/videos?id={video_id}" + "pattern": r"^http(s)?:\/\/(www\.|go\.|player\.)?twitch\.tv\/" + r"(videos/|\?video=v)(?P[0-9]+)", + "url": "https://api.twitch.tv/helix/videos?id={video_id}", }, "clip": { - "pattern": r"(http(s)?:\/\/clips\.twitch\.tv\/|http(s)?:\/\/www\.twitch\.tv\/([^\/]+)\/clip\/)(?P.+)$", - "url": "https://api.twitch.tv/helix/clips?id={clip}" + "pattern": r"(http(s)?:\/\/clips\.twitch\.tv\/|http(s)?:\/\/" + r"www\.twitch\.tv\/([^\/]+)\/clip\/)(?P.+)$", + "url": "https://api.twitch.tv/helix/clips?id={clip}", }, "channel": { - "pattern": r"^http(s)?:\/\/(www\.|go\.)?twitch\.tv\/(?P[^\/]+)", - "url": "https://api.twitch.tv/helix/streams?user_login={channel_name}" - } + "pattern": r"^http(s)?:\/\/(www\.|go\.)?twitch\.tv\/" + r"(?P[^\/]+)", + "url": "https://api.twitch.tv/helix/streams?user_login={channel_name}", + }, } for name in patterns: - match = re.search(patterns[name]['pattern'], url) + match = re.search(patterns[name]["pattern"], url) if match: link_type = name link_info = match.groupdict() - data_url = patterns[name]['url'].format(**link_info) + data_url = patterns[name]["url"].format(**link_info) break if not match: self.log.debug("SpiffyTitles: twitch - no title found.") return self.handler_default(url, channel) headers = self.get_headers() - headers['Client-ID'] = twitch_client_id + headers["Client-ID"] = twitch_client_id self.log.debug("SpiffyTitles: twitch - requesting %s" % (data_url)) request = requests.get(data_url, timeout=10, headers=headers) ok = request.status_code == requests.codes.ok data = {} - extract = "" if ok: - response = json.loads(request.content) + response = json.loads(request.content.decode()) if response: self.log.debug("SpiffyTitles: twitch - got response:\n%s" % (response)) - if 'error' in response: + if "error" in response: return "[ Twitch ] - Error!" try: if link_type == "channel": self.log.debug("SpiffyTitles: Twitch: link_type is channel") - if 'data' in response and response['data']: + if "data" in response and response["data"]: self.log.debug("SpiffyTitles: Twitch: Got data[0]") - data = response['data'][0] - link_type = 'stream' + data = response["data"][0] + link_type = "stream" else: self.log.debug("SpiffyTitles: Twitch: No data[0]") - data_url = 'https://api.twitch.tv/helix/users?login={channel_name}'.format(**link_info) + data_url = ( + "https://api.twitch.tv/helix/users?" + "login={channel_name}".format(**link_info) + ) request = requests.get(data_url, timeout=10, headers=headers) ok = request.status_code == requests.codes.ok user_data = {} if not ok: - self.log.error("SpiffyTitles: twitch HTTP %s: %s" % - (request.status_code, request.content.decode()[:200])) + self.log.error( + "SpiffyTitles: twitch HTTP %s: %s" + % (request.status_code, request.content.decode()[:200]) + ) else: - response = json.loads(request.content) + response = json.loads(request.content.decode()) if not response: - self.log.error("SpiffyTitles: Error parsing Twitch JSON response") + self.log.error( + "SpiffyTitles: Error parsing Twitch JSON response" + ) else: - if 'error' in response: - self.log.error("SpiffyTitles: twitch - error in JSON response") + if "error" in response: + self.log.error( + "SpiffyTitles: twitch - error in JSON response" + ) return self.handler_default(url, channel) try: - user_data = response['data'][0] - display_name = user_data['display_name'] - description = user_data['description'] - view_count = user_data['view_count'] + user_data = response["data"][0] + display_name = user_data["display_name"] + description = user_data["description"] + view_count = user_data["view_count"] except KeyError as e: - self.log.error("SpiffyTitles: KeyError parsing Twitch.TV JSON response: %s" % (str(e))) + self.log.error( + "SpiffyTitles: KeyError parsing Twitch.TV JSON " + "response: %s" % (str(e)) + ) elif link_type == "video": data = response - elif link_type == 'clip': + elif link_type == "clip": data = response except KeyError as e: - self.log.error("SpiffyTitles: KeyError parsing Twitch.TV JSON response: %s" % (str(e))) + self.log.error( + "SpiffyTitles: KeyError parsing Twitch.TV JSON response: %s" + % (str(e)) + ) if data or user_data: self.log.debug("SpiffyTitles: twitch - Got data '%s'" % (data)) - twitch_template = self.get_template(''.join(["twitch.", link_type, "Template"]), channel) - twitch_logo = self.get_twitch_logo() + twitch_template = self.get_template( + "".join(["twitch.", link_type, "Template"]), channel + ) + twitch_logo = self.get_twitch_logo(channel) if not twitch_template: - self.log.debug("SpiffyTitles - twitch: bad template for %s" % (link_type)) + self.log.debug( + "SpiffyTitles - twitch: bad template for %s" % (link_type) + ) reply = "[ Twitch.TV ] - Got data, but template was bad" - elif link_type == 'stream': - display_name = data['user_name'] - game_id = data['game_id'] + elif link_type == "stream": + display_name = data["user_name"] + game_id = data["game_id"] game_name = game_id - title = data['title'] - view_count = data['viewer_count'] - created_at = self._time_created_at(data['started_at']) + title = data["title"] + view_count = data["viewer_count"] + created_at = self._time_created_at(data["started_at"]) if game_id: - get_game = requests.get("https://api.twitch.tv/helix/games?id={}".format(game_id), timeout=10, headers=headers) - game_data = json.loads(get_game.content) + get_game = requests.get( + "https://api.twitch.tv/helix/games?id={}".format( + game_id + ), + timeout=10, + headers=headers, + ) + game_data = json.loads(get_game.content.decode()) game_name = game_data["data"][0]["name"] template_vars = { "display_name": display_name, @@ -946,40 +936,57 @@ class SpiffyTitles(callbacks.Plugin): "view_count": view_count, "description": description, "created_at": created_at, - "twitch_logo": twitch_logo + "twitch_logo": twitch_logo, } reply = twitch_template.render(template_vars) - elif link_type == 'clip': - data = response['data'][0] - display_name = data['broadcaster_name'] - data_url = 'https://api.twitch.tv/helix/users?login={}'.format(display_name) + elif link_type == "clip": + data = response["data"][0] + display_name = data["broadcaster_name"] + data_url = "https://api.twitch.tv/helix/users?login={}".format( + display_name + ) request = requests.get(data_url, timeout=10, headers=headers) ok = request.status_code == requests.codes.ok user_data = {} if not ok: - self.log.error("SpiffyTitles: twitch HTTP %s: %s" % - (request.status_code, request.content.decode()[:200])) + self.log.error( + "SpiffyTitles: twitch HTTP %s: %s" + % (request.status_code, request.content.decode()[:200]) + ) else: - response = json.loads(request.content) + response = json.loads(request.content.decode()) if not response: - self.log.error("SpiffyTitles: Error parsing Twitch JSON response") + self.log.error( + "SpiffyTitles: Error parsing Twitch JSON response" + ) else: - if 'error' in response: - self.log.error("SpiffyTitles: twitch - error in JSON response") + if "error" in response: + self.log.error( + "SpiffyTitles: twitch - error in JSON response" + ) return self.handler_default(url, channel) try: - user_data = response['data'][0] - description = user_data['description'] + user_data = response["data"][0] + description = user_data["description"] except KeyError as e: - self.log.error("SpiffyTitles: KeyError parsing Twitch.TV JSON response: %s" % (str(e))) - game_id = data['game_id'] + self.log.error( + "SpiffyTitles: KeyError parsing Twitch.TV JSON " + "response: %s" % (str(e)) + ) + game_id = data["game_id"] game_name = game_id - title = data['title'] - view_count = data['view_count'] - created_at = self._time_created_at(data['created_at']) + title = data["title"] + view_count = data["view_count"] + created_at = self._time_created_at(data["created_at"]) if game_id: - get_game = requests.get("https://api.twitch.tv/helix/games?id={}".format(game_id), timeout=10, headers=headers) - game_data = json.loads(get_game.content) + get_game = requests.get( + "https://api.twitch.tv/helix/games?id={}".format( + game_id + ), + timeout=10, + headers=headers, + ) + game_data = json.loads(get_game.content.decode()) game_name = game_data["data"][0]["name"] template_vars = { "display_name": display_name, @@ -988,36 +995,47 @@ class SpiffyTitles(callbacks.Plugin): "view_count": view_count, "description": description, "created_at": created_at, - "twitch_logo": twitch_logo + "twitch_logo": twitch_logo, } reply = twitch_template.render(template_vars) - elif link_type == 'video': - data = response['data'][0] - display_name = data['user_name'] - data_url = 'https://api.twitch.tv/helix/users?login={}'.format(display_name) + elif link_type == "video": + data = response["data"][0] + display_name = data["user_name"] + data_url = "https://api.twitch.tv/helix/users?login={}".format( + display_name + ) request = requests.get(data_url, timeout=10, headers=headers) ok = request.status_code == requests.codes.ok user_data = {} if not ok: - self.log.error("SpiffyTitles: twitch HTTP %s: %s" % - (request.status_code, request.content.decode()[:200])) + self.log.error( + "SpiffyTitles: twitch HTTP %s: %s" + % (request.status_code, request.content.decode()[:200]) + ) else: - response = json.loads(request.content) + response = json.loads(request.content.decode()) if not response: - self.log.error("SpiffyTitles: Error parsing Twitch JSON response") + self.log.error( + "SpiffyTitles: Error parsing Twitch JSON response" + ) else: - if 'error' in response: - self.log.error("SpiffyTitles: twitch - error in JSON response") + if "error" in response: + self.log.error( + "SpiffyTitles: twitch - error in JSON response" + ) return self.handler_default(url, channel) try: - user_data = response['data'][0] - description = user_data['description'] + user_data = response["data"][0] + description = user_data["description"] except KeyError as e: - self.log.error("SpiffyTitles: KeyError parsing Twitch.TV JSON response: %s" % (str(e))) - title = data['title'] - view_count = data['view_count'] - created_at = self._time_created_at(data['created_at']) - duration = data['duration'] + self.log.error( + "SpiffyTitles: KeyError parsing Twitch.TV JSON " + "response: %s" % (str(e)) + ) + title = data["title"] + view_count = data["view_count"] + created_at = self._time_created_at(data["created_at"]) + duration = data["duration"] template_vars = { "display_name": display_name, "title": title, @@ -1025,7 +1043,7 @@ class SpiffyTitles(callbacks.Plugin): "created_at": created_at, "description": description, "duration": duration, - "twitch_logo": twitch_logo + "twitch_logo": twitch_logo, } reply = twitch_template.render(template_vars) else: @@ -1033,7 +1051,7 @@ class SpiffyTitles(callbacks.Plugin): "display_name": display_name, "description": description, "view_count": view_count, - "twitch_logo": twitch_logo + "twitch_logo": twitch_logo, } reply = twitch_template.render(template_vars) self.log.debug("SpiffyTitles: twitch - reply = '%s'" % (reply)) @@ -1043,7 +1061,6 @@ class SpiffyTitles(callbacks.Plugin): """ Return relative time delta between now and s (dt string). """ - try: # timeline's created_at Tue May 08 10:58:49 +0000 2012 ddate = pendulum.parse(s) except ValueError: @@ -1054,9 +1071,9 @@ class SpiffyTitles(callbacks.Plugin): if d.days: rel_time = "{:1d}d ago".format(abs(d.days)) elif d.seconds > 3600: - rel_time = "{:.1f}h ago".format(round((abs(d.seconds) / 3600),1)) + rel_time = "{:.1f}h ago".format(round((abs(d.seconds) / 3600), 1)) elif 60 <= d.seconds < 3600: - rel_time = "{:.1f}m ago".format(round((abs(d.seconds) / 60),1)) + rel_time = "{:.1f}m ago".format(round((abs(d.seconds) / 60), 1)) else: rel_time = "%ss ago" % (abs(d.seconds)) return rel_time @@ -1064,115 +1081,110 @@ class SpiffyTitles(callbacks.Plugin): def handler_imdb(self, url, info, channel): """ Handles imdb.com links, querying the OMDB API for additional info - Typical IMDB URL: http://www.imdb.com/title/tt2467372/ """ - apikey = self.registryValue('imdb.omdbAPI') + apikey = self.registryValue("imdb.omdbAPI") headers = self.get_headers() result = None - + response = None if not self.registryValue("imdb.enabled", channel=channel): - log.debug("SpiffyTitles: IMDB handler disabled. Falling back to default handler.") - + log.debug( + "SpiffyTitles: IMDB handler disabled. Falling back to default handler." + ) return self.handler_default(url, channel) - # Don't care about query strings if "?" in url: url = url.split("?")[0] - # We can only accommodate a specific format of URL here if "/title/" in url: imdb_id = url.split("/title/")[1].rstrip("/") - omdb_url = "http://www.omdbapi.com/?i=%s&plot=short&r=json&tomatoes=true&apikey=%s" % (imdb_id, apikey) - + omdb_url = "http://www.omdbapi.com/" + options = {"apikey": apikey, "i": imdb_id, "r": "json", "plot": "short"} try: - request = requests.get(omdb_url, timeout=10, headers=headers) - - if request.status_code == requests.codes.ok: - response = json.loads(request.content) - result = None - imdb_template = Template(self.registryValue("imdb.template")) - not_found = "Error" in response - unknown_error = response["Response"] != "True" - - if not_found or unknown_error: - log.debug("SpiffyTitles: OMDB error for %s" % (omdb_url)) - else: - meta = "N/A" - tomato = "N/A" - for rating in response["Ratings"]: - if rating["Source"] == "Rotten Tomatoes": - tomato = rating["Value"] - if rating["Source"] == "Metacritic": - meta = "{0}%".format(rating["Value"].split('/')[0]) - template_vars = { - "title": response["Title"], - "year": response["Year"], - "country": response["Country"], - "director": response["Director"], - "plot": response["Plot"], - "imdb_id": response["imdbID"], - "imdb_rating": response["imdbRating"], - "tomatoMeter": tomato, - "metascore": meta, - "released": response["Released"], - "genre": response["Genre"], - "awards": response["Awards"], - "actors": response["Actors"], - "rated": response["Rated"], - "runtime": response["Runtime"], - "writer": response["Writer"], - "votes": response["imdbVotes"], - "website": response["Website"], - "language": response["Language"], - "box_office": response["BoxOffice"], - "production": response["Production"], - "poster": response["Poster"], - "imdb_logo": self.get_imdb_logo() - } - result = imdb_template.render(template_vars) - else: - log.error("SpiffyTitles OMDB API %s: %s" % - (request.status_code, request.content.decode()[:200])) - - except requests.exceptions.Timeout as e: - log.error("SpiffyTitles imdb Timeout: %s" % (str(e))) - except requests.exceptions.ConnectionError as e: - log.error("SpiffyTitles imdb ConnectionError: %s" % (str(e))) - except requests.exceptions.HTTPError as e: - log.error("SpiffyTitles imdb HTTPError: %s" % (str(e))) - + request = requests.get( + omdb_url, params=options, timeout=10, headers=headers + ) + request.raise_for_status() + except ( + requests.exceptions.RequestException, + requests.exceptions.HTTPError, + ) as e: + log.error("SpiffyTitles OMDB Error: %s" % (str(e))) + try: + response = json.loads(request.content.decode()) + imdb_template = Template(self.registryValue("imdb.template")) + if "Error" in response or response["Response"] != "True": + response = None + except: + log.error( + "SpiffyTitles: JSON error opening OMDB response: %s" + % (request.content.decode()) + ) + response = None + if response: + imdb_template = Template(self.registryValue("imdb.template")) + meta = None + tomato = None + for rating in response["Ratings"]: + if rating["Source"] == "Rotten Tomatoes": + tomato = rating["Value"] + if rating["Source"] == "Metacritic": + meta = "{0}%".format(rating["Value"].split("/")[0]) + template_vars = { + "title": response.get("Title"), + "year": response.get("Year"), + "country": response.get("Country"), + "director": response.get("Director"), + "plot": response.get("Plot"), + "imdb_id": response.get("imdbID"), + "imdb_rating": response.get("imdbRating"), + "tomatoMeter": tomato, + "metascore": meta, + "released": response.get("Released"), + "genre": response.get("Genre"), + "awards": response.get("Awards"), + "actors": response.get("Actors"), + "rated": response.get("Rated"), + "runtime": response.get("Runtime"), + "writer": response.get("Writer"), + "votes": response.get("imdbVotes"), + "website": response.get("Website"), + "language": response.get("Language"), + "box_office": response.get("BoxOffice"), + "production": response.get("Production"), + "poster": response.get("Poster"), + "imdb_logo": self.get_imdb_logo(channel), + } + result = imdb_template.render(template_vars) if result is not None: return result else: log.debug("SpiffyTitles: IMDB handler failed. calling default handler") - return self.handler_default(url, channel) def handler_wikipedia(self, url, domain, channel): """ Queries wikipedia API for article extracts. """ - wikipedia_handler_enabled = self.registryValue("wikipedia.enabled", channel=channel) - + wikipedia_handler_enabled = self.registryValue( + "wikipedia.enabled", channel=channel + ) if not wikipedia_handler_enabled: return self.handler_default(url, channel) - self.log.debug("SpiffyTitles: calling Wikipedia handler for %s" % (url)) - pattern = r"/(?:w(?:iki))/(?P[^/]+)$" info = urlparse(url) match = re.search(pattern, info.path) - if not match: self.log.debug("SpiffyTitles: no title found.") return self.handler_default(url, channel) - elif info.fragment and self.registryValue("wikipedia.ignoreSectionLinks", channel=channel): + elif info.fragment and self.registryValue( + "wikipedia.ignoreSectionLinks", channel=channel + ): self.log.debug("SpiffyTitles: ignoring section link.") return self.handler_default(url, channel) else: - page_title = match.groupdict()['page'] - + page_title = match.groupdict()["page"] default_api_params = { "format": "json", "action": "query", @@ -1180,56 +1192,57 @@ class SpiffyTitles(callbacks.Plugin): "exsentences": "2", "exlimit": "1", "exintro": "", - "explaintext": "" + "explaintext": "", } - wiki_api_params = self.registryValue("wikipedia.apiParams", channel=channel) - extra_params = dict(parse_qsl('&'.join(wiki_api_params))) + extra_params = dict(parse_qsl("&".join(wiki_api_params))) title_param = { self.registryValue("wikipedia.titleParam", channel=channel): page_title } - # merge dicts api_params = default_api_params.copy() api_params.update(extra_params) api_params.update(title_param) - param_string = "&".join("%s=%s" % (key, val) for (key, val) in api_params.items()) - api_url = "https://%s/w/api.php?%s" % (info.netloc, param_string) + api_url = "https://%s/w/api.php" % (info.netloc) headers = self.get_headers() extract = "" - self.log.debug("SpiffyTitles: requesting %s" % (api_url)) - - request = requests.get(api_url, headers=headers, timeout=10) + request = requests.get(api_url, headers=headers, params=api_params, timeout=10) ok = request.status_code == requests.codes.ok - if ok: - response = json.loads(request.content) - + response = json.loads(request.content.decode()) if response: try: - extract = list(response['query']['pages'].values())[0]['extract'] + extract = list(response["query"]["pages"].values())[0]["extract"] except KeyError as e: - self.log.error("SpiffyTitles: KeyError parsing Wikipedia API JSON response: \ - %s" % (str(e))) + self.log.error( + "SpiffyTitles: KeyError. Wikipedia API JSON response: %s" + % (str(e)) + ) else: - self.log.error("SpiffyTitles: Error parsing Wikipedia API JSON response") + self.log.error( + "SpiffyTitles: Error parsing Wikipedia API JSON response" + ) else: - self.log.error("SpiffyTitles: Wikipedia API HTTP %s: %s" % - (request.status_code, request.content.decode()[:200])) - + self.log.error( + "SpiffyTitles: Wikipedia API HTTP %s: %s" + % (request.status_code, request.content.decode()[:200]) + ) if extract: - if (self.registryValue("wikipedia.removeParentheses")): - extract = re.sub(r' ?\([^)]*\)', '', extract) + if self.registryValue("wikipedia.removeParentheses"): + extract = re.sub(r" ?\([^)]*\)", "", extract) max_chars = self.registryValue("wikipedia.maxChars", channel=channel) if len(extract) > max_chars: - extract = extract[:max_chars - 3].rsplit(' ', 1)[0].rstrip(',.') + '...' - extract_template = self.registryValue("wikipedia.extractTemplate", channel=channel) + extract = ( + extract[: max_chars - 3].rsplit(" ", 1)[0].rstrip(",.") + "..." + ) + extract_template = self.registryValue( + "wikipedia.extractTemplate", channel=channel + ) wikipedia_template = Template(extract_template) return wikipedia_template.render({"extract": extract}) else: self.log.debug("SpiffyTitles: falling back to default handler") - return self.handler_default(url, channel) def handler_reddit(self, url, domain, channel): @@ -1239,120 +1252,120 @@ class SpiffyTitles(callbacks.Plugin): reddit_handler_enabled = self.registryValue("reddit.enabled", channel=channel) if not reddit_handler_enabled: return self.handler_default(url, channel) - self.log.debug("SpiffyTitles: calling reddit handler for %s" % (url)) - patterns = { "thread": { - "pattern": r"^/r/(?P[^/]+)/comments/(?P[^/]+)(?:/[^/]+/?)?$", - "url": "https://www.reddit.com/r/{subreddit}/comments/{thread}.json" + "pattern": r"^/r/(?P[^/]+)/comments/(?P[^/]+)" + r"(?:/[^/]+/?)?$", + "url": "https://www.reddit.com/r/{subreddit}/comments/{thread}.json", }, "comment": { - "pattern": - r"^/r/(?P[^/]+)/comments/(?P[^/]+)/[^/]+/(?P\w+/?)$", - "url": "https://www.reddit.com/r/{subreddit}/comments/{thread}/x/{comment}.json" + "pattern": r"^/r/(?P[^/]+)/comments/(?P[^/]+)/" + r"[^/]+/(?P\w+/?)$", + "url": "https://www.reddit.com/r/{subreddit}/comments/{thread}/x/" + "{comment}.json", }, "user": { "pattern": r"^/u(?:ser)?/(?P[^/]+)/?$", - "url": "https://www.reddit.com/user/{user}/about.json" - } + "url": "https://www.reddit.com/user/{user}/about.json", + }, } - info = urlparse(url) for name in patterns: - match = re.search(patterns[name]['pattern'], info.path) + match = re.search(patterns[name]["pattern"], info.path) if match: link_type = name link_info = match.groupdict() - data_url = patterns[name]['url'].format(**link_info) + data_url = patterns[name]["url"].format(**link_info) break - if not match: self.log.debug("SpiffyTitles: no title found.") return self.handler_default(url, channel) - headers = self.get_headers() - self.log.debug("SpiffyTitles: requesting %s" % (data_url)) - request = requests.get(data_url, headers=headers, timeout=10) ok = request.status_code == requests.codes.ok data = {} - extract = '' - + extract = "" if ok: - response = json.loads(request.content) - + response = json.loads(request.content.decode()) if response: try: if link_type == "thread": - data = response[0]['data']['children'][0]['data'] - + data = response[0]["data"]["children"][0]["data"] if link_type == "comment": - data = response[1]['data']['children'][0]['data'] - data['title'] = response[0]['data']['children'][0]['data']['title'] - + data = response[1]["data"]["children"][0]["data"] + data["title"] = response[0]["data"]["children"][0]["data"][ + "title" + ] if link_type == "user": - data = response['data'] + data = response["data"] except KeyError as e: - self.log.error("SpiffyTitles: KeyError parsing Reddit JSON response: %s" % - (str(e))) + self.log.error( + "SpiffyTitles: KeyError parsing Reddit JSON response: %s" + % (str(e)) + ) else: self.log.error("SpiffyTitles: Error parsing Reddit JSON response") else: - self.log.error("SpiffyTitles: Reddit HTTP %s: %s" % - (request.status_code, request.content.decode()[:200])) - + self.log.error( + "SpiffyTitles: Reddit HTTP %s: %s" + % (request.status_code, request.content.decode()[:200]) + ) if data: today = pendulum.now().date() - created = pendulum.from_timestamp(data['created_utc']).date() + created = pendulum.from_timestamp(data["created_utc"]).date() age_days = (today - created).days if age_days == 0: age = "today" elif age_days == 1: age = "yesterday" else: - age = '{}d'.format(age_days % 365) + age = "{}d".format(age_days % 365) if age_days > 365: - age = '{}y, '.format(age_days // 365) + age + age = "{}y, ".format(age_days // 365) + age age = age + " ago" if link_type == "thread": link_type = "linkThread" - if data['is_self']: + if data["is_self"]: link_type = "textThread" - data['url'] = "" - extract = data.get('selftext', '') + data["url"] = "" + extract = data.get("selftext", "") if link_type == "comment": - extract = data.get('body', '') - link_type_template = self.registryValue("reddit." + link_type + "Template", - channel=channel) + extract = data.get("body", "") + link_type_template = self.registryValue( + "reddit." + link_type + "Template", channel=channel + ) reddit_template = Template(link_type_template) template_vars = { - "id": data.get('id', ''), - "user": data.get('name', ''), - "gold": (data.get('is_gold', False) is True), - "mod": (data.get('is_mod', False) is True), - "author": data.get('author', ''), - "subreddit": data.get('subreddit', ''), - "url": data.get('url', ''), - "title": data.get('title', ''), - "domain": data.get('domain', ''), - "score": data.get('score', 0), - "percent": '{}%'.format(int(data.get('upvote_ratio', 0) * 100)), - "comments": '{:,}'.format(data.get('num_comments', 0)), - "created": created.strftime('%Y-%m-%d'), + "id": data.get("id", ""), + "user": data.get("name", ""), + "gold": (data.get("is_gold", False) is True), + "mod": (data.get("is_mod", False) is True), + "author": data.get("author", ""), + "subreddit": data.get("subreddit", ""), + "url": data.get("url", ""), + "title": data.get("title", ""), + "domain": data.get("domain", ""), + "score": data.get("score", 0), + "percent": "{}%".format(int(data.get("upvote_ratio", 0) * 100)), + "comments": "{:,}".format(data.get("num_comments", 0)), + "created": created.strftime("%Y-%m-%d"), "age": age, - "link_karma": '{:,}'.format(data.get('link_karma', 0)), - "comment_karma": '{:,}'.format(data.get('comment_karma', 0)), - "extract": "%%extract%%" + "link_karma": "{:,}".format(data.get("link_karma", 0)), + "comment_karma": "{:,}".format(data.get("comment_karma", 0)), + "extract": "%%extract%%", } reply = reddit_template.render(template_vars) if extract: max_chars = self.registryValue("reddit.maxChars", channel=channel) - max_extract_chars = max_chars + len('%%extract%%') - len(reply) + max_extract_chars = max_chars + len("%%extract%%") - len(reply) if len(extract) > max_extract_chars: - extract = extract[:max_extract_chars - 3].rsplit(' ', 1)[0].rstrip(',.') + '...' - template_vars['extract'] = extract + extract = ( + extract[: max_extract_chars - 3].rsplit(" ", 1)[0].rstrip(",.") + + "..." + ) + template_vars["extract"] = extract reply = reddit_template.render(template_vars) return reply else: @@ -1361,73 +1374,72 @@ class SpiffyTitles(callbacks.Plugin): def is_valid_imgur_id(self, input): """ - Tests if input matches the typical imgur id, which seems to be alphanumeric. \ + 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, channel): """ Queries imgur API for additional information about imgur links. - This handler is for any imgur.com domain. """ self.initialize_imgur_client(channel) - is_album = info.path.startswith("/a/") result = None - if is_album: result = self.handler_imgur_album(url, info, channel) else: result = self.handler_imgur_image(url, info, channel) - return result def handler_imgur_album(self, url, info, channel): """ Handles retrieving information about albums from the imgur API. - - imgur provides the following information about albums: https://api.imgur.com/models/album + 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 - self.initialize_imgur_client(channel) + self.initialize_imgur_client(channel) if self.imgur_client: 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 self.is_valid_imgur_id(album_id): log.debug("SpiffyTitles: found imgur album id %s" % (album_id)) - try: album = self.imgur_client.get_album(album_id) - if album: - album_template = self.registryValue("imgur.template", channel=channel) + album_template = self.registryValue( + "imgur.template", channel=channel + ) imgur_album_template = Template(album_template) - 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 - }) - + 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: - log.error("SpiffyTitles: imgur album API returned unexpected results!") - + log.error( + "SpiffyTitles: imgur album API returned unexpected results!" + ) except ImgurClientRateLimitError as e: - log.error("SpiffyTitles: imgur rate limit error: %s" % (e.error_message)) + log.error( + "SpiffyTitles: imgur rate limit error: %s" % (e.error_message) + ) except ImgurClientError as e: - log.error("SpiffyTitles: imgur client error: %s" % (e.error_message)) + log.error( + "SpiffyTitles: imgur client error: %s" % (e.error_message) + ) else: log.debug("SpiffyTitles: unable to determine album id for %s" % (url)) else: @@ -1436,58 +1448,61 @@ class SpiffyTitles(callbacks.Plugin): def handler_imgur_image(self, url, info, channel): """ Handles retrieving information about images from the imgur API. - Used for both direct images and imgur.com/some_image_id_here type links, as they're both single images. """ self.initialize_imgur_client(channel) - from imgurpython.helpers.error import ImgurClientRateLimitError from imgurpython.helpers.error import ImgurClientError - title = None + title = None if self.imgur_client: """ - 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 there is a period in the path, it's a direct link to an image. + If not, then it's an 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): log.debug("SpiffyTitles: found image id %s" % (image_id)) - try: image = self.imgur_client.get_image(image_id) - if image: - channel_template = self.registryValue("imgur.template", channel=channel) + channel_template = self.registryValue( + "imgur.template", channel=channel + ) imgur_template = Template(channel_template) readable_file_size = self.get_readable_file_size(image.size) - compiled_template = imgur_template.render({ - "title": image.title, - "type": image.type, - "nsfw": image.nsfw, - "width": image.width, - "height": image.height, - "view_count": "{:,}".format(image.views), - "file_size": readable_file_size, - "section": image.section - }) - + compiled_template = imgur_template.render( + { + "title": image.title, + "type": image.type, + "nsfw": image.nsfw, + "width": image.width, + "height": image.height, + "view_count": "{:,}".format(image.views), + "file_size": readable_file_size, + "section": image.section, + } + ) title = compiled_template else: - log.error("SpiffyTitles: imgur API returned unexpected results!") + log.error( + "SpiffyTitles: imgur API returned unexpected results!" + ) except ImgurClientRateLimitError as e: - log.error("SpiffyTitles: imgur rate limit error: %s" % (e.error_message)) + log.error( + "SpiffyTitles: imgur rate limit error: %s" % (e.error_message) + ) except ImgurClientError as e: - log.error("SpiffyTitles: imgur client error: %s" % (e.error_message)) + log.error( + "SpiffyTitles: imgur client error: %s" % (e.error_message) + ) else: log.error("SpiffyTitles: error retrieving image id for %s" % (url)) - if title is not None: return title else: @@ -1508,20 +1523,11 @@ class SpiffyTitles(callbacks.Plugin): Remove cruft from title and apply bold if applicable """ use_bold = self.registryValue("useBold", channel=channel) - - IP_pattern = r"((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)([ (\[]?(\.|dot)[ )\]]?(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3})" - # Replace anywhere in string - title = title.replace("\n", " ") - title = title.replace("\t", " ") - title = re.sub(" +", " ", title) - title = re.sub(IP_pattern, "206.18.125.29", title) - + title = re.sub(r"\s+| +", " ", title) if use_bold: title = ircutils.bold(title) - title = title.strip() - return title def get_title_from_html(self, html): @@ -1529,7 +1535,6 @@ class SpiffyTitles(callbacks.Plugin): Retrieves value of tag from HTML """ soup = BeautifulSoup(html) - if soup is not None: """ Some websites have more than one title tag, so get all of them @@ -1537,36 +1542,29 @@ class SpiffyTitles(callbacks.Plugin): """ head = soup.find("head") titles = head.find_all("title") - if titles is not None and len(titles): for t in titles[::-1]: title = t.get_text().strip() - if len(title): + if title: return title - timeout_decorator.timeout(wall_clock_timeout) - def get_source_by_url(self, url, retries=1): + def get_source_by_url(self, url, channel, retries=1): """ Get the HTML of a website based on a URL """ max_retries = self.registryValue("maxRetries") if retries is None: retries = 1 - if retries >= max_retries: log.debug("SpiffyTitles: hit maximum retries for %s" % url) - return (None, False) - log.debug("SpiffyTitles: attempt #%s for %s" % (retries, url)) - try: headers = self.get_headers() - log.debug("SpiffyTitles: requesting %s" % (url)) - - with requests.get(url, headers=headers, timeout=10, - allow_redirects=True, stream=True) as request: + with requests.get( + url, headers=headers, timeout=10, allow_redirects=True, stream=True + ) as request: is_redirect = False if request.history: # check the top two domain levels @@ -1574,90 +1572,97 @@ class SpiffyTitles(callbacks.Plugin): real_domain = self.get_base_domain(request.url) if link_domain != real_domain: is_redirect = True - for redir in request.history: - log.debug("SpiffyTitles: Redirect %s from %s" % (redir.status_code, redir.url)) + log.debug( + "SpiffyTitles: Redirect %s from %s" + % (redir.status_code, redir.url) + ) log.debug("SpiffyTitles: Final url %s" % (request.url)) - 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() + # Check the content type + content_type = ( + request.headers.get("content-type").split(";")[0].strip() + ) acceptable_types = self.registryValue("mimeTypes") - log.debug("SpiffyTitles: content type %s" % (content_type)) - if content_type in acceptable_types: text = request.content - if text: return (text, is_redirect) else: log.debug("SpiffyTitles: empty content from %s" % (url)) - else: - log.debug("SpiffyTitles: unacceptable mime type %s for url %s" % - (content_type, url)) - suffixes = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'] + log.debug( + "SpiffyTitles: unacceptable mime type %s for url %s" + % (content_type, url) + ) + suffixes = ["B", "KiB", "MiB", "GiB", "TiB", "PiB"] + def humansize(nbytes): i = 0 - while nbytes >= 1024 and i < len(suffixes)-1: - nbytes /= 1024. + while nbytes >= 1024 and i < len(suffixes) - 1: + nbytes /= 1024.0 i += 1 - f = ('%.2f' % nbytes).rstrip('0').rstrip('.') - return '%s %s' % (f, suffixes[i]) + f = ("%.2f" % nbytes).rstrip("0").rstrip(".") + return "%s %s" % (f, suffixes[i]) + size = request.headers.get("content-length") if size: size = humansize(int(size)) - text = "[{0}] ({1})".format(content_type, size) - else: - text = "[{0}]".format(content_type) - text = "<html><head><title>{0}".format(text) + file_template = self.registryValue( + "default.fileTemplate", channel=channel + ) + text = Template(file_template).render( + {"type": content_type, "size": size} + ) + text = ( + "{0}" + "".format(text) + ) return (text, is_redirect) else: - log.error("SpiffyTitles HTTP response code %s" % (request.status_code,)) + log.error( + "SpiffyTitles HTTP response code %s" % (request.status_code,) + ) text = self.registryValue("badLinkText") - text = "{0}".format(text) + text = ( + "{0}" + "".format(text) + ) return (text, is_redirect) - - except timeout_decorator.TimeoutError: - log.error("SpiffyTitles: wall timeout!") - self.get_source_by_url(url, retries + 1) except requests.exceptions.MissingSchema as e: url_wschema = "http://%s" % (url) log.error("SpiffyTitles missing schema. Retrying with %s" % (url_wschema)) info = urlparse(url_wschema) - if self.is_ignored_domain(info.netloc, dynamic.channel): + if self.is_ignored_domain(info.netloc, channel): return else: - return self.get_source_by_url(url_wschema) + return self.get_source_by_url(url_wschema, channel) except requests.exceptions.Timeout as e: log.error("SpiffyTitles Timeout: %s" % (str(e))) - self.get_source_by_url(url, retries + 1) + self.get_source_by_url(url, channel, retries + 1) except requests.exceptions.ConnectionError as e: log.error("SpiffyTitles ConnectionError: %s" % (str(e))) - self.get_source_by_url(url, retries + 1) + self.get_source_by_url(url, channel, retries + 1) except requests.exceptions.HTTPError as e: log.error("SpiffyTitles HTTPError: %s" % (str(e))) except requests.exceptions.InvalidURL as e: log.error("SpiffyTitles InvalidURL: %s" % (str(e))) - return (None, False) def get_base_domain(self, url): """ Returns the FQDN comprising the top two domain levels """ - return '.'.join(urlparse(url).netloc.rsplit('.', 2)[-2:]) + return ".".join(urlparse(url).netloc.rsplit(".", 2)[-2:]) def get_headers(self): agent = self.get_user_agent() self.accept_language = self.registryValue("language") - headers = { "User-Agent": agent, - "Accept-Language": ";".join((self.accept_language, "q=1.0")) + "Accept-Language": "{0}".format(self.accept_language), } - return headers def get_user_agent(self): @@ -1674,10 +1679,8 @@ class SpiffyTitles(callbacks.Plugin): """ match = False pattern = self.registryValue("linkMessageIgnorePattern") - if pattern: match = re.search(pattern, input) - return match def title_matches_ignore_pattern(self, input, channel): @@ -1687,14 +1690,13 @@ class SpiffyTitles(callbacks.Plugin): """ match = False pattern = self.registryValue("ignoredTitlePattern", channel=channel) - if pattern: match = re.search(pattern, input) - if match: - log.debug("SpiffyTitles: title %s matches ignoredTitlePattern for %s" % - (input, channel)) - + log.debug( + "SpiffyTitles: title %s matches ignoredTitlePattern for %s" + % (input, channel) + ) return match def get_url_from_message(self, input): @@ -1703,11 +1705,9 @@ class SpiffyTitles(callbacks.Plugin): """ url_re = self.registryValue("urlRegularExpression") match = re.search(url_re, input) - if match: raw_url = match.group(0).strip() url = self.remove_control_characters(str(raw_url)) - return url def remove_control_characters(self, s): @@ -1719,13 +1719,16 @@ class SpiffyTitles(callbacks.Plugin): required_capability = self.registryValue("requireCapability", channel=channel) cap = ircdb.makeChannelCapability(channel, required_capability) has_cap = ircdb.checkCapability(mask, cap, ignoreDefaultAllow=True) - if has_cap: - log.debug("SpiffyTitles: %s has required capability '%s'" % (mask, required_capability)) + log.debug( + "SpiffyTitles: %s has required capability '%s'" + % (mask, required_capability) + ) else: - log.debug("SpiffyTitles: %s does NOT have required capability '%s'" % - (mask, required_capability)) - + log.debug( + "SpiffyTitles: %s does NOT have required capability '%s'" + % (mask, required_capability) + ) return has_cap def get_template(self, handler_template, channel): @@ -1735,4 +1738,5 @@ class SpiffyTitles(callbacks.Plugin): template = Template(self.registryValue(handler_template, channel=channel)) return template + Class = SpiffyTitles diff --git a/SpiffyTitles/requirements.txt b/SpiffyTitles/requirements.txt index c5d7956..ce69fbd 100644 --- a/SpiffyTitles/requirements.txt +++ b/SpiffyTitles/requirements.txt @@ -1,9 +1,5 @@ -html5lib -lxml +requests beautifulsoup4 jinja2 -imgurpython -requests -timeout-decorator -certifi pendulum +imgurpython \ No newline at end of file