From baf3f4823e07739758801aab01e42866be823e5e Mon Sep 17 00:00:00 2001 From: oddluck Date: Sat, 9 May 2020 01:43:09 -0400 Subject: [PATCH] Set more config variables private --- Geo/config.py | 2 +- IMDb/__init__.py | 13 +- IMDb/config.py | 59 +++- IMDb/plugin.py | 176 +++++++--- Lyrics/__init__.py | 6 +- Lyrics/config.py | 32 +- Lyrics/plugin.py | 29 +- SpiffyTitles/config.py | 8 +- TextArt/config.py | 4 +- Tweety/__init__.py | 14 +- Tweety/config.py | 107 +++++- Tweety/plugin.py | 721 +++++++++++++++++++++++++++-------------- Weed/__init__.py | 6 +- Weed/config.py | 15 +- Weed/plugin.py | 60 ++-- WorldTime/__init__.py | 12 +- WorldTime/config.py | 35 +- WorldTime/plugin.py | 143 +++++--- 18 files changed, 986 insertions(+), 456 deletions(-) diff --git a/Geo/config.py b/Geo/config.py index 6f672ba..49f8bc9 100644 --- a/Geo/config.py +++ b/Geo/config.py @@ -53,7 +53,7 @@ conf.registerGlobalValue(Geo, 'datalastupdated', registry.PositiveInteger(1, """An integer representing the time since epoch the .dat file was last updated.""")) conf.registerGlobalValue(Geo, 'licenseKey', - registry.String('', """MaxMind license key.""")) + registry.String('', """MaxMind license key.""", private=True)) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/IMDb/__init__.py b/IMDb/__init__.py index 0710dd8..1d099fc 100644 --- a/IMDb/__init__.py +++ b/IMDb/__init__.py @@ -40,21 +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(config) reload(plugin) diff --git a/IMDb/config.py b/IMDb/config.py index dc12b4b..e021d7a 100644 --- a/IMDb/config.py +++ b/IMDb/config.py @@ -30,9 +30,11 @@ import supybot.conf as conf import supybot.registry as registry + try: from supybot.i18n import PluginInternationalization - _ = PluginInternationalization('IMDb') + + _ = PluginInternationalization("IMDb") except: # Placeholder that allows to run the plugin on a bot # without the i18n module @@ -45,24 +47,55 @@ 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('IMDb', True) + + conf.registerPlugin("IMDb", True) -IMDb = conf.registerPlugin('IMDb') +IMDb = conf.registerPlugin("IMDb") -conf.registerChannelValue(IMDb, 'template', - registry.String("\x02\x031,8 IMDb \x0F\x02 :: $title ($year, $country, [$rated], $genre, $runtime) :: IMDb: $imdbRating | MC: $metascore | RT: $tomatoMeter :: http://imdb.com/title/$imdbID :: $plot :: Director: $director :: Cast: $actors :: Writer: $writer", _("""Template for the output of a search query."""))) +conf.registerChannelValue( + IMDb, + "template", + registry.String( + "\x02\x031,8 IMDb \x0F\x02 :: $title ($year, $country, [$rated], $genre, " + "$runtime) :: IMDb: $imdbRating | MC: $metascore | RT: $tomatoMeter :: " + "http://imdb.com/title/$imdbID :: $plot :: Director: $director :: Cast: " + "$actors :: Writer: $writer", + _("""Template for the output of a search query."""), + ), +) -conf.registerChannelValue(IMDb, 'noResultsMessage', - registry.String("No results for that query.", _("""This message is sent when there are no results"""))) +conf.registerChannelValue( + IMDb, + "noResultsMessage", + registry.String( + "No results for that query.", + _("""This message is sent when there are no results"""), + ), +) -conf.registerGlobalValue(IMDb, 'omdbAPI', - registry.String('', _("""OMDB API Key"""))) +conf.registerGlobalValue( + IMDb, "omdbAPI", registry.String("", _("""OMDB API Key"""), private=True) +) -conf.registerChannelValue(IMDb, 'googleSearch', - registry.Boolean(True, _("""Use google to perform searches for better results."""))) +conf.registerChannelValue( + IMDb, + "googleSearch", + registry.Boolean(True, _("""Use google to perform searches for better results.""")), +) -conf.registerGlobalValue(IMDb, '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( + IMDb, + "userAgents", + registry.CommaSeparatedListOfStrings( + [ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:76.0) Gecko/20100101 Firefox/76.0", + "Mozilla/5.0 (Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0", + "Mozilla/5.0 (Linux x86_64; rv:76.0) Gecko/20100101 Firefox/76.0", + ], + _("""Reported user agent when fetching links"""), + ), +) # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: diff --git a/IMDb/plugin.py b/IMDb/plugin.py index 1769042..fe17ca6 100644 --- a/IMDb/plugin.py +++ b/IMDb/plugin.py @@ -43,14 +43,17 @@ import re try: from supybot.i18n import PluginInternationalization - _ = PluginInternationalization('IMDb') + + _ = PluginInternationalization("IMDb") except ImportError: # Placeholder that allows to run the plugin on a bot # without the i18n module _ = lambda x: x + class IMDb(callbacks.Plugin): """Queries OMDB database for information about IMDb titles""" + threaded = True def dosearch(self, query): @@ -60,12 +63,13 @@ class IMDb(callbacks.Plugin): searchurl += "{0} site:imdb.com/title/".format(query) agents = self.registryValue("userAgents") ua = random.choice(agents) - header = {'User-Agent': ua} + header = {"User-Agent": ua} data = requests.get(searchurl, headers=header, timeout=10) data.raise_for_status() soup = BeautifulSoup(data.content) - elements = soup.select('.r a') - url = elements[0]['href'] + url = soup.find( + "a", attrs={"href": re.compile(r"https://www.imdb.com/title/tt\d+/$")} + )["href"] except Exception: pass return url @@ -77,21 +81,27 @@ class IMDb(callbacks.Plugin): channel = msg.channel url = result = None id = stop = False - apikey = self.registryValue('omdbAPI') + apikey = self.registryValue("omdbAPI") if not apikey: irc.reply("Error: You must set an API key to use this plugin.") return - if re.match('tt\d+', query.strip()): + if re.match(r"tt\d+", query.strip()): id = True url = "http://imdb.com/title/{0}".format(query.strip()) if not id and self.registryValue("googleSearch", channel): url = self.dosearch(query) - if url and 'imdb.com/title/' in url: + if url and "imdb.com/title/" in url: imdb_id = url.split("/title/")[1].rstrip("/") - omdb_url = "http://www.omdbapi.com/?i=%s&plot=short&r=json&apikey=%s" % (imdb_id, apikey) + omdb_url = "http://www.omdbapi.com/?i=%s&plot=short&r=json&apikey=%s" % ( + imdb_id, + apikey, + ) log.debug("IMDb: requesting %s" % omdb_url) else: - omdb_url = "http://www.omdbapi.com/?t=%s&plot=short&r=json&apikey=%s" % (query, apikey) + omdb_url = "http://www.omdbapi.com/?t=%s&plot=short&r=json&apikey=%s" % ( + query, + apikey, + ) try: request = requests.get(omdb_url, timeout=10) if request.status_code == requests.codes.ok: @@ -99,11 +109,14 @@ class IMDb(callbacks.Plugin): not_found = "Error" in response unknown_error = response["Response"] != "True" if not_found or unknown_error: - match = re.match('(.*) \(*(\d\d\d\d)\)*$', query.strip()) + match = re.match(r"(.*) \(*(\d\d\d\d)\)*$", query.strip()) if match: query = match.group(1).strip() year = match.group(2).strip() - omdb_url = "http://www.omdbapi.com/?t=%s&y=%s&plot=short&r=json&apikey=%s" % (query, year, apikey) + omdb_url = ( + "http://www.omdbapi.com/?t=%s&y=%s&plot=short&r=json&apikey=%s" + % (query, year, apikey) + ) request = requests.get(omdb_url, timeout=10) if request.status_code == requests.codes.ok: response = json.loads(request.content) @@ -112,11 +125,17 @@ class IMDb(callbacks.Plugin): if not_found or unknown_error: log.debug("IMDb: OMDB error for %s" % (omdb_url)) else: - log.error("IMDb OMDB API %s - %s" % (request.status_code, request.content.decode())) + log.error( + "IMDb OMDB API %s - %s" + % (request.status_code, request.content.decode()) + ) else: log.debug("IMDb: OMDB error for %s" % (omdb_url)) - query = re.sub('\d\d\d\d', '', query) - omdb_url = "http://www.omdbapi.com/?s=%s&plot=short&r=json&apikey=%s" % (query, apikey) + query = re.sub(r"\d\d\d\d", "", query) + omdb_url = ( + "http://www.omdbapi.com/?s=%s&plot=short&r=json&apikey=%s" + % (query, apikey) + ) request = requests.get(omdb_url, timeout=10) if request.status_code == requests.codes.ok: response = json.loads(request.content) @@ -124,42 +143,78 @@ class IMDb(callbacks.Plugin): unknown_error = response["Response"] != "True" if not_found or unknown_error: log.debug("IMDb: OMDB error for %s" % (omdb_url)) - elif response.get("Search") and len(response.get("Search")) == 1: + elif ( + response.get("Search") + and len(response.get("Search")) == 1 + ): imdb_id = response["Search"][0]["imdbID"] - omdb_url = "http://www.omdbapi.com/?i=%s&plot=short&r=json&apikey=%s" % (imdb_id, apikey) + omdb_url = ( + "http://www.omdbapi.com/?i=%s&plot=short&r=json&apikey=%s" + % (imdb_id, apikey) + ) request = requests.get(omdb_url, timeout=10) if request.status_code == requests.codes.ok: response = json.loads(request.content) not_found = "Error" in response unknown_error = response["Response"] != "True" if not_found or unknown_error: - log.debug("IMDb: OMDB error for %s" % (omdb_url)) + log.debug( + "IMDb: OMDB error for %s" % (omdb_url) + ) else: - log.error("IMDb OMDB API %s - %s" % (request.status_code, request.content.decode())) - elif response.get("Search") and len(response.get("Search")) > 1: + log.error( + "IMDb OMDB API %s - %s" + % ( + request.status_code, + request.content.decode(), + ) + ) + elif ( + response.get("Search") + and len(response.get("Search")) > 1 + ): reply = "No title found. Did you mean:" for item in response["Search"]: - reply += " {0} ({1}) [{2}],".format(item["Title"], item["Year"], item["imdbID"]) - irc.reply(reply.rstrip(',')) + reply += " {0} ({1}) [{2}],".format( + item["Title"], item["Year"], item["imdbID"] + ) + irc.reply(reply.rstrip(",")) not_found = stop = True return else: - log.error("IMDb OMDB API %s - %s" % (request.status_code, request.content.decode())) + log.error( + "IMDb OMDB API %s - %s" + % (request.status_code, request.content.decode()) + ) if not not_found or not unknown_error: meta = tomato = None imdb_template = self.registryValue("template", channel) - imdb_template = imdb_template.replace("$title", str(response.get("Title"))) - imdb_template = imdb_template.replace("$year", str(response.get("Year"))) - imdb_template = imdb_template.replace("$country", str(response.get("Country"))) - imdb_template = imdb_template.replace("$director", str(response.get("Director"))) - imdb_template = imdb_template.replace("$plot", str(response.get("Plot"))) - imdb_template = imdb_template.replace("$imdbID", str(response.get("imdbID"))) - imdb_template = imdb_template.replace("$imdbRating", str(response.get("imdbRating"))) + imdb_template = imdb_template.replace( + "$title", str(response.get("Title")) + ) + imdb_template = imdb_template.replace( + "$year", str(response.get("Year")) + ) + imdb_template = imdb_template.replace( + "$country", str(response.get("Country")) + ) + imdb_template = imdb_template.replace( + "$director", str(response.get("Director")) + ) + imdb_template = imdb_template.replace( + "$plot", str(response.get("Plot")) + ) + imdb_template = imdb_template.replace( + "$imdbID", str(response.get("imdbID")) + ) + imdb_template = imdb_template.replace( + "$imdbRating", str(response.get("imdbRating")) + ) for rating in response["Ratings"]: if rating["Source"] == "Rotten Tomatoes": tomato = rating.get("Value") if rating["Source"] == "Metacritic": - meta = "{0}%".format(rating.get("Value").split('/')[0]) + meta = "{0}%".format(rating.get("Value").split("/")[0]) if meta: imdb_template = imdb_template.replace("$metascore", meta) else: @@ -168,22 +223,51 @@ class IMDb(callbacks.Plugin): imdb_template = imdb_template.replace("$tomatoMeter", tomato) else: imdb_template = imdb_template.replace("$tomatoMeter", "N/A") - imdb_template = imdb_template.replace("$released", str(response.get("Released"))) - imdb_template = imdb_template.replace("$genre", str(response.get("Genre"))) - imdb_template = imdb_template.replace("$released", str(response.get("Released"))) - imdb_template = imdb_template.replace("$awards", str(response.get("Awards"))) - imdb_template = imdb_template.replace("$actors", str(response.get("Actors"))) - imdb_template = imdb_template.replace("$rated", str(response.get("Rated"))) - imdb_template = imdb_template.replace("$runtime", str(response.get("Runtime"))) - imdb_template = imdb_template.replace("$writer", str(response.get("Writer"))) - imdb_template = imdb_template.replace("$votes", str(response.get("imdbVotes"))) - imdb_template = imdb_template.replace("$boxOffice", str(response.get("BoxOffice"))) - imdb_template = imdb_template.replace("$production", str(response.get("Production"))) - imdb_template = imdb_template.replace("$website", str(response.get("Website"))) - imdb_template = imdb_template.replace("$poster", str(response.get("Poster"))) + imdb_template = imdb_template.replace( + "$released", str(response.get("Released")) + ) + imdb_template = imdb_template.replace( + "$genre", str(response.get("Genre")) + ) + imdb_template = imdb_template.replace( + "$released", str(response.get("Released")) + ) + imdb_template = imdb_template.replace( + "$awards", str(response.get("Awards")) + ) + imdb_template = imdb_template.replace( + "$actors", str(response.get("Actors")) + ) + imdb_template = imdb_template.replace( + "$rated", str(response.get("Rated")) + ) + imdb_template = imdb_template.replace( + "$runtime", str(response.get("Runtime")) + ) + imdb_template = imdb_template.replace( + "$writer", str(response.get("Writer")) + ) + imdb_template = imdb_template.replace( + "$votes", str(response.get("imdbVotes")) + ) + imdb_template = imdb_template.replace( + "$boxOffice", str(response.get("BoxOffice")) + ) + imdb_template = imdb_template.replace( + "$production", str(response.get("Production")) + ) + imdb_template = imdb_template.replace( + "$website", str(response.get("Website")) + ) + imdb_template = imdb_template.replace( + "$poster", str(response.get("Poster")) + ) result = imdb_template else: - log.error("IMDb OMDB API %s - %s" % (request.status_code, request.content.decode())) + log.error( + "IMDb OMDB API %s - %s" + % (request.status_code, request.content.decode()) + ) except requests.exceptions.Timeout as e: log.error("IMDb Timeout: %s" % (str(e))) except requests.exceptions.ConnectionError as e: @@ -195,7 +279,9 @@ class IMDb(callbacks.Plugin): irc.reply(result, prefixNick=False) elif not stop: irc.error(self.registryValue("noResultsMessage", channel)) - imdb = wrap(imdb, ['text']) + + imdb = wrap(imdb, ["text"]) + Class = IMDb diff --git a/Lyrics/__init__.py b/Lyrics/__init__.py index 89b962d..2a716fa 100644 --- a/Lyrics/__init__.py +++ b/Lyrics/__init__.py @@ -39,19 +39,19 @@ import supybot.world as world __version__ = "2020.02.24+git" # XXX Replace this with an appropriate author or supybot.Author instance. -__author__ = supybot.Author('oddluck', 'oddluck', - 'oddluck@riseup.net') +__author__ = 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(config) reload(plugin) diff --git a/Lyrics/config.py b/Lyrics/config.py index b06021c..7661157 100644 --- a/Lyrics/config.py +++ b/Lyrics/config.py @@ -29,9 +29,11 @@ import supybot.conf as conf import supybot.registry as registry + try: from supybot.i18n import PluginInternationalization - _ = PluginInternationalization('Lyrics') + + _ = PluginInternationalization("Lyrics") except: # Placeholder that allows to run the plugin on a bot # without the i18n module @@ -44,12 +46,28 @@ 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('Lyrics', True) -Lyrics = conf.registerPlugin('Lyrics') + conf.registerPlugin("Lyrics", True) -conf.registerChannelValue(Lyrics, 'googleSearch', - registry.Boolean(True, _("""Use google to perform searches for better results."""))) -conf.registerGlobalValue(Lyrics, '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"""))) +Lyrics = conf.registerPlugin("Lyrics") + +conf.registerChannelValue( + Lyrics, + "googleSearch", + registry.Boolean(True, _("""Use google to perform searches for better results.""")), +) + +conf.registerGlobalValue( + Lyrics, + "userAgents", + registry.CommaSeparatedListOfStrings( + [ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:76.0) Gecko/20100101 Firefox/76.0", + "Mozilla/5.0 (Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0", + "Mozilla/5.0 (Linux x86_64; rv:76.0) Gecko/20100101 Firefox/76.0", + ], + _("""Reported user agent when fetching links"""), + ), +) diff --git a/Lyrics/plugin.py b/Lyrics/plugin.py index 20ecfa6..2e6b6ea 100644 --- a/Lyrics/plugin.py +++ b/Lyrics/plugin.py @@ -42,14 +42,17 @@ import random try: from supybot.i18n import PluginInternationalization - _ = PluginInternationalization('Weed') + + _ = PluginInternationalization("Weed") except ImportError: # Placeholder that allows to run the plugin on a bot # without the i18n module _ = lambda x: x + class Lyrics(callbacks.Plugin): """Retrieves song lyrics""" + threaded = True def dosearch(self, lyric): @@ -60,33 +63,33 @@ class Lyrics(callbacks.Plugin): searchurl += "{0} site:lyrics.fandom.com/wiki/".format(lyric) agents = self.registryValue("userAgents") ua = random.choice(agents) - header = {'User-Agent': ua} + header = {"User-Agent": ua} data = requests.get(searchurl, headers=header, timeout=10) data.raise_for_status() log.debug(data.content.decode()) soup = BeautifulSoup(data.content) - elements = soup.select('.r a') - title = soup.find("h3").getText().replace(":", " - ").split('|')[0] - url = elements[0]['href'] + elements = soup.select(".r a") + title = soup.find("h3").getText().replace(":", " - ").split("|")[0] + url = elements[0]["href"] except Exception: pass return title, url def getlyrics(self, query): lyrics = None - if 'lyrics.fandom.com/wiki/' in query: + if "lyrics.fandom.com/wiki/" in query: try: log.debug("Lyrics: requesting {0}".format(query)) lyrics = pylyrics3.get_lyrics_from_url(query) - lyrics = re.sub('(?, ") return - lyric = wrap(lyric, ['text']) + + lyric = wrap(lyric, ["text"]) + Class = Lyrics diff --git a/SpiffyTitles/config.py b/SpiffyTitles/config.py index d7a4af0..89db841 100644 --- a/SpiffyTitles/config.py +++ b/SpiffyTitles/config.py @@ -486,7 +486,9 @@ conf.registerChannelValue( # Twitch API Key conf.registerGlobalValue( - SpiffyTitles.twitch, "clientID", registry.String("", _("""Twitch API Client_ID""")) + SpiffyTitles.twitch, + "clientID", + registry.String("", _("""Twitch API Client_ID"""), private=True), ) # Twitch Logo @@ -579,7 +581,9 @@ conf.registerChannelValue( # OMDB API Key conf.registerGlobalValue( - SpiffyTitles.imdb, "omdbAPI", registry.String("", _("""OMDB API Key""")) + SpiffyTitles.imdb, + "omdbAPI", + registry.String("", _("""OMDB API Key"""), private=True), ) # IMDB Logo diff --git a/TextArt/config.py b/TextArt/config.py index 119fac4..be6ad47 100644 --- a/TextArt/config.py +++ b/TextArt/config.py @@ -49,10 +49,10 @@ def configure(advanced): TextArt = conf.registerPlugin('TextArt') conf.registerGlobalValue(TextArt, 'pasteAPI', - registry.String('', _("""Paste.ee API Key"""))) + registry.String('', _("""Paste.ee API Key"""), private=True)) conf.registerGlobalValue(TextArt, 'imgurAPI', - registry.String('', _("""Imgur Client ID"""))) + registry.String('', _("""Imgur Client ID"""), private=True)) conf.registerChannelValue(TextArt, 'pasteEnable', registry.Boolean(False, _("""Turns on and off paste.ee support"""))) diff --git a/Tweety/__init__.py b/Tweety/__init__.py index 72d54ac..5f1d8ec 100644 --- a/Tweety/__init__.py +++ b/Tweety/__init__.py @@ -40,21 +40,25 @@ import supybot.world as world __version__ = "2020.02.24+git" # XXX Replace this with an appropriate author or supybot.Author instance. -__author__ = supybot.Author('reticulatingspline', 'spline', 'spline') -__maintainer__ = getattr(supybot.authors, 'oddluck', - supybot.Author('oddluck', 'oddluck', 'oddluck@riseup.net')) +__author__ = supybot.Author("reticulatingspline", "spline", "spline") +__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 -reload(plugin) # In case we're being reloaded. + +reload(plugin) # In case we're being reloaded. reload(config) # 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! diff --git a/Tweety/config.py b/Tweety/config.py index 3874da5..54ef664 100644 --- a/Tweety/config.py +++ b/Tweety/config.py @@ -31,30 +31,103 @@ import supybot.conf as conf import supybot.registry as registry + def configure(advanced): # This will be called by supybot to configure this module. advanced is # a bool that specifies whether the user identified himself 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('Tweety', True) + + conf.registerPlugin("Tweety", True) -Tweety = conf.registerPlugin('Tweety') -conf.registerGlobalValue(Tweety,'consumerKey',registry.String('', """The consumer key of the application.""")) -conf.registerGlobalValue(Tweety,'consumerSecret',registry.String('', """The consumer secret of the application.""", private=True)) -conf.registerGlobalValue(Tweety,'accessKey',registry.String('', """The Twitter Access Token key for the bot's account""")) -conf.registerGlobalValue(Tweety,'accessSecret',registry.String('', """The Twitter Access Token secret for the bot's account""", private=True)) -conf.registerChannelValue(Tweety,'hideRealName',registry.Boolean(False, """Do not show real name when displaying tweets.""")) -conf.registerChannelValue(Tweety,'addShortUrl',registry.Boolean(False, """Whether or not to add a short URL to the tweets.""")) -conf.registerChannelValue(Tweety,'woeid',registry.Integer(1, """Where On Earth ID. World Wide is 1. USA is 23424977.""")) -conf.registerChannelValue(Tweety,'defaultSearchResults',registry.Integer(3, """Default number of results to return on searches.""")) -conf.registerChannelValue(Tweety,'maxSearchResults',registry.Integer(10, """Maximum number of results to return on searches""")) -conf.registerChannelValue(Tweety,'defaultResults',registry.Integer(1, """Default number of results to return on timelines.""")) -conf.registerChannelValue(Tweety,'maxResults',registry.Integer(10, """Maximum number of results to return on timelines.""")) -conf.registerChannelValue(Tweety,'outputColorTweets',registry.Boolean(False, """When outputting Tweets, display them with some color.""")) -conf.registerChannelValue(Tweety,'hideHashtagsTrends',registry.Boolean(False, """When displaying trends, should we display #hashtags? Default is no.""")) -conf.registerChannelValue(Tweety,'requireVoiceOrAbove',registry.Boolean(False, """Only allows a user with voice or above on a channel to use commands.""")) -conf.registerChannelValue(Tweety,'colorTweetURLs',registry.Boolean(False, """Try and color URLs (red) in Tweets?""")) +Tweety = conf.registerPlugin("Tweety") +conf.registerGlobalValue( + Tweety, + "consumerKey", + registry.String("", """The consumer key of the application.""", private=True), +) +conf.registerGlobalValue( + Tweety, + "consumerSecret", + registry.String("", """The consumer secret of the application.""", private=True), +) +conf.registerGlobalValue( + Tweety, + "accessKey", + registry.String( + "", """The Twitter Access Token key for the bot's account""", private=True + ), +) +conf.registerGlobalValue( + Tweety, + "accessSecret", + registry.String( + "", """The Twitter Access Token secret for the bot's account""", private=True + ), +) +conf.registerChannelValue( + Tweety, + "hideRealName", + registry.Boolean(False, """Do not show real name when displaying tweets."""), +) +conf.registerChannelValue( + Tweety, + "addShortUrl", + registry.Boolean(False, """Whether or not to add a short URL to the tweets."""), +) +conf.registerChannelValue( + Tweety, + "woeid", + registry.Integer(1, """Where On Earth ID. World Wide is 1. USA is 23424977."""), +) +conf.registerChannelValue( + Tweety, + "defaultSearchResults", + registry.Integer(3, """Default number of results to return on searches."""), +) +conf.registerChannelValue( + Tweety, + "maxSearchResults", + registry.Integer(10, """Maximum number of results to return on searches"""), +) +conf.registerChannelValue( + Tweety, + "defaultResults", + registry.Integer(1, """Default number of results to return on timelines."""), +) +conf.registerChannelValue( + Tweety, + "maxResults", + registry.Integer(10, """Maximum number of results to return on timelines."""), +) +conf.registerChannelValue( + Tweety, + "outputColorTweets", + registry.Boolean( + False, """When outputting Tweets, display them with some color.""" + ), +) +conf.registerChannelValue( + Tweety, + "hideHashtagsTrends", + registry.Boolean( + False, """When displaying trends, should we display #hashtags? Default is no.""" + ), +) +conf.registerChannelValue( + Tweety, + "requireVoiceOrAbove", + registry.Boolean( + False, + """Only allows a user with voice or above on a channel to use commands.""", + ), +) +conf.registerChannelValue( + Tweety, + "colorTweetURLs", + registry.Boolean(False, """Try and color URLs (red) in Tweets?"""), +) # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=250: diff --git a/Tweety/plugin.py b/Tweety/plugin.py index 344133e..73eca6d 100644 --- a/Tweety/plugin.py +++ b/Tweety/plugin.py @@ -33,12 +33,15 @@ import json import requests from requests_oauthlib import OAuth1 import os + # libraries for time_created_at import time from datetime import datetime + # for unescape import re import html.entities + # supybot libs import supybot.utils as utils from supybot.commands import * @@ -49,6 +52,7 @@ import supybot.conf as conf import supybot.world as world import supybot.log as log + class OAuthApi: """OAuth class to work with Twitter v1.1 API.""" @@ -61,22 +65,44 @@ class OAuthApi: if parameters: extra_params.update(parameters) try: - r = requests.get("https://api.twitter.com/1.1/" + call + ".json", params=extra_params, auth=self.auth, timeout=10) + r = requests.get( + "https://api.twitter.com/1.1/" + call + ".json", + params=extra_params, + auth=self.auth, + timeout=10, + ) r.raise_for_status() - except (requests.exceptions.RequestException, requests.exceptions.HTTPError) as e: - log.info('Tweety: error connecting to Twitter API (Retrying...): {0}'.format(e)) + except ( + requests.exceptions.RequestException, + requests.exceptions.HTTPError, + ) as e: + log.info( + "Tweety: error connecting to Twitter API (Retrying...): {0}".format(e) + ) try: - r = requests.get("https://api.twitter.com/1.1/" + call + ".json", params=extra_params, auth=self.auth, timeout=10) + r = requests.get( + "https://api.twitter.com/1.1/" + call + ".json", + params=extra_params, + auth=self.auth, + timeout=10, + ) r.raise_for_status() - except (requests.exceptions.RequestException, requests.exceptions.HTTPError) as e: - log.info('Tweety: error connecting to Twitter API on retry: {0}'.format(e)) + except ( + requests.exceptions.RequestException, + requests.exceptions.HTTPError, + ) as e: + log.info( + "Tweety: error connecting to Twitter API on retry: {0}".format(e) + ) else: return r.content else: return r.content + class Tweety(callbacks.Plugin): """Public Twitter class for working with the API.""" + threaded = True def __init__(self, irc): @@ -95,7 +121,7 @@ class Tweety(callbacks.Plugin): world.flushers.append(self._flush_db) def _flush_db(self): - with open(self.data_file, 'w') as f: + with open(self.data_file, "w") as f: json.dump(self.since_id, f) def die(self): @@ -103,29 +129,17 @@ class Tweety(callbacks.Plugin): self._flush_db() super().die() - def _httpget(self, url, h=None, d=None, l=False): - """General HTTP resource fetcher. Pass headers via h, data via d, and to log via l.""" - try: - if h and d: - page = utils.web.getUrl(url, headers=h, data=d) - else: - h = {"User-Agent":"Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:17.0) Gecko/20100101 Firefox/17.0"} - page = utils.web.getUrl(url, headers=h) - try: - page = page.decode() - except: - page = page.decode('iso-8859-1') - return page - except utils.web.Error as e: - log.error("Tweety: ERROR opening {0} message: {1}".format(url, e)) - return None - def _shortenUrl(self, url): """Shortens a long URL into a short one.""" try: - data = requests.get('http://tinyurl.com/api-create.php?url={0}'.format(url), timeout=5) - except (requests.exceptions.RequestException, requests.exceptions.HTTPError) as e: - log.error('Tweety: ERROR retrieving tiny url: {0}'.format(e)) + data = requests.get( + "http://tinyurl.com/api-create.php?url={0}".format(url), timeout=5 + ) + except ( + requests.exceptions.RequestException, + requests.exceptions.HTTPError, + ) as e: + log.error("Tweety: ERROR retrieving tiny url: {0}".format(e)) return else: return data.content.decode() @@ -134,26 +148,50 @@ class Tweety(callbacks.Plugin): """ Check if we have our keys and can auth.""" if not self.twitterApi: # if not set, try and auth. failTest = False # first check that we have all 4 keys. - for checkKey in ('consumerKey', 'consumerSecret', 'accessKey', 'accessSecret'): + for checkKey in ( + "consumerKey", + "consumerSecret", + "accessKey", + "accessSecret", + ): try: # try to see if each key is set. testKey = self.registryValue(checkKey) except: # a key is not set, break and error. - log.error("Tweety: ERROR checking keys. We're missing the config value for: {0}. Please set this and try again.".format(checkKey)) + log.error( + "Tweety: ERROR checking keys. We're missing the config value " + "for: {0}. Please set this and try again.".format(checkKey) + ) failTest = True break # if any missing, throw an error and keep twitterApi=False if failTest: - log.error('Tweety: ERROR getting keys. You must set all 4 keys in config variables and reload plugin.') + log.error( + "Tweety: ERROR getting keys. You must set all 4 keys in config " + "variables and reload plugin." + ) return False - # We have all 4 keys. Now lets see if they are valid by calling verify_credentials in the API. + # We have all 4 keys. Check validity by calling verify_credentials in the API. self.log.info("Got all 4 keys. Now trying to auth up with Twitter.") - twitterApi = OAuthApi(self.registryValue('consumerKey'), self.registryValue('consumerSecret'), self.registryValue('accessKey'), self.registryValue('accessSecret')) - data = twitterApi.ApiCall('account/verify_credentials') - # check the response. if we can load json, it means we're authenticated. else, return response. + twitterApi = OAuthApi( + self.registryValue("consumerKey"), + self.registryValue("consumerSecret"), + self.registryValue("accessKey"), + self.registryValue("accessSecret"), + ) + data = twitterApi.ApiCall("account/verify_credentials") + # check the response. if we can load json, it means we're authenticated. try: # if we pass, response is validated. set self.twitterApi w/object. json.loads(data) - self.log.info("I have successfully authorized and logged in to Twitter using your credentials.") - self.twitterApi = OAuthApi(self.registryValue('consumerKey'), self.registryValue('consumerSecret'), self.registryValue('accessKey'), self.registryValue('accessSecret')) + self.log.info( + "I have successfully authorized and logged in to Twitter using " + "your credentials." + ) + self.twitterApi = OAuthApi( + self.registryValue("consumerKey"), + self.registryValue("consumerSecret"), + self.registryValue("accessKey"), + self.registryValue("accessSecret"), + ) except: # response failed. Return what we got back. log.error("Tweety: ERROR. I could not log in using your credentials.") return False @@ -166,11 +204,11 @@ class Tweety(callbacks.Plugin): def _red(self, string): """Returns a red string.""" - return ircutils.mircColor(string, 'red') + return ircutils.mircColor(string, "red") def _blue(self, string): """Returns a blue string.""" - return ircutils.mircColor(string, 'blue') + return ircutils.mircColor(string, "blue") def _bold(self, string): """Returns a bold string.""" @@ -191,7 +229,7 @@ class Tweety(callbacks.Plugin): def _unescape(self, text): """Created by Fredrik Lundh (http://effbot.org/zone/re-sub.htm#unescape-html)""" # quick dump \n and \r, usually coming from bots that autopost html. - text = text.replace('\n', ' ').replace('\r', ' ') + text = text.replace("\n", " ").replace("\r", " ") # now the actual unescape. def fixup(m): text = m.group(0) @@ -210,8 +248,9 @@ class Tweety(callbacks.Plugin): text = chr(html.entities.name2codepoint[text[1:-1]]) except KeyError: pass - return text # leave as is - return re.sub("&#?\w+;", fixup, text) + return text # leave as is + + return re.sub(r"&#?\w+;", fixup, text) def _time_created_at(self, s): """ @@ -230,41 +269,52 @@ class Tweety(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 - def _outputTweet(self, irc, msg, nick, name, verified, text, time, tweetid, retweetid): + def _outputTweet( + self, irc, msg, nick, name, verified, text, time, tweetid, retweetid + ): """ Constructs string to output for Tweet. Used for tsearch and twitter. """ url = url2 = None # build output string. - if self.registryValue('outputColorTweets', msg.args[0]): + if self.registryValue("outputColorTweets", msg.args[0]): ret = "@{0}".format(self._ul(self._blue(nick))) else: # bold otherwise. ret = "@{0}".format(self._bu(nick)) if verified: - string = self._bold(ircutils.mircColor("✓", 'white', 'blue')) + string = self._bold(ircutils.mircColor("✓", "white", "blue")) ret += "{}".format(string) # show real name in tweet output? - if not self.registryValue('hideRealName', msg.args[0]): + if not self.registryValue("hideRealName", msg.args[0]): ret += " ({0})".format(name) # short url the link to the tweet? - if self.registryValue('addShortUrl', msg.args[0]): - url = self._shortenUrl("https://twitter.com/{0}/status/{1}".format(nick, tweetid)) + if self.registryValue("addShortUrl", msg.args[0]): + url = self._shortenUrl( + "https://twitter.com/{0}/status/{1}".format(nick, tweetid) + ) if url: text += " {0}".format(url) if retweetid and retweetid != tweetid: - url2 = self._shortenUrl("https://twitter.com/{0}/status/{1}".format(nick, retweetid)) + url2 = self._shortenUrl( + "https://twitter.com/{0}/status/{1}".format(nick, retweetid) + ) if url2: text += " {0}".format(url2) # add in the end with the text + tape. - if self.registryValue('colorTweetURLs', msg.args[0]): # color urls. - text = re.sub(r'(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)', self._red(r'\1'), text) + if self.registryValue("colorTweetURLs", msg.args[0]): # color urls. + text = re.sub( + r"(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F]" + r"[0-9a-fA-F]))+)", + self._red(r"\1"), + text, + ) ret += ": {0} ({1})".format(text, self._bold(time)) else: # only bold time. no text color. ret += ": {0} ({1})".format(text, self._bold(time)) @@ -275,19 +325,22 @@ class Tweety(callbacks.Plugin): """ Use Yahoo's API to look-up a WOEID. """ - data = self.twitterApi.ApiCall('trends/available') + data = self.twitterApi.ApiCall("trends/available") if not data: - log.error('Tweety: ERROR retrieving data from Trends API') + log.error("Tweety: ERROR retrieving data from Trends API") return try: data = json.loads(data) except: data = None - log.error('Tweety: ERROR retrieving data from Trends API') + log.error("Tweety: ERROR retrieving data from Trends API") if not data: log.info("Tweety: No location results for {0}".format(lookup)) return - return next((item["woeid"] for item in data if lookup.lower() in item["name"].lower()), None) + return next( + (item["woeid"] for item in data if lookup.lower() in item["name"].lower()), + None, + ) #################### # PUBLIC FUNCTIONS # @@ -302,8 +355,11 @@ class Tweety(callbacks.Plugin): if woeid: irc.reply("WOEID: {0} for '{1}'".format(self._bold(woeid), lookup)) else: - irc.reply("ERROR: Something broke trying to find a WOEID for '{0}'".format(lookup)) - woeidlookup = wrap(woeidlookup, ['text']) + irc.reply( + "ERROR: Something broke trying to find a WOEID for '{0}'".format(lookup) + ) + + woeidlookup = wrap(woeidlookup, ["text"]) def ratelimits(self, irc, msg, args): """ @@ -311,96 +367,135 @@ class Tweety(callbacks.Plugin): """ # before we do anything, make sure we have a twitterApi object. if not self.twitterApi: - irc.reply("ERROR: Twitter is not authorized. Please check logs before running this command.") + irc.reply( + "ERROR: Twitter is not authorized. Please check logs before running " + "this command." + ) return # make API call. - data = self.twitterApi.ApiCall('application/rate_limit_status', parameters={'resources':'trends,search,statuses,users'}) + data = self.twitterApi.ApiCall( + "application/rate_limit_status", + parameters={"resources": "trends,search,statuses,users"}, + ) try: data = json.loads(data) except: irc.reply("ERROR: Failed to lookup ratelimit data: {0}".format(data)) return # parse data; - data = data.get('resources') + data = data.get("resources") if not data: # simple check if we have part of the json dict. - irc.reply("ERROR: Failed to fetch application rate limit status. Something could be wrong with Twitter.") + irc.reply( + "ERROR: Failed to fetch application rate limit status. Something could " + "be wrong with Twitter." + ) log.error("Tweety: ERROR fetching rate limit data: {0}".format(data)) return - # dict of resources we want and how to parse. key=human name, values are for the json dict. - resources = {'trends':['trends', '/trends/place'], - 'tsearch':['search', '/search/tweets'], - 'twitter --id':['statuses', '/statuses/show/:id'], - 'twitter --info':['users', '/users/show/:id'], - 'twitter timeline':['statuses', '/statuses/user_timeline'] } + # dict of resources and how to parse. key=name, values are for the json dict. + resources = { + "trends": ["trends", "/trends/place"], + "tsearch": ["search", "/search/tweets"], + "twitter --id": ["statuses", "/statuses/show/:id"], + "twitter --info": ["users", "/users/show/:id"], + "twitter timeline": ["statuses", "/statuses/user_timeline"], + } # now iterate through dict above. for resource in resources: rdict = resources[resource] # get value. endpoint = data.get(rdict[0]).get(rdict[1]) # value[0], value[1] - minutes = "%sm%ss" % divmod(int(endpoint['reset'])-int(time.time()), 60) # math. - output = "Reset in: {0} Remaining: {1}".format(minutes, endpoint['remaining']) + minutes = "%sm%ss" % divmod( + int(endpoint["reset"]) - int(time.time()), 60 + ) # math. + output = "Reset in: {0} Remaining: {1}".format( + minutes, endpoint["remaining"] + ) irc.reply("{0} :: {1}".format(self._bold(resource), output)) ratelimits = wrap(ratelimits) def trends(self, irc, msg, args, getopts, optwoeid): """[--exclude] [location] - Returns the top Twitter trends for a specific location. Use optional argument location for trends. - Defaults to worldwide and can be set via config variable. + Returns the top Twitter trends for a specific location. Use optional argument + location for trends. Defaults to worldwide and can be set via config variable. Use --exclude to not include #hashtags in trends data. Ex: Boston or --exclude London """ # enforce +voice or above to use command? - if self.registryValue('requireVoiceOrAbove', msg.args[0]): # should we check? + if self.registryValue("requireVoiceOrAbove", msg.args[0]): # should we check? if ircutils.isChannel(msg.args[0]): # are we in a channel? - if not irc.state.channels[msg.args[0]].isVoicePlus(msg.nick): # are they + or @? - irc.error("ERROR: You have to be at least voiced to use the trends command in {0}.".format(msg.args[0])) + if not irc.state.channels[msg.args[0]].isVoicePlus( + msg.nick + ): # are they + or @? + irc.error( + "ERROR: You have to be at least voiced to use the trends " + "command in {0}.".format(msg.args[0]) + ) return # before we do anything, make sure we have a twitterApi object. if not self.twitterApi: - irc.reply("ERROR: Twitter is not authorized. Please check logs before running this command.") + irc.reply( + "ERROR: Twitter is not authorized. Please check logs before running " + "this command." + ) return # default arguments. - args = {'id': self.registryValue('woeid', msg.args[0]), - 'exclude': self.registryValue('hideHashtagsTrends', msg.args[0])} + args = { + "id": self.registryValue("woeid", msg.args[0]), + "exclude": self.registryValue("hideHashtagsTrends", msg.args[0]), + } # handle input. if getopts: for (key, value) in getopts: - if key == 'exclude': # remove hashtags from trends. - args['exclude'] = 'hashtags' + if key == "exclude": # remove hashtags from trends. + args["exclude"] = "hashtags" # work with woeid. 1 is world, the default. can be set via input or via config. if optwoeid: # if we have an input location, lookup the woeid. - if optwoeid.lower().startswith('world'): # looking for worldwide or some variation. (bypass) - args['id'] = 1 # "World Wide" is worldwide (odd bug) = 1. + if optwoeid.lower().startswith( + "world" + ): # looking for worldwide or some variation. (bypass) + args["id"] = 1 # "World Wide" is worldwide (odd bug) = 1. elif optwoeid.strip().isdigit(): - args['id'] = optwoeid.strip() + args["id"] = optwoeid.strip() else: # looking for something else. woeid = self._woeid_lookup(optwoeid) # yahoo search for woeid. - if woeid: # if we get a returned value, set it. otherwise default value. - args['id'] = woeid + if ( + woeid + ): # if we get a returned value, set it. otherwise default value. + args["id"] = woeid else: # location not found. - irc.reply("ERROR: I could not lookup location: {0}. Try a different location.".format(optwoeid)) + irc.reply( + "ERROR: I could not lookup location: {0}. Try a different " + "location.".format(optwoeid) + ) return # now build our API call - data = self.twitterApi.ApiCall('trends/place', parameters=args) + data = self.twitterApi.ApiCall("trends/place", parameters=args) try: data = json.loads(data) except: irc.reply("ERROR: failed to lookup trends on Twitter: {0}".format(data)) return # now, before processing, check for errors: - if 'errors' in data: - if data['errors'][0]['code'] == 34: # 34 means location not found. + if "errors" in data: + if data["errors"][0]["code"] == 34: # 34 means location not found. irc.reply("ERROR: I do not have any trends for: {0}".format(optwoeid)) return else: # just return the message. - errmsg = data['errors'][0] - irc.reply("ERROR: Could not load trends. ({0} {1})".format(errmsg['code'], errmsg['message'])) + errmsg = data["errors"][0] + irc.reply( + "ERROR: Could not load trends. ({0} {1})".format( + errmsg["code"], errmsg["message"] + ) + ) return # if no error here, we found trends. prepare string and output. - location = data[0]['locations'][0]['name'] - ttrends = " | ".join([trend['name'] for trend in data[0]['trends']]) - irc.reply("Top Twitter Trends in {0} :: {1}".format(self._bold(location), ttrends)) - trends = wrap(trends, [getopts({'exclude':''}), optional('text')]) + location = data[0]["locations"][0]["name"] + ttrends = " | ".join([trend["name"] for trend in data[0]["trends"]]) + irc.reply( + "Top Twitter Trends in {0} :: {1}".format(self._bold(location), ttrends) + ) + + trends = wrap(trends, [getopts({"exclude": ""}), optional("text")]) def tsearch(self, irc, msg, args, optlist, optterm): """[--num number] [--searchtype mixed|recent|popular] [--lang xx] [--new] @@ -411,79 +506,126 @@ class Tweety(callbacks.Plugin): Ex: --num 3 breaking news """ # enforce +voice or above to use command? - if self.registryValue('requireVoiceOrAbove', msg.args[0]): # should we check? + if self.registryValue("requireVoiceOrAbove", msg.args[0]): # should we check? if ircutils.isChannel(msg.args[0]): # are we in a channel? - if not irc.state.channels[msg.args[0]].isVoicePlus(msg.nick): # are they + or @? - irc.error("ERROR: You have to be at least voiced to use the tsearch command in {0}.".format(msg.args[0])) + if not irc.state.channels[msg.args[0]].isVoicePlus( + msg.nick + ): # are they + or @? + irc.error( + "ERROR: You have to be at least voiced to use the tsearch " + "command in {0}.".format(msg.args[0]) + ) return # before we do anything, make sure we have a twitterApi object. if not self.twitterApi: - irc.reply("ERROR: Twitter is not authorized. Please check logs before running this command.") + irc.reply( + "ERROR: Twitter is not authorized. Please check logs before running " + "this command." + ) return self.since_id.setdefault(msg.channel, {}) - self.since_id[msg.channel].setdefault('{0}'.format(optterm), None) + self.since_id[msg.channel].setdefault("{0}".format(optterm), None) new = False # default arguments. - tsearchArgs = {'include_entities':'false', - 'tweet_mode': 'extended', - 'count': self.registryValue('defaultSearchResults', msg.args[0]), - 'lang':'en', - 'q':utils.web.urlquote(optterm)} + tsearchArgs = { + "include_entities": "false", + "tweet_mode": "extended", + "count": self.registryValue("defaultSearchResults", msg.args[0]), + "lang": "en", + "q": utils.web.urlquote(optterm), + } # check input. if optlist: for (key, value) in optlist: - if key == 'num': # --num - maxresults = self.registryValue('maxSearchResults', msg.args[0]) - if not (1 <= value <= maxresults): # make sure it's between what we should output. - irc.reply("ERROR: '{0}' is not a valid number of tweets. Range is between 1 and {1}.".format(value, maxresults)) + if key == "num": # --num + maxresults = self.registryValue("maxSearchResults", msg.args[0]) + if not ( + 1 <= value <= maxresults + ): # make sure it's between what we should output. + irc.reply( + "ERROR: '{0}' is not a valid number of tweets. Range is " + "between 1 and {1}.".format(value, maxresults) + ) return else: # change number to output. - tsearchArgs['count'] = value - if key == 'searchtype': # getopts limits us here. - tsearchArgs['result_type'] = value # limited by getopts to valid values. - if key == 'lang': # lang . Uses ISO-639 codes like 'en' http://en.wikipedia.org/wiki/ISO_639-1 - tsearchArgs['lang'] = value - if key == 'new' and self.since_id[msg.channel]['{0}'.format(optterm)]: + tsearchArgs["count"] = value + if key == "searchtype": # getopts limits us here. + tsearchArgs[ + "result_type" + ] = value # limited by getopts to valid values. + if key == "lang": # lang . Uses ISO-639 codes like 'en' + tsearchArgs["lang"] = value + if key == "new" and self.since_id[msg.channel]["{0}".format(optterm)]: new = True - tsearchArgs['since_id'] = self.since_id[msg.channel]['{0}'.format(optterm)] + tsearchArgs["since_id"] = self.since_id[msg.channel][ + "{0}".format(optterm) + ] # now build our API call. - data = self.twitterApi.ApiCall('search/tweets', parameters=tsearchArgs) + data = self.twitterApi.ApiCall("search/tweets", parameters=tsearchArgs) if not data: if not new: - irc.reply("ERROR: Something went wrong trying to search Twitter. ({0})".format(data)) + irc.reply( + "ERROR: Something went wrong trying to search Twitter. ({0})".format( + data + ) + ) log.error("Tweety: ERROR trying to search Twitter: {0}".format(data)) return try: data = json.loads(data) except: if not new: - irc.reply("ERROR: Something went wrong trying to search Twitter. ({0})".format(data)) + irc.reply( + "ERROR: Something went wrong trying to search Twitter. ({0})".format( + data + ) + ) log.error("Tweety: ERROR trying to search Twitter: {0}".format(data)) return # check the return data. - results = data.get('statuses') # data returned as a dict. + results = data.get("statuses") # data returned as a dict. if not results or len(results) == 0: # found nothing or length 0. if not new: - irc.reply("ERROR: No Twitter Search results found for '{0}'".format(optterm)) - log.info("Tweety: No Twitter Search results found for '{0}': {1}".format(optterm, data)) + irc.reply( + "ERROR: No Twitter Search results found for '{0}'".format(optterm) + ) + log.info( + "Tweety: No Twitter Search results found for '{0}': {1}".format( + optterm, data + ) + ) return else: # we found something. - self.since_id[msg.channel]['{0}'.format(optterm)] = results[0].get('id') - for result in results[0:int(tsearchArgs['count'])]: # iterate over each. - nick = self._unescape(result['user'].get('screen_name')) - name = self._unescape(result["user"].get('name')) - verified = result['user'].get('verified') - text = self._unescape(result.get('full_text')) or self._unescape(result.get('text')) - date = self._time_created_at(result.get('created_at')) - tweetid = result.get('id_str') + self.since_id[msg.channel]["{0}".format(optterm)] = results[0].get("id") + for result in results[0 : int(tsearchArgs["count"])]: # iterate over each. + nick = self._unescape(result["user"].get("screen_name")) + name = self._unescape(result["user"].get("name")) + verified = result["user"].get("verified") + text = self._unescape(result.get("full_text")) or self._unescape( + result.get("text") + ) + date = self._time_created_at(result.get("created_at")) + tweetid = result.get("id_str") # build output string and output. - output = self._outputTweet(irc, msg, nick, name, verified, text, date, tweetid, None) + output = self._outputTweet( + irc, msg, nick, name, verified, text, date, tweetid, None + ) irc.reply(output) - tsearch = wrap(tsearch, [getopts({'num':('int'), - 'searchtype':('literal', ('popular', 'mixed', 'recent')), - 'lang':('somethingWithoutSpaces'), - 'new':''}), - ('text')]) + + tsearch = wrap( + tsearch, + [ + getopts( + { + "num": ("int"), + "searchtype": ("literal", ("popular", "mixed", "recent")), + "lang": ("somethingWithoutSpaces"), + "new": "", + } + ), + ("text"), + ], + ) def twitter(self, irc, msg, args, optlist, optnick): """[--noreply] [--nort] [--num <##>] [--info] [--new] [--id ] @@ -495,150 +637,202 @@ class Tweety(callbacks.Plugin): Ex: --info CNN | --id 337197009729622016 | --num 3 CNN """ self.since_id.setdefault(msg.channel, {}) - self.since_id[msg.channel].setdefault('{0}'.format(optnick), None) + self.since_id[msg.channel].setdefault("{0}".format(optnick), None) # enforce +voice or above to use command? - if self.registryValue('requireVoiceOrAbove', msg.args[0]): # should we check? + if self.registryValue("requireVoiceOrAbove", msg.args[0]): # should we check? if ircutils.isChannel(msg.args[0]): # are we in a channel? - if not irc.state.channels[msg.args[0]].isVoicePlus(msg.nick): # are they + or @? - irc.error("ERROR: You have to be at least voiced to use the twitter command in {0}.".format(msg.args[0])) + if not irc.state.channels[msg.args[0]].isVoicePlus( + msg.nick + ): # are they + or @? + irc.error( + "ERROR: You have to be at least voiced to use the twitter " + "command in {0}.".format(msg.args[0]) + ) return # before we do anything, make sure we have a twitterApi object. if not self.twitterApi: - irc.reply("ERROR: Twitter is not authorized. Please check logs before running this command.") + irc.reply( + "ERROR: Twitter is not authorized. Please check logs before running " + "this command." + ) return # now begin - optnick = optnick.replace('@','') # strip @ from input if given. + optnick = optnick.replace("@", "") # strip @ from input if given. # default options. - args = {'id': False, - 'nort': False, - 'noreply': False, - 'url': False, - 'new': False, - 'num': self.registryValue('defaultResults', msg.args[0]), - 'info': False} + args = { + "id": False, + "nort": False, + "noreply": False, + "url": False, + "new": False, + "num": self.registryValue("defaultResults", msg.args[0]), + "info": False, + } # handle input optlist. if optlist: for (key, value) in optlist: - if key == 'id': - args['id'] = True - if key == 'url': - args['url'] = True - if key == 'nort': - args['nort'] = True - if key == 'noreply': - args['noreply'] = True - if key == 'new': - args['new'] = True - if key == 'num': - maxresults = self.registryValue('maxResults', msg.args[0]) - if not (1 <= value <= maxresults): # make sure it's between what we should output. - irc.reply("ERROR: '{0}' is not a valid number of tweets. Range is between 1 and {1}.".format(value, maxresults)) + if key == "id": + args["id"] = True + if key == "url": + args["url"] = True + if key == "nort": + args["nort"] = True + if key == "noreply": + args["noreply"] = True + if key == "new": + args["new"] = True + if key == "num": + maxresults = self.registryValue("maxResults", msg.args[0]) + if not ( + 1 <= value <= maxresults + ): # make sure it's between what we should output. + irc.reply( + "ERROR: '{0}' is not a valid number of tweets. Range is " + "between 1 and {1}.".format(value, maxresults) + ) return else: # number is valid so return this. - args['num'] = value - if key == 'info': - args['info'] = True + args["num"] = value + if key == "info": + args["info"] = True # handle the four different rest api endpoint urls + twitterArgs dict for options. - if args['id']: # -id #. - apiUrl = 'statuses/show' - twitterArgs = {'id': optnick, 'include_entities':'false', 'tweet_mode': 'extended'} - elif args['info']: # --info. - apiUrl = 'users/show' - twitterArgs = {'screen_name': optnick, 'include_entities':'false'} - elif args['new']: # --new. - apiUrl = 'statuses/user_timeline' - if self.since_id[msg.channel]['{0}'.format(optnick)]: - twitterArgs = {'screen_name': optnick, 'since_id':self.since_id[msg.channel]['{0}'.format(optnick)], 'count': args['num'], 'tweet_mode': 'extended'} - if args['nort']: # show retweets? - twitterArgs['include_rts'] = 'false' + if args["id"]: # -id #. + apiUrl = "statuses/show" + twitterArgs = { + "id": optnick, + "include_entities": "false", + "tweet_mode": "extended", + } + elif args["info"]: # --info. + apiUrl = "users/show" + twitterArgs = {"screen_name": optnick, "include_entities": "false"} + elif args["new"]: # --new. + apiUrl = "statuses/user_timeline" + if self.since_id[msg.channel]["{0}".format(optnick)]: + twitterArgs = { + "screen_name": optnick, + "since_id": self.since_id[msg.channel]["{0}".format(optnick)], + "count": args["num"], + "tweet_mode": "extended", + } + if args["nort"]: # show retweets? + twitterArgs["include_rts"] = "false" else: # default is to show retweets. - twitterArgs['include_rts'] = 'true' - if args['noreply']: # show replies? - twitterArgs['exclude_replies'] = 'true' + twitterArgs["include_rts"] = "true" + if args["noreply"]: # show replies? + twitterArgs["exclude_replies"] = "true" else: # default is to NOT exclude replies. - twitterArgs['exclude_replies'] = 'false' + twitterArgs["exclude_replies"] = "false" else: - twitterArgs = {'screen_name': optnick, 'count': args['num'], 'tweet_mode': 'extended'} - if args['nort']: # show retweets? - twitterArgs['include_rts'] = 'false' + twitterArgs = { + "screen_name": optnick, + "count": args["num"], + "tweet_mode": "extended", + } + if args["nort"]: # show retweets? + twitterArgs["include_rts"] = "false" else: # default is to show retweets. - twitterArgs['include_rts'] = 'true' - if args['noreply']: # show replies? - twitterArgs['exclude_replies'] = 'true' + twitterArgs["include_rts"] = "true" + if args["noreply"]: # show replies? + twitterArgs["exclude_replies"] = "true" else: # default is to NOT exclude replies. - twitterArgs['exclude_replies'] = 'false' + twitterArgs["exclude_replies"] = "false" else: # if not an --id --info, or --new we're printing from their timeline. - apiUrl = 'statuses/user_timeline' - twitterArgs = {'screen_name': optnick, 'count': args['num'], 'tweet_mode': 'extended'} - if args['nort']: # show retweets? - twitterArgs['include_rts'] = 'false' + apiUrl = "statuses/user_timeline" + twitterArgs = { + "screen_name": optnick, + "count": args["num"], + "tweet_mode": "extended", + } + if args["nort"]: # show retweets? + twitterArgs["include_rts"] = "false" else: # default is to show retweets. - twitterArgs['include_rts'] = 'true' - if args['noreply']: # show replies? - twitterArgs['exclude_replies'] = 'true' + twitterArgs["include_rts"] = "true" + if args["noreply"]: # show replies? + twitterArgs["exclude_replies"] = "true" else: # default is to NOT exclude replies. - twitterArgs['exclude_replies'] = 'false' + twitterArgs["exclude_replies"] = "false" # call the Twitter API with our data. data = self.twitterApi.ApiCall(apiUrl, parameters=twitterArgs) if not data: - if not args['new']: - irc.reply("ERROR: Failed to lookup Twitter for '{0}' ({1})".format(optnick, data)) - log.error:("Tweety: ERROR looking up Twitter for '{0}': {1}".format(optnick, data)) + if not args["new"]: + irc.reply( + "ERROR: Failed to lookup Twitter for '{0}' ({1})".format( + optnick, data + ) + ) + log.error: ( + "Tweety: ERROR looking up Twitter for '{0}': {1}".format(optnick, data) + ) return try: data = json.loads(data) except: - if not args['new']: - irc.reply("ERROR: Failed to lookup Twitter for '{0}' ({1})".format(optnick, data)) - log.error:("Tweety: ERROR looking up Twitter for '{0}': {1}".format(optnick, data)) + if not args["new"]: + irc.reply( + "ERROR: Failed to lookup Twitter for '{0}' ({1})".format( + optnick, data + ) + ) + log.error: ( + "Tweety: ERROR looking up Twitter for '{0}': {1}".format(optnick, data) + ) return # before anything, check for errors. errmsg is conditional. - if 'errors' in data: - if data['errors'][0]['code'] == 34: # not found. - if args['id']: # --id #. # is not found. + if "errors" in data: + if data["errors"][0]["code"] == 34: # not found. + if args["id"]: # --id #. # is not found. errmsg = "ERROR: Tweet ID '{0}' not found.".format(optnick) else: # --info or twitter not found. errmsg = "ERROR: Twitter user '{0}' not found.".format(optnick) irc.reply(errmsg) # print the error and exit. return else: # errmsg is not 34. just return it. - errmsg = data['errors'][0] - if not args['new']: - irc.reply("ERROR: {0} {1}".format(errmsg['code'], errmsg['message'])) - log.error("Tweety: ERROR: {0}: {1}".format(errmsg['code'], errmsg['message'])) + errmsg = data["errors"][0] + if not args["new"]: + irc.reply( + "ERROR: {0} {1}".format(errmsg["code"], errmsg["message"]) + ) + log.error( + "Tweety: ERROR: {0}: {1}".format(errmsg["code"], errmsg["message"]) + ) return # no errors, so we process data conditionally. - if args['id']: # If --id was given for a single tweet. - text = self._unescape(data.get('full_text')) or self._unescape(data.get('text')) - nick = self._unescape(data["user"].get('screen_name')) - name = self._unescape(data["user"].get('name')) - verified = data["user"].get('verified') - relativeTime = self._time_created_at(data.get('created_at')) - tweetid = data.get('id') - if data.get('retweeted_status'): - retweetid = data['retweeted_status'].get('id') + if args["id"]: # If --id was given for a single tweet. + text = self._unescape(data.get("full_text")) or self._unescape( + data.get("text") + ) + nick = self._unescape(data["user"].get("screen_name")) + name = self._unescape(data["user"].get("name")) + verified = data["user"].get("verified") + relativeTime = self._time_created_at(data.get("created_at")) + tweetid = data.get("id") + if data.get("retweeted_status"): + retweetid = data["retweeted_status"].get("id") else: retweetid = None # prepare string to output and send to irc. - output = self._outputTweet(irc, msg, nick, name, verified, text, relativeTime, tweetid, retweetid) + output = self._outputTweet( + irc, msg, nick, name, verified, text, relativeTime, tweetid, retweetid + ) irc.reply(output) return - elif args['info']: # --info to return info on a Twitter user. - location = data.get('location') - followers = data.get('followers_count') - friends = data.get('friends_count') - description = self._unescape(data.get('description')) - screen_name = self._unescape(data.get('screen_name')) - created_at = data.get('created_at') - statuses_count = data.get('statuses_count') - protected = data.get('protected') - name = self._unescape(data.get('name')) - url = data.get('url') + elif args["info"]: # --info to return info on a Twitter user. + location = data.get("location") + followers = data.get("followers_count") + friends = data.get("friends_count") + description = self._unescape(data.get("description")) + screen_name = self._unescape(data.get("screen_name")) + created_at = data.get("created_at") + statuses_count = data.get("statuses_count") + protected = data.get("protected") + name = self._unescape(data.get("name")) + url = data.get("url") # build output string conditionally. build string conditionally. ret = self._bu("@{0}".format(screen_name)) ret += " ({0})".format(name) if protected: # is the account protected/locked? - ret += " [{0}]:".format(self._bu('LOCKED')) + ret += " [{0}]:".format(self._bu("LOCKED")) else: # open. ret += ":" if url: # do they have a url? @@ -651,40 +845,63 @@ class Tweety(callbacks.Plugin): ret += " signup: {0}".format(self._bold(self._time_created_at(created_at))) if location: # do we have location? ret += " Location: {0}]".format(self._bold(location)) - else: # nope. + else: # nope. ret += "]" # finally, output. irc.reply(ret) return else: # this will display tweets/a user's timeline. can be n+1 tweets. if len(data) == 0: # no tweets found. - if not args['new']: + if not args["new"]: irc.reply("ERROR: '{0}' has not tweeted yet.".format(optnick)) log.info("Tweety: '{0}' has not tweeted yet.".format(optnick)) return - self.since_id[msg.channel]['{0}'.format(optnick)] = data[0].get('id') + self.since_id[msg.channel]["{0}".format(optnick)] = data[0].get("id") for tweet in data: # n+1 tweets found. iterate through each tweet. - text = self._unescape(tweet.get('full_text')) or self._unescape(tweet.get('text')) - nick = self._unescape(tweet["user"].get('screen_name')) - name = self._unescape(tweet["user"].get('name')) - verified = tweet['user'].get('verified') - tweetid = tweet.get('id') - if tweet.get('retweeted_status'): - retweetid = tweet['retweeted_status'].get('id') + text = self._unescape(tweet.get("full_text")) or self._unescape( + tweet.get("text") + ) + nick = self._unescape(tweet["user"].get("screen_name")) + name = self._unescape(tweet["user"].get("name")) + verified = tweet["user"].get("verified") + tweetid = tweet.get("id") + if tweet.get("retweeted_status"): + retweetid = tweet["retweeted_status"].get("id") else: retweetid = None - relativeTime = self._time_created_at(tweet.get('created_at')) + relativeTime = self._time_created_at(tweet.get("created_at")) # prepare string to output and send to irc. - output = self._outputTweet(irc, msg, nick, name, verified, text, relativeTime, tweetid, retweetid) + output = self._outputTweet( + irc, + msg, + nick, + name, + verified, + text, + relativeTime, + tweetid, + retweetid, + ) irc.reply(output) - twitter = wrap(twitter, [getopts({'noreply':'', - 'nort':'', - 'info':'', - 'id':'', - 'url':'', - 'new':'', - 'num':('int')}), - ('somethingWithoutSpaces')]) + + twitter = wrap( + twitter, + [ + getopts( + { + "noreply": "", + "nort": "", + "info": "", + "id": "", + "url": "", + "new": "", + "num": ("int"), + } + ), + ("somethingWithoutSpaces"), + ], + ) + Class = Tweety diff --git a/Weed/__init__.py b/Weed/__init__.py index 31b6ba9..7179a84 100644 --- a/Weed/__init__.py +++ b/Weed/__init__.py @@ -39,19 +39,19 @@ import supybot.world as world __version__ = "2020.02.24+git" # XXX Replace this with an appropriate author or supybot.Author instance. -__author__ = supybot.Author('oddluck', 'oddluck', - 'oddluck@riseup.net') +__author__ = 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(config) reload(plugin) diff --git a/Weed/config.py b/Weed/config.py index 413e4a4..d2973dd 100644 --- a/Weed/config.py +++ b/Weed/config.py @@ -30,9 +30,11 @@ import supybot.conf as conf import supybot.registry as registry + try: from supybot.i18n import PluginInternationalization - _ = PluginInternationalization('Weed') + + _ = PluginInternationalization("Weed") except: # Placeholder that allows to run the plugin on a bot # without the i18n module @@ -45,14 +47,15 @@ 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('Weed', True) + + conf.registerPlugin("Weed", True) -Weed = conf.registerPlugin('Weed') +Weed = conf.registerPlugin("Weed") -conf.registerGlobalValue(Weed, 'strain_api', -registry.String('', _("""Strain API Key"""))) +conf.registerGlobalValue( + Weed, "strain_api", registry.String("", _("""Strain API Key"""), private=True) +) # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: - diff --git a/Weed/plugin.py b/Weed/plugin.py index 01daf39..27b5e12 100644 --- a/Weed/plugin.py +++ b/Weed/plugin.py @@ -39,14 +39,17 @@ import re try: from supybot.i18n import PluginInternationalization - _ = PluginInternationalization('Weed') + + _ = PluginInternationalization("Weed") except ImportError: # Placeholder that allows to run the plugin on a bot # without the i18n module _ = lambda x: x + class Weed(callbacks.Plugin): """Uses API to retrieve information""" + threaded = True def strain(self, irc, msg, args, strain): @@ -56,53 +59,68 @@ class Weed(callbacks.Plugin): response1 = None response2 = None channel = msg.args[0] - strain = re.sub('[^\w\:\"\#\-\.\' ]', '', strain).casefold() - strain_api = self.registryValue('strain_api') + strain = re.sub("[^\w\:\"\#\-\.' ]", "", strain).casefold() + strain_api = self.registryValue("strain_api") if not strain_api: irc.reply("Error: You must set an API key to use this plugin.") return - url = "http://strainapi.evanbusse.com/{0}/strains/search/name/{1}".format(strain_api, strain) + url = "http://strainapi.evanbusse.com/{0}/strains/search/name/{1}".format( + strain_api, strain + ) request = requests.get(url) ok = request.status_code == requests.codes.ok if not ok: irc.reply("Error: Unable to retrieve data. Did you set an API key?") - log.error("IMDB: API Error %s: %s" % (request.status_code, request.content.decode())) + log.error( + "IMDB: API Error %s: %s" + % (request.status_code, request.content.decode()) + ) return data = json.loads(request.content) for item in data: - if item['desc'] is not None and item['name'].casefold() == strain: - id = item['id'] - name = ircutils.bold(item['name']) - type = ircutils.bold(item['race']) - desc = item['desc'] - url2 = "http://strainapi.evanbusse.com/{0}/strains/data/flavors/{1}".format(strain_api, id) + if item["desc"] is not None and item["name"].casefold() == strain: + id = item["id"] + name = ircutils.bold(item["name"]) + type = ircutils.bold(item["race"]) + desc = item["desc"] + url2 = "http://strainapi.evanbusse.com/{0}/strains/data/flavors/{1}".format( + strain_api, id + ) data2 = requests.get(url2) data2 = json.loads(data2.content) flavor1 = data2[0] flavor2 = data2[1] flavor3 = data2[2] - response1 = "{0} | {1} | Flavors: {2}, {3}, {4} | {5}".format(name, type, flavor1, flavor2, flavor3, desc) + response1 = "{0} | {1} | Flavors: {2}, {3}, {4} | {5}".format( + name, type, flavor1, flavor2, flavor3, desc + ) break for item in data: - if item['desc'] is not None and item['name'].casefold() != strain: - id = item['id'] - name = ircutils.bold(item['name']) - type = ircutils.bold(item['race']) - desc = item['desc'] - url2 = "http://strainapi.evanbusse.com/{0}/strains/data/flavors/{1}".format(strain_api, id) + if item["desc"] is not None and item["name"].casefold() != strain: + id = item["id"] + name = ircutils.bold(item["name"]) + type = ircutils.bold(item["race"]) + desc = item["desc"] + url2 = "http://strainapi.evanbusse.com/{0}/strains/data/flavors/{1}".format( + strain_api, id + ) data2 = requests.get(url2) data2 = json.loads(data2.content) flavor1 = data2[0] flavor2 = data2[1] flavor3 = data2[2] - response2 = "{0} | {1} | Flavors: {2}, {3}, {4} | {5}".format(name, type.title(), flavor1, flavor2, flavor3, desc) + response2 = "{0} | {1} | Flavors: {2}, {3}, {4} | {5}".format( + name, type.title(), flavor1, flavor2, flavor3, desc + ) break if response1 != None: irc.reply(response1) elif response1 == None and response2 != None: irc.reply(response2) else: - irc.reply('No results found, what have you been smoking?') - strain = wrap(strain, ['text']) + irc.reply("No results found, what have you been smoking?") + + strain = wrap(strain, ["text"]) + Class = Weed diff --git a/WorldTime/__init__.py b/WorldTime/__init__.py index 6fdbe34..9336b62 100644 --- a/WorldTime/__init__.py +++ b/WorldTime/__init__.py @@ -41,20 +41,24 @@ import importlib __version__ = "2020.02.24+git" # XXX Replace this with an appropriate author or supybot.Author instance. -__author__ = supybot.Author('reticulatingspline', 'spline', '') -__maintainer__ = getattr(supybot.authors, 'oddluck', - supybot.Author('oddluck', 'oddluck', 'oddluck@riseup.net')) +__author__ = supybot.Author("reticulatingspline", "spline", "") +__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__ = '' # 'http://supybot.com/Members/yourname/WorldTime/download' +__url__ = "" # 'http://supybot.com/Members/yourname/WorldTime/download' from . import config from . import plugin from imp import reload + # In case we're being reloaded. importlib.reload(config) importlib.reload(plugin) diff --git a/WorldTime/config.py b/WorldTime/config.py index 291135d..37b3658 100644 --- a/WorldTime/config.py +++ b/WorldTime/config.py @@ -30,13 +30,16 @@ import supybot.conf as conf import supybot.registry as registry + try: from supybot.i18n import PluginInternationalization - _ = PluginInternationalization('WorldTime') + + _ = PluginInternationalization("WorldTime") except: # Placeholder that allows to run the plugin on a bot # without the i18n module - _ = lambda x:x + _ = lambda x: x + def configure(advanced): # This will be called by supybot to configure this module. advanced is @@ -44,13 +47,31 @@ 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('WorldTime', True) + + conf.registerPlugin("WorldTime", True) -WorldTime = conf.registerPlugin('WorldTime') +WorldTime = conf.registerPlugin("WorldTime") # This is where your configuration variables (if any) should go. For example: -conf.registerChannelValue(WorldTime, 'disableANSI', registry.Boolean(False, _("""Disable color/bolding for WorldTime output in channel."""))) -conf.registerChannelValue(WorldTime, 'format', registry.String('%a, %H:%M', _("""Sets the output time format (using an strftime-formatted string)."""))) -conf.registerGlobalValue(WorldTime, 'mapsAPIkey', registry.String('', """Sets the Google Maps Places API key""")) +conf.registerChannelValue( + WorldTime, + "disableANSI", + registry.Boolean( + False, _("""Disable color/bolding for WorldTime output in channel.""") + ), +) +conf.registerChannelValue( + WorldTime, + "format", + registry.String( + "%a, %H:%M", + _("""Sets the output time format (using an strftime-formatted string)."""), + ), +) +conf.registerGlobalValue( + WorldTime, + "mapsAPIkey", + registry.String("", """Sets the Google Maps Places API key""", private=True), +) # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: diff --git a/WorldTime/plugin.py b/WorldTime/plugin.py index 67c7969..c95082a 100644 --- a/WorldTime/plugin.py +++ b/WorldTime/plugin.py @@ -44,23 +44,28 @@ import supybot.callbacks as callbacks import supybot.world as world import supybot.conf as conf import supybot.log as log + try: from supybot.i18n import PluginInternationalization - _ = PluginInternationalization('WorldTime') + + _ = PluginInternationalization("WorldTime") except ImportError: # Placeholder that allows to run the plugin on a bot # without the i18n module - _ = lambda x:x + _ = lambda x: x -filename = conf.supybot.directories.data.dirize('WorldTime.db') +filename = conf.supybot.directories.data.dirize("WorldTime.db") HEADERS = { - 'User-agent': 'Mozilla/5.0 (compatible; Supybot/Limnoria %s; WorldTime plugin)' % conf.version + "User-agent": "Mozilla/5.0 (compatible; Supybot/Limnoria %s; WorldTime plugin)" + % conf.version } + class WorldTime(callbacks.Plugin): """Add the help for "@plugin help WorldTime" here This should describe *how* to use this plugin.""" + threaded = True ############################### @@ -75,27 +80,27 @@ class WorldTime(callbacks.Plugin): world.flushers.append(self._flushDb) def _loadDb(self): - """Loads the (flatfile) database mapping ident@hosts to timezones.""" + """Loads the (flatfile) database mapping ident@hosts to timezones.""" - try: - with open(filename, 'rb') as f: - self.db = pickle.load(f) - except Exception as e: - self.log.debug('WorldTime: Unable to load pickled database: %s', e) + try: + with open(filename, "rb") as f: + self.db = pickle.load(f) + except Exception as e: + self.log.debug("WorldTime: Unable to load pickled database: %s", e) def _flushDb(self): - """Flushes the (flatfile) database mapping ident@hosts to timezones.""" + """Flushes the (flatfile) database mapping ident@hosts to timezones.""" - try: - with open(filename, 'wb') as f: - pickle.dump(self.db, f, 2) - except Exception as e: - self.log.warning('WorldTime: Unable to write pickled database: %s', e) + try: + with open(filename, "wb") as f: + pickle.dump(self.db, f, 2) + except Exception as e: + self.log.warning("WorldTime: Unable to write pickled database: %s", e) def die(self): - self._flushDb() - world.flushers.remove(self._flushDb) - self.__parent.die() + self._flushDb() + world.flushers.remove(self._flushDb) + self.__parent.die() ################## # TIME FUNCTIONS # @@ -117,9 +122,12 @@ class WorldTime(callbacks.Plugin): ############## def _getlatlng(self, location): - api_key = self.registryValue('mapsAPIkey') + api_key = self.registryValue("mapsAPIkey") location = utils.web.urlquote(location) - url = 'https://maps.googleapis.com/maps/api/geocode/json?address=%s&sensor=false&key=%s' % (location, api_key) + url = ( + "https://maps.googleapis.com/maps/api/geocode/json?" + "address=%s&sensor=false&key=%s" % (location, api_key) + ) # try and fetch url try: @@ -130,21 +138,28 @@ class WorldTime(callbacks.Plugin): # wrap in a big try/except try: result = json.loads(response.decode()) - if result['status'] == 'OK': - lat = str(result['results'][0]['geometry']['location']['lat']) - lng = str(result['results'][0]['geometry']['location']['lng']) - place = (result['results'][0]['formatted_address']) - ll = '%s,%s' % (lat, lng) # lat+long into a single string. - return {'place':place, 'll':ll} + if result["status"] == "OK": + lat = str(result["results"][0]["geometry"]["location"]["lat"]) + lng = str(result["results"][0]["geometry"]["location"]["lng"]) + place = result["results"][0]["formatted_address"] + ll = "%s,%s" % (lat, lng) # lat+long into a single string. + return {"place": place, "ll": ll} else: - self.log.info("ERROR: _getlatlng: status result NOT ok. Result: {0}".format(result)) + self.log.info( + "ERROR: _getlatlng: status result NOT ok. Result: {0}".format( + result + ) + ) except Exception as e: self.log.info("ERROR: _getlatlng: {0}".format(e)) def _gettime(self, latlng): - api_key = self.registryValue('mapsAPIkey') + api_key = self.registryValue("mapsAPIkey") latlng = utils.web.urlquote(latlng) - url = 'https://maps.googleapis.com/maps/api/timezone/json?location=%s&sensor=false×tamp=%s&key=%s' % (latlng, time.time(), api_key) + url = ( + "https://maps.googleapis.com/maps/api/timezone/json?location=" + "%s&sensor=false×tamp=%s&key=%s" % (latlng, time.time(), api_key) + ) # try and fetch url try: @@ -154,11 +169,15 @@ class WorldTime(callbacks.Plugin): # wrap in a big try/except try: - result = json.loads(response.decode('utf-8')) - if result['status'] == 'OK': + result = json.loads(response.decode("utf-8")) + if result["status"] == "OK": return result else: - self.log.info("WorldTime: _gettime: status result NOT ok. Result: {0}".format(result)) + self.log.info( + "WorldTime: _gettime: status result NOT ok. Result: {0}".format( + result + ) + ) except Exception as e: self.log.info("WorldTime: _gettime: {0}".format(e)) @@ -176,58 +195,80 @@ class WorldTime(callbacks.Plugin): opts = dict(opts) if not location: try: - if 'nick' in opts: - host = irc.state.nickToHostmask(opts['nick']) + if "nick" in opts: + host = irc.state.nickToHostmask(opts["nick"]) else: host = msg.prefix - ih = host.split('!')[1] + ih = host.split("!")[1] location = self.db[ih] except KeyError: - irc.error("No location for %s is set. Use the 'set' command " + irc.error( + "No location for %s is set. Use the 'set' command " "to set a location for your current hostmask, or call 'worldtime' " - "with as an argument." % ircutils.bold('*!'+ih), Raise=True) + "with as an argument." % ircutils.bold("*!" + ih), + Raise=True, + ) # first, grab lat and long for user location gc = self._getlatlng(location) if not gc: - irc.error("I could not find the location for: {0}. Bad location? Spelled wrong?".format(location), Raise=True) + irc.error( + "I could not find the location for: {0}. Bad location? " + "Spelled wrong?".format(location), + Raise=True, + ) # next, lets grab the localtime for that location w/lat+long. - ll = self._gettime(gc['ll']) + ll = self._gettime(gc["ll"]) if not ll: - irc.error("I could not find the local timezone for: {0}. Bad location? Spelled wrong?".format(location), Raise=True) + irc.error( + "I could not find the local timezone for: {0}. Bad location? " + "Spelled wrong?".format(location), + Raise=True, + ) # if we're here, we have localtime zone. - lt = self._converttz(msg, ll['timeZoneId']) + lt = self._converttz(msg, ll["timeZoneId"]) if lt: # make sure we get it back. if sys.version_info[0] <= 2: - s = "{0} :: Current local time is: {1} ({2})".format(ircutils.bold(gc['place'].encode('utf-8')), lt, ll['timeZoneName'].encode('utf-8')) + s = "{0} :: Current local time is: {1} ({2})".format( + ircutils.bold(gc["place"].encode("utf-8")), + lt, + ll["timeZoneName"].encode("utf-8"), + ) else: - s ="{0} :: Current local time is: {1} ({2})".format(ircutils.bold(gc['place']), lt, ll['timeZoneName']) - if self.registryValue('disableANSI', msg.args[0]): + s = "{0} :: Current local time is: {1} ({2})".format( + ircutils.bold(gc["place"]), lt, ll["timeZoneName"] + ) + if self.registryValue("disableANSI", msg.args[0]): s = ircutils.stripFormatting(s) irc.reply(s) else: - irc.error("Something went wrong during conversion to timezone. Check the logs.", Raise=True) + irc.error( + "Something went wrong during conversion to timezone. Check the logs.", + Raise=True, + ) - worldtime = wrap(worldtime, [getopts({'nick': 'nick'}), additional('text')]) + worldtime = wrap(worldtime, [getopts({"nick": "nick"}), additional("text")]) def set(self, irc, msg, args, timezone): """ Sets the location for your current ident@host to .""" - ih = msg.prefix.split('!')[1] + ih = msg.prefix.split("!")[1] self.db[ih] = timezone irc.replySuccess() - set = wrap(set, ['text']) + + set = wrap(set, ["text"]) def unset(self, irc, msg, args): """takes no arguments. Unsets the location for your current ident@host.""" - ih = msg.prefix.split('!')[1] + ih = msg.prefix.split("!")[1] try: del self.db[ih] irc.replySuccess() except KeyError: - irc.error("No entry for %s exists." % ircutils.bold('*!'+ih), Raise=True) + irc.error("No entry for %s exists." % ircutils.bold("*!" + ih), Raise=True) + Class = WorldTime