diff --git a/cloudbot/clients/irc.py b/cloudbot/clients/irc.py index 6d87147f..165cc4a4 100644 --- a/cloudbot/clients/irc.py +++ b/cloudbot/clients/irc.py @@ -178,7 +178,7 @@ def get_channel_key( channel: str, default: Optional[str] = None, *, - set_key: bool = True + set_key: bool = True, ) -> Optional[str]: if channel in self._channel_keys: key = self._channel_keys[channel] diff --git a/cloudbot/event.py b/cloudbot/event.py index 5ae5921a..338b6a44 100644 --- a/cloudbot/event.py +++ b/cloudbot/event.py @@ -65,7 +65,7 @@ def __init__( irc_command=None, irc_paramlist=None, irc_ctcp_text=None, - irc_tags=None + irc_tags=None, ): """ All of these parameters except for `bot` and `hook` are optional. @@ -169,8 +169,8 @@ def __iter__(self) -> Iterator[str]: def __getitem__(self, item: str) -> Any: try: return getattr(self, item) - except AttributeError: - raise KeyError(item) + except AttributeError as e: + raise KeyError(item) from e async def prepare(self): """ @@ -268,7 +268,9 @@ def message(self, message, target=None): """ if target is None: if self.chan is None: - raise ValueError("Target must be specified when chan is not assigned") + raise ValueError( + "Target must be specified when chan is not assigned" + ) target = self.chan @@ -295,11 +297,15 @@ def reply(self, *messages, target=None): reply_ping = self.conn.config.get("reply_ping", True) if target is None: if self.chan is None: - raise ValueError("Target must be specified when chan is not assigned") + raise ValueError( + "Target must be specified when chan is not assigned" + ) target = self.chan - if not messages: # if there are no messages specified, don't do anything + if ( + not messages + ): # if there are no messages specified, don't do anything return if target == self.nick or not reply_ping: @@ -317,7 +323,9 @@ def action(self, message, target=None): """ if target is None: if self.chan is None: - raise ValueError("Target must be specified when chan is not assigned") + raise ValueError( + "Target must be specified when chan is not assigned" + ) target = self.chan @@ -331,7 +339,9 @@ def ctcp(self, message, ctcp_type, target=None): """ if target is None: if self.chan is None: - raise ValueError("Target must be specified when chan is not assigned") + raise ValueError( + "Target must be specified when chan is not assigned" + ) target = self.chan @@ -349,7 +359,9 @@ def notice(self, message, target=None): avoid_notices = self.conn.config.get("avoid_notices", False) if target is None: if self.nick is None: - raise ValueError("Target must be specified when nick is not assigned") + raise ValueError( + "Target must be specified when nick is not assigned" + ) target = self.nick @@ -360,17 +372,19 @@ def notice(self, message, target=None): self.conn.notice(target, message) def has_permission(self, permission, notice=True): - """ returns whether or not the current user has a given permission + """returns whether or not the current user has a given permission :type permission: str :rtype: bool """ if not self.mask: raise ValueError("has_permission requires mask is not assigned") - return self.conn.permissions.has_perm_mask(self.mask, permission, notice=notice) + return self.conn.permissions.has_perm_mask( + self.mask, permission, notice=notice + ) async def check_permission(self, permission, notice=True): - """ returns whether or not the current user has a given permission + """returns whether or not the current user has a given permission :type permission: str :type notice: bool :rtype: bool @@ -379,7 +393,9 @@ async def check_permission(self, permission, notice=True): return True for perm_hook in self.bot.plugin_manager.perm_hooks[permission]: - ok, res = await self.bot.plugin_manager.internal_launch(perm_hook, self) + ok, res = await self.bot.plugin_manager.internal_launch( + perm_hook, self + ) if ok and res: return True @@ -440,7 +456,7 @@ def __init__( irc_raw=None, irc_prefix=None, irc_command=None, - irc_paramlist=None + irc_paramlist=None, ): """ :param text: The arguments for the command @@ -520,7 +536,7 @@ def __init__( irc_raw=None, irc_prefix=None, irc_command=None, - irc_paramlist=None + irc_paramlist=None, ): """ :param: match: The match objected returned by the regex search method @@ -567,7 +583,9 @@ async def prepare(self): try: self.parsed_line = Message.parse(self.line) except Exception: - logger.exception("Unable to parse line requested by hook %s", self.hook) + logger.exception( + "Unable to parse line requested by hook %s", self.hook + ) self.parsed_line = None def prepare_threaded(self): @@ -577,7 +595,9 @@ def prepare_threaded(self): try: self.parsed_line = Message.parse(self.line) except Exception: - logger.exception("Unable to parse line requested by hook %s", self.hook) + logger.exception( + "Unable to parse line requested by hook %s", self.hook + ) self.parsed_line = None @property @@ -593,7 +613,7 @@ def __init__( launched_event=None, result=None, error=None, - **kwargs + **kwargs, ): super().__init__(*args, **kwargs) self.launched_hook = launched_hook diff --git a/cloudbot/permissions.py b/cloudbot/permissions.py index 20023f45..48a5cb73 100644 --- a/cloudbot/permissions.py +++ b/cloudbot/permissions.py @@ -24,7 +24,8 @@ def __init__(self, conn): """ logger.info( "[%s|permissions] Created permission manager for %s.", - conn.name, conn.name + conn.name, + conn.name, ) # stuff @@ -43,7 +44,8 @@ def reload(self): self.perm_users = {} logger.info( "[%s|permissions] Reloading permissions for %s.", - self.name, self.name + self.name, + self.name, ) groups = self.config.get("permissions", {}) # work out the permissions and users each group has @@ -53,7 +55,8 @@ def reload(self): "[%s|permissions] Warning! Non-lower-case group %r in " "config. This will cause problems when setting " "permissions using the bot's permissions commands", - self.name, key + self.name, + key, ) key = key.lower() self.group_perms[key] = [] @@ -72,15 +75,14 @@ def reload(self): logger.debug( "[%s|permissions] Group permissions: %s", - self.name, self.group_perms + self.name, + self.group_perms, ) logger.debug( - "[%s|permissions] Group users: %s", - self.name, self.group_users + "[%s|permissions] Group users: %s", self.name, self.group_users ) logger.debug( - "[%s|permissions] Permission users: %s", - self.name, self.perm_users + "[%s|permissions] Permission users: %s", self.name, self.perm_users ) def has_perm_mask(self, user_mask, perm, notice=True): @@ -105,7 +107,9 @@ def has_perm_mask(self, user_mask, perm, notice=True): if notice: logger.info( "[%s|permissions] Allowed user %s access to %s", - self.name, user_mask, perm + self.name, + user_mask, + perm, ) return True @@ -198,7 +202,9 @@ def remove_group_user(self, group, user_mask): if group not in config_groups: logger.warning( "[%s|permissions] Can't remove user from group due to" - " upper-case group names!", self.name) + " upper-case group names!", + self.name, + ) continue config_group = config_groups.get(group) config_users = config_group.get("users") diff --git a/cloudbot/util/__init__.py b/cloudbot/util/__init__.py index d7363182..8d693889 100644 --- a/cloudbot/util/__init__.py +++ b/cloudbot/util/__init__.py @@ -1,4 +1,5 @@ # CONSTANTS -ATTR_PREFIX = '_cloudbot' -HOOK_ATTR = ATTR_PREFIX + '_hook' -LOADED_ATTR = ATTR_PREFIX + '_loaded' + +ATTR_PREFIX = "_cloudbot" +HOOK_ATTR = ATTR_PREFIX + "_hook" +LOADED_ATTR = ATTR_PREFIX + "_loaded" diff --git a/cloudbot/util/colors.py b/cloudbot/util/colors.py index 725ccf1f..315479dc 100644 --- a/cloudbot/util/colors.py +++ b/cloudbot/util/colors.py @@ -65,28 +65,23 @@ "dark_gray": "14", "grey": "15", "gray": "15", - "random": "" # Special keyword, generate a random number. + "random": "", # Special keyword, generate a random number. } IRC_FORMATTING_DICT = { "colour": "\x03", "color": "\x03", - "bold": "\x02", "b": "\x02", - "underlined": "\x1F", "underline": "\x1F", "ul": "\x1F", - "italics": "\x1D", "italic": "\x1D", "i": "\x1D", - "reverse": "\x16", - "reset": "\x0F", - "clear": "\x0F" + "clear": "\x0F", } COLOR_RE = re.compile(r"\$\(.*?\)", re.I) @@ -102,11 +97,17 @@ def get_color(colour, return_formatted=True): colour = colour.lower() if colour not in IRC_COLOUR_DICT: - raise KeyError("The colour '{}' is not in the list of available colours.".format(colour)) + raise KeyError( + "The colour '{}' is not in the list of available colours.".format( + colour + ) + ) if colour == "random": # Special keyword for a random colour rand = randint(0, 15) - if rand < 10: # Prepend '0' before colour so it always is double digits. + if ( + rand < 10 + ): # Prepend '0' before colour so it always is double digits. rand = "0" + str(rand) rand = str(rand) @@ -126,7 +127,11 @@ def get_format(formatting): """ if formatting.lower() not in IRC_FORMATTING_DICT: - raise KeyError("The formatting '{}' is not found in the list of available formats.".format(formatting)) + raise KeyError( + "The formatting '{}' is not found in the list of available formats.".format( + formatting + ) + ) return IRC_FORMATTING_DICT[formatting.lower()] @@ -169,6 +174,7 @@ def parse(string): # Formatting stripping. + def strip(string): """ Removes all $() syntax formatting codes from the input string and returns it. @@ -190,7 +196,7 @@ def strip_irc(string): :rtype str """ - return IRC_COLOR_RE.sub('', string) + return IRC_COLOR_RE.sub("", string) def strip_all(string): @@ -205,6 +211,7 @@ def strip_all(string): # Internal use + def _convert(string): if not string.startswith("$(") and not string.endswith(")"): return string diff --git a/cloudbot/util/filesize.py b/cloudbot/util/filesize.py index a08fb18b..75f2ab32 100644 --- a/cloudbot/util/filesize.py +++ b/cloudbot/util/filesize.py @@ -53,48 +53,48 @@ """ traditional = ( - (1024 ** 5, 'P'), - (1024 ** 4, 'T'), - (1024 ** 3, 'G'), - (1024 ** 2, 'M'), - (1024 ** 1, 'K'), - (1024 ** 0, 'B'), + (1024 ** 5, "P"), + (1024 ** 4, "T"), + (1024 ** 3, "G"), + (1024 ** 2, "M"), + (1024 ** 1, "K"), + (1024 ** 0, "B"), ) alternative = ( - (1024 ** 5, ' PB'), - (1024 ** 4, ' TB'), - (1024 ** 3, ' GB'), - (1024 ** 2, ' MB'), - (1024 ** 1, ' KB'), - (1024 ** 0, (' byte', ' bytes')), + (1024 ** 5, " PB"), + (1024 ** 4, " TB"), + (1024 ** 3, " GB"), + (1024 ** 2, " MB"), + (1024 ** 1, " KB"), + (1024 ** 0, (" byte", " bytes")), ) verbose = ( - (1024 ** 5, (' petabyte', ' petabytes')), - (1024 ** 4, (' terabyte', ' terabytes')), - (1024 ** 3, (' gigabyte', ' gigabytes')), - (1024 ** 2, (' megabyte', ' megabytes')), - (1024 ** 1, (' kilobyte', ' kilobytes')), - (1024 ** 0, (' byte', ' bytes')), + (1024 ** 5, (" petabyte", " petabytes")), + (1024 ** 4, (" terabyte", " terabytes")), + (1024 ** 3, (" gigabyte", " gigabytes")), + (1024 ** 2, (" megabyte", " megabytes")), + (1024 ** 1, (" kilobyte", " kilobytes")), + (1024 ** 0, (" byte", " bytes")), ) iec = ( - (1024 ** 5, 'Pi'), - (1024 ** 4, 'Ti'), - (1024 ** 3, 'Gi'), - (1024 ** 2, 'Mi'), - (1024 ** 1, 'Ki'), - (1024 ** 0, ''), + (1024 ** 5, "Pi"), + (1024 ** 4, "Ti"), + (1024 ** 3, "Gi"), + (1024 ** 2, "Mi"), + (1024 ** 1, "Ki"), + (1024 ** 0, ""), ) si = ( - (1000 ** 5, 'P'), - (1000 ** 4, 'T'), - (1000 ** 3, 'G'), - (1000 ** 2, 'M'), - (1000 ** 1, 'K'), - (1000 ** 0, 'B'), + (1000 ** 5, "P"), + (1000 ** 4, "T"), + (1000 ** 3, "G"), + (1000 ** 2, "M"), + (1000 ** 1, "K"), + (1000 ** 0, "B"), ) # re.I style aliases diff --git a/cloudbot/util/formatting.py b/cloudbot/util/formatting.py index 0cb8f06b..7935509f 100644 --- a/cloudbot/util/formatting.py +++ b/cloudbot/util/formatting.py @@ -56,63 +56,64 @@ IRC_COLOR_RE = re.compile(r"(\x03(\d+,\d+|\d)|[\x0f\x02\x16\x1f])") REPLACEMENTS = { - 'a': 'ä', - 'b': 'Б', - 'c': 'ċ', - 'd': 'đ', - 'e': 'ë', - 'f': 'ƒ', - 'g': 'ġ', - 'h': 'ħ', - 'i': 'í', - 'j': 'ĵ', - 'k': 'ķ', - 'l': 'ĺ', - 'm': 'ṁ', - 'n': 'ñ', - 'o': 'ö', - 'p': 'ρ', - 'q': 'ʠ', - 'r': 'ŗ', - 's': 'š', - 't': 'ţ', - 'u': 'ü', - 'v': '', - 'w': 'ω', - 'x': 'χ', - 'y': 'ÿ', - 'z': 'ź', - 'A': 'Å', - 'B': 'Β', - 'C': 'Ç', - 'D': 'Ď', - 'E': 'Ē', - 'F': 'Ḟ', - 'G': 'Ġ', - 'H': 'Ħ', - 'I': 'Í', - 'J': 'Ĵ', - 'K': 'Ķ', - 'L': 'Ĺ', - 'M': 'Μ', - 'N': 'Ν', - 'O': 'Ö', - 'P': 'Р', - 'Q': 'Q', - 'R': 'Ŗ', - 'S': 'Š', - 'T': 'Ţ', - 'U': 'Ů', - 'V': 'Ṿ', - 'W': 'Ŵ', - 'X': 'Χ', - 'Y': 'Ỳ', - 'Z': 'Ż' + "a": "ä", + "b": "Б", + "c": "ċ", + "d": "đ", + "e": "ë", + "f": "ƒ", + "g": "ġ", + "h": "ħ", + "i": "í", + "j": "ĵ", + "k": "ķ", + "l": "ĺ", + "m": "ṁ", + "n": "ñ", + "o": "ö", + "p": "ρ", + "q": "ʠ", + "r": "ŗ", + "s": "š", + "t": "ţ", + "u": "ü", + "v": "", + "w": "ω", + "x": "χ", + "y": "ÿ", + "z": "ź", + "A": "Å", + "B": "Β", + "C": "Ç", + "D": "Ď", + "E": "Ē", + "F": "Ḟ", + "G": "Ġ", + "H": "Ħ", + "I": "Í", + "J": "Ĵ", + "K": "Ķ", + "L": "Ĺ", + "M": "Μ", + "N": "Ν", + "O": "Ö", + "P": "Р", + "Q": "Q", + "R": "Ŗ", + "S": "Š", + "T": "Ţ", + "U": "Ů", + "V": "Ṿ", + "W": "Ŵ", + "X": "Χ", + "Y": "Ỳ", + "Z": "Ż", } # Classes + class HTMLTextExtractor(HTMLParser): """ Takes HTML and provides cleaned and stripped text. @@ -126,11 +127,12 @@ def handle_data(self, d): self.result.append(d) def get_text(self): - return ''.join(self.result) + return "".join(self.result) # Functions + def strip_html(to_strip): """ Takes HTML and returns cleaned and stripped text. @@ -151,7 +153,7 @@ def munge(text, count=0): for n, c in enumerate(text): rep = REPLACEMENTS.get(c) if rep: - text = text[:n] + rep + text[n + 1:] + text = text[:n] + rep + text[n + 1 :] reps += 1 if reps == count: break @@ -178,7 +180,7 @@ def multi_replace(text, word_dic): then returns the changed text :rtype str """ - rc = re.compile('|'.join(map(re.escape, word_dic))) + rc = re.compile("|".join(map(re.escape, word_dic))) def translate(match): return word_dic[match.group(0)] @@ -190,7 +192,7 @@ def translate(match): multiword_replace = multi_replace -def truncate_words(content, length=10, suffix='...'): +def truncate_words(content, length=10, suffix="..."): """ Truncates a string after a certain number of words. :rtype str @@ -202,7 +204,7 @@ def truncate_words(content, length=10, suffix='...'): return " ".join(split[:length]) + suffix -def truncate(content, length=100, suffix='...', sep=' '): +def truncate(content, length=100, suffix="...", sep=" "): """ Truncates a string after a certain number of characters. Function always tries to truncate on a word boundary. @@ -227,38 +229,38 @@ def chunk_str(content, length=420): def chunk(c, l): while c: - out = (c + ' ')[:l].rsplit(' ', 1)[0] - c = c[len(out):].strip() + out = (c + " ")[:l].rsplit(" ", 1)[0] + c = c[len(out) :].strip() yield out return list(chunk(content, length)) -def pluralize(num=0, text=''): # pragma: no cover +def pluralize(num=0, text=""): # pragma: no cover """ Takes a number and a string, and pluralizes that string using the number and combines the results. :rtype: str """ warnings.warn( "formatting.pluralize() is deprecated, please use one of the other formatting.pluralize_*() functions", - DeprecationWarning + DeprecationWarning, ) return pluralize_suffix(num, text) -def pluralise(num=0, text=''): # pragma: no cover +def pluralise(num=0, text=""): # pragma: no cover """ Takes a number and a string, and pluralizes that string using the number and combines the results. :rtype: str """ warnings.warn( "formatting.pluralise() is deprecated, please use one of the other formatting.pluralise_*() functions", - DeprecationWarning + DeprecationWarning, ) return pluralise_suffix(num, text) -def pluralize_suffix(num=0, text='', suffix='s'): +def pluralize_suffix(num=0, text="", suffix="s"): """ Takes a number and a string, and pluralizes that string using the number and combines the results. :rtype: str @@ -277,29 +279,29 @@ def pluralize_select(count, single, plural): def pluralize_auto(count, thing): - if thing.endswith('us'): - return pluralize_select(count, thing, thing[:-2] + 'i') + if thing.endswith("us"): + return pluralize_select(count, thing, thing[:-2] + "i") - if thing.endswith('is'): - return pluralize_select(count, thing, thing[:-2] + 'es') + if thing.endswith("is"): + return pluralize_select(count, thing, thing[:-2] + "es") - if thing.endswith(('s', 'ss', 'sh', 'ch', 'x', 'z')): - return pluralize_suffix(count, thing, 'es') + if thing.endswith(("s", "ss", "sh", "ch", "x", "z")): + return pluralize_suffix(count, thing, "es") - if thing.endswith(('f', 'fe')): - return pluralize_select(count, thing, thing.rsplit('f', 1)[0] + 'ves') + if thing.endswith(("f", "fe")): + return pluralize_select(count, thing, thing.rsplit("f", 1)[0] + "ves") - if thing.endswith('y') and thing[-2:-1].lower() not in "aeiou": - return pluralize_select(count, thing, thing[:-1] + 'ies') + if thing.endswith("y") and thing[-2:-1].lower() not in "aeiou": + return pluralize_select(count, thing, thing[:-1] + "ies") - if thing.endswith('y') and thing[-2:-1].lower() in "aeiou": + if thing.endswith("y") and thing[-2:-1].lower() in "aeiou": return pluralize_suffix(count, thing) - if thing.endswith('o'): - return pluralize_suffix(count, thing, 'es') + if thing.endswith("o"): + return pluralize_suffix(count, thing, "es") - if thing.endswith('on'): - return pluralize_select(count, thing, thing[:-2] + 'a') + if thing.endswith("on"): + return pluralize_select(count, thing, thing[:-2] + "a") return pluralize_suffix(count, thing) @@ -319,7 +321,9 @@ def dict_format(args, formats): # Check if values can be mapped m = f.format(**args) # Insert match and number of matched values (max matched values if already in dict) - matches[m] = max([matches.get(m, 0), len(re.findall(r'({.*?\})', f))]) + matches[m] = max( + [matches.get(m, 0), len(re.findall(r"({.*?\})", f))] + ) except KeyError: continue @@ -332,8 +336,11 @@ def dict_format(args, formats): # DJANGO LICENCE -split_re = re.compile(r"""((?:[^\s'"]*(?:(?:"(?:[^"\\]|\\.)*" | '(?:[""" - r"""^'\\]|\\.)*')[^\s'"]*)+) | \S+)""", re.VERBOSE) +split_re = re.compile( + r"""((?:[^\s'"]*(?:(?:"(?:[^"\\]|\\.)*" | '(?:[""" + r"""^'\\]|\\.)*')[^\s'"]*)+) | \S+)""", + re.VERBOSE, +) def smart_split(text): @@ -355,7 +362,7 @@ def smart_split(text): yield bit.group(0) -def get_text_list(list_, last_word='or'): +def get_text_list(list_, last_word="or"): """ >>> get_text_list(['a', 'b', 'c', 'd']) 'a, b, c or d' @@ -369,15 +376,17 @@ def get_text_list(list_, last_word='or'): '' """ if not list_: - return '' + return "" if len(list_) == 1: return list_[0] - return '%s %s %s' % ( + return "%s %s %s" % ( # Translators: This string is used as a separator between list elements - ', '.join([i for i in list_][:-1]), - last_word, list_[-1]) + ", ".join([i for i in list_][:-1]), + last_word, + list_[-1], + ) def gen_markdown_table(headers, rows): @@ -389,9 +398,11 @@ def gen_markdown_table(headers, rows): rotated = zip(*reversed(rows)) sizes = tuple(map(lambda l: max(max(map(len, l)), 3), rotated)) - rows.insert(1, tuple(('-' * size) for size in sizes)) + rows.insert(1, tuple(("-" * size) for size in sizes)) lines = [ - "| {} |".format(' | '.join(cell.ljust(sizes[i]) for i, cell in enumerate(row))) + "| {} |".format( + " | ".join(cell.ljust(sizes[i]) for i, cell in enumerate(row)) + ) for row in rows ] - return '\n'.join(lines) + return "\n".join(lines) diff --git a/cloudbot/util/func_utils.py b/cloudbot/util/func_utils.py index 90f49a89..66dc368d 100644 --- a/cloudbot/util/func_utils.py +++ b/cloudbot/util/func_utils.py @@ -16,7 +16,9 @@ def call_with_args(func, arg_data): sig = inspect.signature(func, follow_wrapped=False) try: args = [ - arg_data[key] for key in sig.parameters.keys() if not key.startswith("_") + arg_data[key] + for key in sig.parameters.keys() + if not key.startswith("_") ] except KeyError as e: raise ParameterError(e.args[0], arg_data.keys()) from e diff --git a/cloudbot/util/http.py b/cloudbot/util/http.py index f71ec3db..7efbf92e 100644 --- a/cloudbot/util/http.py +++ b/cloudbot/util/http.py @@ -17,15 +17,20 @@ # security parser = etree.XMLParser(resolve_entities=False, no_network=True) -ua_cloudbot = 'Cloudbot/DEV http://github.com/CloudDev/CloudBot' - -ua_firefox = 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:17.0) Gecko/17.0' \ - ' Firefox/17.0' -ua_old_firefox = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; ' \ - 'rv:1.8.1.6) Gecko/20070725 Firefox/2.0.0.6' -ua_internetexplorer = 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)' -ua_chrome = 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.4 (KHTML, ' \ - 'like Gecko) Chrome/22.0.1229.79 Safari/537.4' +ua_cloudbot = "Cloudbot/DEV http://github.com/CloudDev/CloudBot" + +ua_firefox = ( + "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:17.0) Gecko/17.0" " Firefox/17.0" +) +ua_old_firefox = ( + "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; " + "rv:1.8.1.6) Gecko/20070725 Firefox/2.0.0.6" +) +ua_internetexplorer = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)" +ua_chrome = ( + "Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.4 (KHTML, " + "like Gecko) Chrome/22.0.1229.79 Safari/537.4" +) jar = http.cookiejar.CookieJar() @@ -54,7 +59,7 @@ def parse_soup(text, features=None, **kwargs): 'test' """ if features is None: - features = 'lxml' + features = "lxml" return BeautifulSoup(text, features=features, **kwargs) @@ -83,8 +88,18 @@ def get_json(*args, **kwargs): return json.loads(get(*args, **kwargs)) -def open_request(url, query_params=None, user_agent=None, post_data=None, referer=None, get_method=None, cookies=False, - timeout=None, headers=None, **kwargs): +def open_request( + url, + query_params=None, + user_agent=None, + post_data=None, + referer=None, + get_method=None, + cookies=False, + timeout=None, + headers=None, + **kwargs, +): if query_params is None: query_params = {} @@ -104,13 +119,15 @@ def open_request(url, query_params=None, user_agent=None, post_data=None, refere for header_key, header_value in headers.items(): request.add_header(header_key, header_value) - request.add_header('User-Agent', user_agent) + request.add_header("User-Agent", user_agent) if referer is not None: - request.add_header('Referer', referer) + request.add_header("Referer", referer) if cookies: - opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(jar)) + opener = urllib.request.build_opener( + urllib.request.HTTPCookieProcessor(jar) + ) else: opener = urllib.request.build_opener() @@ -121,17 +138,34 @@ def open_request(url, query_params=None, user_agent=None, post_data=None, refere # noinspection PyShadowingBuiltins -def open(url, query_params=None, user_agent=None, post_data=None, - referer=None, get_method=None, cookies=False, timeout=None, headers=None, - **kwargs): # pylint: disable=locally-disabled, redefined-builtin # pragma: no cover +def open( + url, + query_params=None, + user_agent=None, + post_data=None, + referer=None, + get_method=None, + cookies=False, + timeout=None, + headers=None, + **kwargs, +): # pylint: disable=locally-disabled, redefined-builtin # pragma: no cover warnings.warn( "http.open() is deprecated, use http.open_request() instead.", - DeprecationWarning + DeprecationWarning, ) return open_request( - url, query_params=query_params, user_agent=user_agent, post_data=post_data, referer=referer, - get_method=get_method, cookies=cookies, timeout=timeout, headers=headers, **kwargs + url, + query_params=query_params, + user_agent=user_agent, + post_data=post_data, + referer=referer, + get_method=get_method, + cookies=cookies, + timeout=timeout, + headers=headers, + **kwargs, ) @@ -145,8 +179,9 @@ def prepare_url(url, queries): query = dict(urllib.parse.parse_qsl(query)) query.update(queries) - query = urllib.parse.urlencode(dict((to_utf8(key), to_utf8(value)) - for key, value in query.items())) + query = urllib.parse.urlencode( + dict((to_utf8(key), to_utf8(value)) for key, value in query.items()) + ) url = urllib.parse.urlunsplit((scheme, netloc, path, query, fragment)) @@ -163,7 +198,7 @@ def to_utf8(s): b'1' """ if isinstance(s, str): - return s.encode('utf8', 'ignore') + return s.encode("utf8", "ignore") if isinstance(s, bytes): return bytes(s) diff --git a/cloudbot/util/irc.py b/cloudbot/util/irc.py index da96961a..0de64a71 100644 --- a/cloudbot/util/irc.py +++ b/cloudbot/util/irc.py @@ -28,7 +28,9 @@ class ChannelMode: type = attr.ib(type=ModeType) def has_param(self, adding: bool) -> bool: - return self.type in PARAM_MODE_TYPES or (self.type is ModeType.C and adding) + return self.type in PARAM_MODE_TYPES or ( + self.type is ModeType.C and adding + ) @attr.s(hash=True) @@ -58,7 +60,9 @@ class StatusMode(ChannelMode): @classmethod def make(cls, prefix: str, char: str, level: int) -> "StatusMode": - return cls(prefix=prefix, level=level, character=char, type=ModeType.Status) + return cls( + prefix=prefix, level=level, character=char, type=ModeType.Status + ) def parse_mode_string( diff --git a/cloudbot/util/pager.py b/cloudbot/util/pager.py index db08a88e..c06e5ebc 100644 --- a/cloudbot/util/pager.py +++ b/cloudbot/util/pager.py @@ -125,7 +125,12 @@ def handle_lookup(self, text) -> List[str]: def paginated_list( - data, delim=" \u2022 ", suffix='...', max_len=256, page_size=2, pager_cls=Pager + data, + delim=" \u2022 ", + suffix="...", + max_len=256, + page_size=2, + pager_cls=Pager, ): """ >>> list(paginated_list(['abc', 'def'])) @@ -145,7 +150,7 @@ def get_delim(): if lines[-1]: return delim - return '' + return "" for item in data: if len(item) > max_len: diff --git a/cloudbot/util/sequence.py b/cloudbot/util/sequence.py index 80daffa2..d56a7368 100644 --- a/cloudbot/util/sequence.py +++ b/cloudbot/util/sequence.py @@ -11,4 +11,4 @@ def chunk_iter(data, chunk_size): :return: An iterable of all the chunks of the sequence """ for i in range(0, len(data), chunk_size): - yield data[i:i + chunk_size] + yield data[i : i + chunk_size] diff --git a/cloudbot/util/text.py b/cloudbot/util/text.py index 5ff7d00e..f5e5c772 100644 --- a/cloudbot/util/text.py +++ b/cloudbot/util/text.py @@ -1,6 +1,6 @@ from typing import Optional -__all__ = ('parse_bool',) +__all__ = ("parse_bool",) _STR_TO_BOOL = { "yes": True, diff --git a/cloudbot/util/textgen.py b/cloudbot/util/textgen.py index c91876c9..61567ad6 100644 --- a/cloudbot/util/textgen.py +++ b/cloudbot/util/textgen.py @@ -49,7 +49,9 @@ class TextGenerator(object): - def __init__(self, templates, parts, default_templates=None, variables=None): + def __init__( + self, templates, parts, default_templates=None, variables=None + ): self.templates = templates self.default_templates = default_templates self.parts = parts @@ -77,7 +79,9 @@ def generate_string(self, template=None): If no templates are specified, use a random template from the default_templates list. """ if self.default_templates: - text = self.templates[template or random.choice(self.default_templates)] + text = self.templates[ + template or random.choice(self.default_templates) + ] else: text = random.choice(self.templates) diff --git a/cloudbot/util/timeformat.py b/cloudbot/util/timeformat.py index 76f7faa6..0fe46a5d 100644 --- a/cloudbot/util/timeformat.py +++ b/cloudbot/util/timeformat.py @@ -94,7 +94,7 @@ def time_since(d, now=None, count=2, accuracy=6, simple=False): if since <= 0: # d is in the future compared to now, stop processing. - return '0 ' + 'minutes' + return "0 " + "minutes" # pass the number in seconds on to format_time to make the output string return format_time(since, count, accuracy, simple) @@ -170,14 +170,14 @@ def format(self, simple=True, skip_empty=True, count=3): class TimeUnits: - SECOND = TimeUnit(1, 's', 'second', 'seconds') - MINUTE = TimeUnit(60 * SECOND, 'm', 'minute', 'minutes') - HOUR = TimeUnit(60 * MINUTE, 'h', 'hour', 'hours') - DAY = TimeUnit(24 * HOUR, 'd', 'day', 'days') - MONTH = TimeUnit(30 * DAY, 'M', 'month', 'months') - YEAR = TimeUnit(365 * DAY, 'y', 'year', 'years') - DECADE = TimeUnit(10 * YEAR, 'D', 'decade', 'decades') - CENTURY = TimeUnit(10 * DECADE, 'c', 'century', 'centuries') + SECOND = TimeUnit(1, "s", "second", "seconds") + MINUTE = TimeUnit(60 * SECOND, "m", "minute", "minutes") + HOUR = TimeUnit(60 * MINUTE, "h", "hour", "hours") + DAY = TimeUnit(24 * HOUR, "d", "day", "days") + MONTH = TimeUnit(30 * DAY, "M", "month", "months") + YEAR = TimeUnit(365 * DAY, "y", "year", "years") + DECADE = TimeUnit(10 * YEAR, "D", "decade", "decades") + CENTURY = TimeUnit(10 * DECADE, "c", "century", "centuries") units = (CENTURY, DECADE, YEAR, MONTH, DAY, HOUR, MINUTE, SECOND) diff --git a/cloudbot/util/timeparse.py b/cloudbot/util/timeparse.py index 25e29825..23cf6cd8 100644 --- a/cloudbot/util/timeparse.py +++ b/cloudbot/util/timeparse.py @@ -37,56 +37,58 @@ import re -SIGN = r'(?P[+|-])?' +SIGN = r"(?P[+|-])?" # YEARS = r'(?P\d+)\s*(?:ys?|yrs?.?|years?)' # MONTHS = r'(?P\d+)\s*(?:mos?.?|mths?.?|months?)' -WEEKS = r'(?P[\d.]+)\s*(?:w|wks?|weeks?)' -DAYS = r'(?P[\d.]+)\s*(?:d|dys?|days?)' -HOURS = r'(?P[\d.]+)\s*(?:h|hrs?|hours?)' -MINS = r'(?P[\d.]+)\s*(?:m|(mins?)|(minutes?))' -SECS = r'(?P[\d.]+)\s*(?:s|secs?|seconds?)' -SEPARATORS = r'[,/]' -SEC_CLOCK = r':(?P\d{2}(?:\.\d+)?)' -MIN_CLOCK = r'(?P\d{1,2}):(?P\d{2}(?:\.\d+)?)' -HOUR_CLOCK = r'(?P\d+):(?P\d{2}):(?P\d{2}(?:\.\d+)?)' -DAY_CLOCK = (r'(?P\d+):(?P\d{2}):' - r'(?P\d{2}):(?P\d{2}(?:\.\d+)?)') - -OPT = lambda x: r'(?:{x})?'.format(x=x) -OPT_SEP = lambda x: r'(?:{x}\s*(?:{SEPARATORS}\s*)?)?'.format( - x=x, SEPARATORS=SEPARATORS) +WEEKS = r"(?P[\d.]+)\s*(?:w|wks?|weeks?)" +DAYS = r"(?P[\d.]+)\s*(?:d|dys?|days?)" +HOURS = r"(?P[\d.]+)\s*(?:h|hrs?|hours?)" +MINS = r"(?P[\d.]+)\s*(?:m|(mins?)|(minutes?))" +SECS = r"(?P[\d.]+)\s*(?:s|secs?|seconds?)" +SEPARATORS = r"[,/]" +SEC_CLOCK = r":(?P\d{2}(?:\.\d+)?)" +MIN_CLOCK = r"(?P\d{1,2}):(?P\d{2}(?:\.\d+)?)" +HOUR_CLOCK = r"(?P\d+):(?P\d{2}):(?P\d{2}(?:\.\d+)?)" +DAY_CLOCK = ( + r"(?P\d+):(?P\d{2}):" + r"(?P\d{2}):(?P\d{2}(?:\.\d+)?)" +) + +OPT = lambda x: r"(?:{x})?".format(x=x) +OPT_SEP = lambda x: r"(?:{x}\s*(?:{SEPARATORS}\s*)?)?".format( + x=x, SEPARATORS=SEPARATORS +) TIME_FORMATS = [ - r'{WEEKS}\s*{DAYS}\s*{HOURS}\s*{MINS}\s*{SECS}'.format( + r"{WEEKS}\s*{DAYS}\s*{HOURS}\s*{MINS}\s*{SECS}".format( # YEARS=OPT_SEP(YEARS), # MONTHS=OPT_SEP(MONTHS), WEEKS=OPT_SEP(WEEKS), DAYS=OPT_SEP(DAYS), HOURS=OPT_SEP(HOURS), MINS=OPT_SEP(MINS), - SECS=OPT(SECS)), - r'{MIN_CLOCK}'.format( - MIN_CLOCK=MIN_CLOCK), - r'{WEEKS}\s*{DAYS}\s*{HOUR_CLOCK}'.format( - WEEKS=OPT_SEP(WEEKS), - DAYS=OPT_SEP(DAYS), - HOUR_CLOCK=HOUR_CLOCK), - r'{DAY_CLOCK}'.format( - DAY_CLOCK=DAY_CLOCK), - r'{SEC_CLOCK}'.format( - SEC_CLOCK=SEC_CLOCK), + SECS=OPT(SECS), + ), + r"{MIN_CLOCK}".format(MIN_CLOCK=MIN_CLOCK), + r"{WEEKS}\s*{DAYS}\s*{HOUR_CLOCK}".format( + WEEKS=OPT_SEP(WEEKS), DAYS=OPT_SEP(DAYS), HOUR_CLOCK=HOUR_CLOCK + ), + r"{DAY_CLOCK}".format(DAY_CLOCK=DAY_CLOCK), + r"{SEC_CLOCK}".format(SEC_CLOCK=SEC_CLOCK), ] -MULTIPLIERS = dict([ - # ('years', 60 * 60 * 24 * 365), - # ('months', 60 * 60 * 24 * 30), - ('weeks', 60 * 60 * 24 * 7), - ('days', 60 * 60 * 24), - ('hours', 60 * 60), - ('mins', 60), - ('secs', 1) -]) +MULTIPLIERS = dict( + [ + # ('years', 60 * 60 * 24 * 365), + # ('months', 60 * 60 * 24 * 30), + ("weeks", 60 * 60 * 24 * 7), + ("days", 60 * 60 * 24), + ("hours", 60 * 60), + ("mins", 60), + ("secs", 1), + ] +) def _interpret_as_minutes(string, mdict): @@ -99,16 +101,20 @@ def _interpret_as_minutes(string, mdict): >>> pprint.pprint(_interpret_as_minutes('1:24', {'secs': '24', 'mins': '1'})) {'hours': '1', 'mins': '24'} """ - has_keys = mdict.get('hours') is None and mdict.get('days') is None and mdict.get('weeks') is None - if string.count(':') == 1 and '.' not in string and has_keys: - mdict['hours'] = mdict['mins'] - mdict['mins'] = mdict['secs'] - mdict.pop('secs') + has_keys = ( + mdict.get("hours") is None + and mdict.get("days") is None + and mdict.get("weeks") is None + ) + if string.count(":") == 1 and "." not in string and has_keys: + mdict["hours"] = mdict["mins"] + mdict["mins"] = mdict["secs"] + mdict.pop("secs") return mdict -def time_parse(string, granularity='seconds'): +def time_parse(string, granularity="seconds"): """ Parse a time expression, returning it as a number of seconds. If possible, the return value will be an `int`; if this is not @@ -146,28 +152,43 @@ def time_parse(string, granularity='seconds'): >>> time_parse('1:30', granularity='minutes') 5400 """ - match = re.match(r'\s*' + SIGN + r'\s*(?P.*)$', string) - sign = -1 if match.groupdict()['sign'] == '-' else 1 - string = match.groupdict()['unsigned'] + match = re.match(r"\s*" + SIGN + r"\s*(?P.*)$", string) + sign = -1 if match.groupdict()["sign"] == "-" else 1 + string = match.groupdict()["unsigned"] for timefmt in TIME_FORMATS: - match = re.match(r'\s*' + timefmt + r'\s*$', string, re.I) + match = re.match(r"\s*" + timefmt + r"\s*$", string, re.I) if match and match.group(0).strip(): mdict = match.groupdict() - if granularity == 'minutes': + if granularity == "minutes": mdict = _interpret_as_minutes(string, mdict) # if all of the fields are integer numbers if all(v.isdigit() for v in list(mdict.values()) if v): - return sign * sum([MULTIPLIERS[k] * int(v, 10) for (k, v) in - list(mdict.items()) if v is not None]) + return sign * sum( + [ + MULTIPLIERS[k] * int(v, 10) + for (k, v) in list(mdict.items()) + if v is not None + ] + ) # if SECS is an integer number - if mdict.get('secs') is None or mdict['secs'].isdigit(): + if mdict.get("secs") is None or mdict["secs"].isdigit(): # we will return an integer - return ( - sign * int(sum([MULTIPLIERS[k] * float(v) for (k, v) in - list(mdict.items()) if k != 'secs' and v is not None])) + - (int(mdict['secs'], 10) if mdict['secs'] else 0)) + return sign * int( + sum( + [ + MULTIPLIERS[k] * float(v) + for (k, v) in list(mdict.items()) + if k != "secs" and v is not None + ] + ) + ) + (int(mdict["secs"], 10) if mdict["secs"] else 0) # SECS is a float, we will return a float - return sign * sum([MULTIPLIERS[k] * float(v) for (k, v) in - list(mdict.items()) if v is not None]) + return sign * sum( + [ + MULTIPLIERS[k] * float(v) + for (k, v) in list(mdict.items()) + if v is not None + ] + ) diff --git a/cloudbot/util/web.py b/cloudbot/util/web.py index 9a2ded90..d9d509f1 100644 --- a/cloudbot/util/web.py +++ b/cloudbot/util/web.py @@ -20,15 +20,15 @@ from typing import Optional import requests -from requests import RequestException, Response, PreparedRequest, HTTPError +from requests import HTTPError, PreparedRequest, RequestException, Response # Constants -DEFAULT_SHORTENER = 'is.gd' -DEFAULT_PASTEBIN = '' +DEFAULT_SHORTENER = "is.gd" +DEFAULT_PASTEBIN = "" -HASTEBIN_SERVER = 'https://hastebin.com' +HASTEBIN_SERVER = "https://hastebin.com" -logger = logging.getLogger('cloudbot') +logger = logging.getLogger("cloudbot") # Shortening / pasting @@ -53,7 +53,7 @@ def should_use(self): if self.working: return True - if (time.time() - self.last_check) > (5*60): + if (time.time() - self.last_check) > (5 * 60): # It's been 5 minutes, try again self.working = True return True @@ -79,16 +79,13 @@ def get(self, name): def get_item(self, name): return self._items.get(name) - def get_working(self) -> Optional['Item']: - working = [ - item for item in self._items.values() - if item.should_use - ] + def get_working(self) -> Optional["Item"]: + working = [item for item in self._items.values() if item.should_use] if not working: return None - return min(working, key=attrgetter('uses')) + return min(working, key=attrgetter("uses")) def remove(self, name): del self._items[name] @@ -137,7 +134,7 @@ class NoPasteException(Exception): """No pastebins succeeded""" -def paste(data, ext='txt', service=DEFAULT_PASTEBIN, raise_on_no_paste=False): +def paste(data, ext="txt", service=DEFAULT_PASTEBIN, raise_on_no_paste=False): if service: impl = pastebins.get_item(service) else: @@ -174,7 +171,7 @@ class ServiceHTTPError(ServiceError): def __init__(self, message: str, response: Response): super().__init__( response.request, - '[HTTP {}] {}'.format(response.status_code, message) + "[HTTP {}] {}".format(response.status_code, message), ) self.message = message self.response = response @@ -203,10 +200,10 @@ def expand(self, url): except RequestException as e: raise ServiceError(e.request, "Connection error occurred") from e - if 'location' in r.headers: - return r.headers['location'] + if "location" in r.headers: + return r.headers["location"] - raise ServiceHTTPError('That URL does not exist', r) + raise ServiceHTTPError("That URL does not exist", r) class Pastebin: @@ -225,9 +222,9 @@ def paste(self, data, ext): class Isgd(Shortener): def shorten(self, url, custom=None, key=None): - p = {'url': url, 'shorturl': custom, 'format': 'json'} + p = {"url": url, "shorturl": custom, "format": "json"} try: - r = requests.get('http://is.gd/create.php', params=p) + r = requests.get("http://is.gd/create.php", params=p) r.raise_for_status() except HTTPError as e: r = e.response @@ -237,15 +234,15 @@ def shorten(self, url, custom=None, key=None): j = r.json() - if 'shorturl' in j: - return j['shorturl'] + if "shorturl" in j: + return j["shorturl"] - raise ServiceHTTPError(j['errormessage'], r) + raise ServiceHTTPError(j["errormessage"], r) def expand(self, url): - p = {'shorturl': url, 'format': 'json'} + p = {"shorturl": url, "format": "json"} try: - r = requests.get('http://is.gd/forward.php', params=p) + r = requests.get("http://is.gd/forward.php", params=p) r.raise_for_status() except HTTPError as e: r = e.response @@ -255,19 +252,24 @@ def expand(self, url): j = r.json() - if 'url' in j: - return j['url'] + if "url" in j: + return j["url"] - raise ServiceHTTPError(j['errormessage'], r) + raise ServiceHTTPError(j["errormessage"], r) class Googl(Shortener): def shorten(self, url, custom=None, key=None): - h = {'content-type': 'application/json'} - k = {'key': key} - p = {'longUrl': url} + h = {"content-type": "application/json"} + k = {"key": key} + p = {"longUrl": url} try: - r = requests.post('https://www.googleapis.com/urlshortener/v1/url', params=k, data=json.dumps(p), headers=h) + r = requests.post( + "https://www.googleapis.com/urlshortener/v1/url", + params=k, + data=json.dumps(p), + headers=h, + ) r.raise_for_status() except HTTPError as e: r = e.response @@ -277,15 +279,17 @@ def shorten(self, url, custom=None, key=None): j = r.json() - if 'error' not in j: - return j['id'] + if "error" not in j: + return j["id"] - raise ServiceHTTPError(j['error']['message'], r) + raise ServiceHTTPError(j["error"]["message"], r) def expand(self, url): - p = {'shortUrl': url} + p = {"shortUrl": url} try: - r = requests.get('https://www.googleapis.com/urlshortener/v1/url', params=p) + r = requests.get( + "https://www.googleapis.com/urlshortener/v1/url", params=p + ) r.raise_for_status() except HTTPError as e: r = e.response @@ -295,17 +299,17 @@ def expand(self, url): j = r.json() - if 'error' not in j: - return j['longUrl'] + if "error" not in j: + return j["longUrl"] - raise ServiceHTTPError(j['error']['message'], r) + raise ServiceHTTPError(j["error"]["message"], r) class Gitio(Shortener): def shorten(self, url, custom=None, key=None): - p = {'url': url, 'code': custom} + p = {"url": url, "code": custom} try: - r = requests.post('http://git.io', data=p) + r = requests.post("http://git.io", data=p) r.raise_for_status() except HTTPError as e: r = e.response @@ -314,9 +318,9 @@ def shorten(self, url, custom=None, key=None): raise ServiceError(e.request, "Connection error occurred") from e if r.status_code == requests.codes.created: - s = r.headers['location'] + s = r.headers["location"] if custom and custom not in s: - raise ServiceHTTPError('That URL is already in use', r) + raise ServiceHTTPError("That URL is already in use", r) return s @@ -335,7 +339,7 @@ def paste(self, data, ext): encoded = data try: - r = requests.post(self.url + '/documents', data=encoded) + r = requests.post(self.url + "/documents", data=encoded) r.raise_for_status() except HTTPError as e: r = e.response @@ -346,13 +350,13 @@ def paste(self, data, ext): j = r.json() if r.status_code is requests.codes.ok: - return '{}/{}.{}'.format(self.url, j['key'], ext) + return "{}/{}.{}".format(self.url, j["key"], ext) - raise ServiceHTTPError(j['message'], r) + raise ServiceHTTPError(j["message"], r) -pastebins.register('hastebin', Hastebin(HASTEBIN_SERVER)) +pastebins.register("hastebin", Hastebin(HASTEBIN_SERVER)) -shorteners.register('git.io', Gitio()) -shorteners.register('goo.gl', Googl()) -shorteners.register('is.gd', Isgd()) +shorteners.register("git.io", Gitio()) +shorteners.register("goo.gl", Googl()) +shorteners.register("is.gd", Isgd()) diff --git a/format_json.py b/format_json.py index 40b1dc3d..33ad5f10 100644 --- a/format_json.py +++ b/format_json.py @@ -6,13 +6,13 @@ import json from pathlib import Path -if __name__ == '__main__': +if __name__ == "__main__": path = Path().resolve() for file in path.rglob("*.json"): print(file) - with file.open(encoding='utf8') as f: + with file.open(encoding="utf8") as f: data = json.load(f, object_pairs_hook=collections.OrderedDict) - with file.open('w', encoding='utf8') as f: + with file.open("w", encoding="utf8") as f: print(json.dumps(data, indent=4), file=f) diff --git a/plugins/admin_channel.py b/plugins/admin_channel.py index 5a36f275..b8c0d35f 100644 --- a/plugins/admin_channel.py +++ b/plugins/admin_channel.py @@ -36,13 +36,15 @@ def mode_cmd(mode, text, text_inp, chan, nick, event, mode_warn=True): target = split[0] event.notice("Attempting to {} {} in {}...".format(text, target, channel)) - event.admin_log(MODE_CMD_LOG.format( - nick=nick, - cmd=text, - mode=mode, - target=target, - channel=channel, - )) + event.admin_log( + MODE_CMD_LOG.format( + nick=nick, + cmd=text, + mode=mode, + target=target, + channel=channel, + ) + ) event.conn.send("MODE {} {} {}".format(channel, mode, target)) return True @@ -60,12 +62,14 @@ def mode_cmd_no_target(mode, text, text_inp, chan, event, mode_warn=True): channel = chan event.notice("Attempting to {} {}...".format(text, channel)) - event.admin_log(MODE_CMD_NO_TARGET_LOG.format( - nick=event.nick, - cmd=text, - mode=mode, - channel=channel, - )) + event.admin_log( + MODE_CMD_NO_TARGET_LOG.format( + nick=event.nick, + cmd=text, + mode=mode, + channel=channel, + ) + ) event.conn.send("MODE {} {}".format(channel, mode)) return True @@ -108,7 +112,7 @@ def quiet(text, chan, nick, event): if mode_cmd("+q", "quiet", text, chan, nick, event, False): return - if not do_extban('m', "quiet", text, chan, nick, event, True): + if not do_extban("m", "quiet", text, chan, nick, event, True): event.notice("Unable to set +q or a mute extban on this network.") @@ -118,7 +122,7 @@ def unquiet(text, chan, nick, event): if mode_cmd("-q", "unquiet", text, chan, nick, event, False): return - if not do_extban('m', "unquiet", text, chan, nick, event, False): + if not do_extban("m", "unquiet", text, chan, nick, event, False): event.notice("Unable to unset +q or a mute extban on this network.") @@ -149,7 +153,7 @@ def deop(text, chan, nick, event): @hook.command(permissions=["op_topic", "op", "chanop"]) def topic(text, conn, chan, nick, event): """[channel] - changes the topic to in [channel], or in the caller's channel - if no channel is specified""" + if no channel is specified""" split = text.split(" ") if split[0].startswith("#"): msg = " ".join(split[1:]) @@ -198,9 +202,9 @@ def remove(text, chan, conn, nick, event): else: reason = "requested by {}.".format(nick) out = "REMOVE {} {} :{}".format(user, chan, reason) - event.admin_log(REMOVE_LOG.format( - nick=nick, target=user, channel=chan, reason=reason - )) + event.admin_log( + REMOVE_LOG.format(nick=nick, target=user, channel=chan, reason=reason) + ) conn.send(out) diff --git a/plugins/animal_gifs.py b/plugins/animal_gifs.py index 22a5771b..238a40cc 100644 --- a/plugins/animal_gifs.py +++ b/plugins/animal_gifs.py @@ -17,8 +17,11 @@ def get_gifs(url): soup = get_soup(url) - container = soup.find('div', class_="row") - gifs = [urljoin(url, elem["data-src"]) for elem in container.find_all('img', {'data-src': True})] + container = soup.find("div", class_="row") + gifs = [ + urljoin(url, elem["data-src"]) + for elem in container.find_all("img", {"data-src": True}) + ] return gifs diff --git a/plugins/badwords.py b/plugins/badwords.py index 892a9310..d008b78b 100644 --- a/plugins/badwords.py +++ b/plugins/badwords.py @@ -1,22 +1,23 @@ import re from collections import defaultdict +from typing import Dict, List -from sqlalchemy import Table, Column, String, PrimaryKeyConstraint, select +from sqlalchemy import Column, PrimaryKeyConstraint, String, Table, select from cloudbot import hook from cloudbot.event import EventType from cloudbot.util import database table = Table( - 'badwords', + "badwords", database.metadata, - Column('word', String), - Column('nick', String), - Column('chan', String), - PrimaryKeyConstraint('word', 'chan') + Column("word", String), + Column("nick", String), + Column("chan", String), + PrimaryKeyConstraint("word", "chan"), ) -badcache = defaultdict(list) +badcache: Dict[str, List[str]] = defaultdict(list) class BadwordMatcher: @@ -37,7 +38,8 @@ def load_bad(db): words.append(word) new_regex = re.compile( - r'(\s|^|[^\w\s])({0})(\s|$|[^\w\s])'.format('|'.join(words)), re.IGNORECASE + r"(\s|^|[^\w\s])({0})(\s|$|[^\w\s])".format("|".join(words)), + re.IGNORECASE, ) matcher.regex = new_regex @@ -51,7 +53,7 @@ def add_bad(text, nick, db): """ - adds a bad word to the auto kick list must specify a channel with each word""" splt = text.lower().split(None, 1) word, channel = splt - if not channel.startswith('#'): + if not channel.startswith("#"): return "Please specify a valid channel name after the bad word." word = re.escape(word) @@ -62,9 +64,9 @@ def add_bad(text, nick, db): ) if len(badcache[channel]) >= 10: - return "There are too many words listed for channel {}. Please remove a word using .rmbad before adding " \ - "anymore. For a list of bad words use .listbad".format( - channel + return ( + "There are too many words listed for channel {}. Please remove a word using .rmbad before adding " + "anymore. For a list of bad words use .listbad".format(channel) ) db.execute(table.insert().values(word=word, nick=nick, chan=channel)) @@ -79,10 +81,14 @@ def del_bad(text, db): """ - removes the specified word from the specified channels bad word list""" splt = text.lower().split(None, 1) word, channel = splt - if not channel.startswith('#'): + if not channel.startswith("#"): return "Please specify a valid channel name after the bad word." - db.execute(table.delete().where(table.c.word == word).where(table.c.chan == channel)) + db.execute( + table.delete() + .where(table.c.word == word) + .where(table.c.chan == channel) + ) db.commit() newlist = list_bad(channel) load_bad(db) @@ -94,11 +100,11 @@ def del_bad(text, db): @hook.command("listbad", permissions=["badwords"]) def list_bad(text): """ - Returns a list of bad words specify a channel to see words for a particular channel""" - text = text.split(' ')[0].lower() - if not text.startswith('#'): + text = text.split(" ")[0].lower() + if not text.startswith("#"): return "Please specify a valid channel name" - return '|'.join(badcache[text]) + return "|".join(badcache[text]) @hook.event([EventType.message, EventType.action], singlethread=True) diff --git a/plugins/bible.py b/plugins/bible.py index b644df5a..0d331e72 100644 --- a/plugins/bible.py +++ b/plugins/bible.py @@ -7,21 +7,19 @@ def bible(text, reply): """ - Prints the specified passage from the Bible""" passage = text.strip() - params = { - 'passage': passage, - 'formatting': 'plain', - 'type': 'json' - } + params = {"passage": passage, "formatting": "plain", "type": "json"} try: r = requests.get("https://labs.bible.org/api", params=params) r.raise_for_status() response = r.json()[0] except Exception: - reply("Something went wrong, either you entered an invalid passage or the API is down.") + reply( + "Something went wrong, either you entered an invalid passage or the API is down." + ) raise - book = response['bookname'] - ch = response['chapter'] - ver = response['verse'] - txt = response['text'] + book = response["bookname"] + ch = response["chapter"] + ver = response["verse"] + txt = response["text"] return "\x02{} {}:{}\x02 {}".format(book, ch, ver, txt) diff --git a/plugins/books.py b/plugins/books.py index bc4166a4..2eb905b1 100644 --- a/plugins/books.py +++ b/plugins/books.py @@ -5,18 +5,20 @@ from cloudbot.bot import bot from cloudbot.util import formatting, web -base_url = 'https://www.googleapis.com/books/v1/' -book_search_api = base_url + 'volumes?' +base_url = "https://www.googleapis.com/books/v1/" +book_search_api = base_url + "volumes?" @hook.command("books", "gbooks") def books(text, reply): """ - Searches Google Books for .""" - dev_key = bot.config.get_api_key('google_dev_key') + dev_key = bot.config.get_api_key("google_dev_key") if not dev_key: return "This command requires a Google Developers Console API key." - request = requests.get(book_search_api, params={"q": text, "key": dev_key, "country": "US"}) + request = requests.get( + book_search_api, params={"q": text, "key": dev_key, "country": "US"} + ) try: request.raise_for_status() @@ -26,41 +28,45 @@ def books(text, reply): json = request.json() - if json.get('error'): - if json['error']['code'] == 403: + if json.get("error"): + if json["error"]["code"] == 403: return "The Books API is off in the Google Developers Console (or check the console)." - return 'Error performing search.' + return "Error performing search." - if json['totalItems'] == 0: - return 'No results found.' + if json["totalItems"] == 0: + return "No results found." - book = json['items'][0]['volumeInfo'] - title = book['title'] + book = json["items"][0]["volumeInfo"] + title = book["title"] try: - author = book['authors'][0] + author = book["authors"][0] except KeyError: try: - author = book['publisher'] + author = book["publisher"] except KeyError: author = "Unknown Author" try: - description = formatting.truncate_str(book['description'], 130) + description = formatting.truncate_str(book["description"], 130) except KeyError: description = "No description available." try: - year = book['publishedDate'][:4] + year = book["publishedDate"][:4] except KeyError: year = "No Year" try: - page_count = book['pageCount'] - pages = ' - \x02{:,}\x02 page{}'.format(page_count, "s"[page_count == 1:]) + page_count = book["pageCount"] + pages = " - \x02{:,}\x02 page{}".format( + page_count, "s"[page_count == 1 :] + ) except KeyError: - pages = '' + pages = "" - link = web.shorten(book['infoLink'], service="goo.gl", key=dev_key) + link = web.shorten(book["infoLink"], service="goo.gl", key=dev_key) - return "\x02{}\x02 by \x02{}\x02 ({}){} - {} - {}".format(title, author, year, pages, description, link) + return "\x02{}\x02 by \x02{}\x02 ({}){} - {} - {}".format( + title, author, year, pages, description, link + ) diff --git a/plugins/brainfuck.py b/plugins/brainfuck.py index 735e8efb..9d71a279 100644 --- a/plugins/brainfuck.py +++ b/plugins/brainfuck.py @@ -17,14 +17,14 @@ class UnbalancedBrackets(ValueError): class BrainfuckProgram: def __init__(self, text): self.op_map = { - '+': self.inc, - '-': self.dec, - '>': self.next_cell, - '<': self.prev_cell, - '.': self.print, - ',': self.set_random, - '[': self.loop_enter, - ']': self.loop_exit, + "+": self.inc, + "-": self.dec, + ">": self.next_cell, + "<": self.prev_cell, + ".": self.print, + ",": self.set_random, + "[": self.loop_enter, + "]": self.loop_exit, } self.ip = 0 # instruction pointer @@ -41,9 +41,9 @@ def populate_brackets(self): open_brackets = [] bracket_map = {} for pos, c in enumerate(self.text): - if c == '[': + if c == "[": open_brackets.append(pos) - elif c == ']': + elif c == "]": if not open_brackets: raise UnbalancedBrackets() @@ -111,7 +111,7 @@ def bf(text): :type text: str """ - program_text = re.sub(r'[^][<>+\-.,]', '', text) + program_text = re.sub(r"[^][<>+\-.,]", "", text) try: program = BrainfuckProgram(program_text) @@ -131,7 +131,7 @@ def bf(text): program.output += "(exceeded {} iterations)".format(MAX_STEPS) break - stripped_output = re.sub(r'[\x00-\x1F]', '', program.output) + stripped_output = re.sub(r"[\x00-\x1F]", "", program.output) if not stripped_output: if program.output: diff --git a/plugins/brew.py b/plugins/brew.py index eda20923..43b49ce0 100644 --- a/plugins/brew.py +++ b/plugins/brew.py @@ -7,14 +7,14 @@ api_url = "http://api.brewerydb.com/v2/search?format=json" -@hook.command('brew') +@hook.command("brew") def brew(text, reply): """ - returns the first brewerydb search result for """ - api_key = bot.config.get_api_key('brewerydb') + api_key = bot.config.get_api_key("brewerydb") if not api_key: return "No brewerydb API key set." - params = {'key': api_key, 'type': 'beer', 'withBreweries': 'Y', 'q': text} + params = {"key": api_key, "type": "beer", "withBreweries": "Y", "q": text} request = requests.get(api_url, params=params) try: @@ -28,35 +28,36 @@ def brew(text, reply): output = "No results found." try: - if 'totalResults' in response: - if response['totalResults'] == 0: + if "totalResults" in response: + if response["totalResults"] == 0: return output - beer = response['data'][0] - brewery = beer['breweries'][0] + beer = response["data"][0] + brewery = beer["breweries"][0] - style = 'unknown style' - if 'style' in beer: - style = beer['style']['shortName'] + style = "unknown style" + if "style" in beer: + style = beer["style"]["shortName"] - abv = '?.?' - if 'abv' in beer: - abv = beer['abv'] + abv = "?.?" + if "abv" in beer: + abv = beer["abv"] - url = '[no website found]' - if 'website' in brewery: - url = brewery['website'] + url = "[no website found]" + if "website" in brewery: + url = brewery["website"] content = { - 'name': beer['nameDisplay'], - 'style': style, - 'abv': abv, - 'brewer': brewery['name'], - 'url': url + "name": beer["nameDisplay"], + "style": style, + "abv": abv, + "brewer": brewery["name"], + "url": url, } - output = "{name} by {brewer} ({style}, {abv}% ABV) - {url}" \ - .format(**content) + output = "{name} by {brewer} ({style}, {abv}% ABV) - {url}".format( + **content + ) except Exception: reply("Error parsing results.") diff --git a/plugins/cats.py b/plugins/cats.py index 180379a3..57f5035e 100644 --- a/plugins/cats.py +++ b/plugins/cats.py @@ -6,7 +6,9 @@ def get_data(url, reply, bot, params=None): try: - r = requests.get(url, headers={'User-Agent': bot.user_agent}, params=params) + r = requests.get( + url, headers={"User-Agent": bot.user_agent}, params=params + ) r.raise_for_status() except HTTPError: reply("API error occurred.") @@ -18,9 +20,11 @@ def get_data(url, reply, bot, params=None): @hook.command(autohelp=False) def cats(reply, bot): """- gets a fucking fact about cats.""" - r = get_data('https://catfact.ninja/fact', reply, bot, params={'max_length': 100}) + r = get_data( + "https://catfact.ninja/fact", reply, bot, params={"max_length": 100} + ) json = r.json() - response = json['fact'] + response = json["fact"] return response diff --git a/plugins/chain.py b/plugins/chain.py index dca863e0..27c61388 100644 --- a/plugins/chain.py +++ b/plugins/chain.py @@ -1,7 +1,8 @@ import itertools from operator import attrgetter +from typing import Dict -from sqlalchemy import Table, Column, String, Boolean, PrimaryKeyConstraint +from sqlalchemy import Boolean, Column, PrimaryKeyConstraint, String, Table from cloudbot import hook from cloudbot.event import CommandEvent @@ -9,14 +10,14 @@ from cloudbot.util.formatting import chunk_str, pluralize_auto commands = Table( - 'chain_commands', + "chain_commands", database.metadata, - Column('hook', String), - Column('allowed', Boolean, default=True), - PrimaryKeyConstraint('hook', 'allowed') + Column("hook", String), + Column("allowed", Boolean, default=True), + PrimaryKeyConstraint("hook", "allowed"), ) -allow_cache = {} +allow_cache: Dict[str, bool] = {} @hook.on_start @@ -35,7 +36,7 @@ def format_hook_name(_hook): def get_hook_from_command(bot, hook_name): manager = bot.plugin_manager - if '.' in hook_name: + if "." in hook_name: for _hook in manager.commands.values(): if format_hook_name(_hook) == hook_name: return _hook @@ -74,7 +75,7 @@ def chainallow(text, db, notice_doc, bot): hook_name = format_hook_name(_hook) if subcmd == "add": - values = {'hook': hook_name} + values = {"hook": hook_name} if args: allow = args.pop(0).lower() if allow == "allow": @@ -84,10 +85,14 @@ def chainallow(text, db, notice_doc, bot): else: return notice_doc() - values['allowed'] = allow + values["allowed"] = allow updated = True - res = db.execute(commands.update().values(**values).where(commands.c.hook == hook_name)) + res = db.execute( + commands.update() + .values(**values) + .where(commands.c.hook == hook_name) + ) if res.rowcount == 0: updated = False db.execute(commands.insert().values(**values)) @@ -95,7 +100,9 @@ def chainallow(text, db, notice_doc, bot): db.commit() load_cache(db) if updated: - return "Updated state of '{}' in chainallow to allowed={}".format(hook_name, allow_cache.get(hook_name)) + return "Updated state of '{}' in chainallow to allowed={}".format( + hook_name, allow_cache.get(hook_name) + ) if allow_cache.get(hook_name): return "Added '{}' as an allowed command".format(hook_name) @@ -112,11 +119,11 @@ def chainallow(text, db, notice_doc, bot): def parse_chain(text, bot): - parts = text.split('|') + parts = text.split("|") cmds = [] for part in parts: - cmd, _, args = part.strip().partition(' ') + cmd, _, args = part.strip().partition(" ") _hook = get_hook_from_command(bot, cmd) cmds.append([cmd, _hook, args or ""]) @@ -129,7 +136,13 @@ def is_hook_allowed(_hook): def wrap_event(_hook, event, cmd, args): - cmd_event = CommandEvent(base_event=event, text=args.strip(), triggered_command=cmd, hook=_hook, cmd_prefix='') + cmd_event = CommandEvent( + base_event=event, + text=args.strip(), + triggered_command=cmd, + hook=_hook, + cmd_prefix="", + ) return cmd_event @@ -144,13 +157,21 @@ async def chain(text, bot, event): return "Unable to find command '{}'".format(name) if not is_hook_allowed(_hook): - event.notice("'{}' may not be used in command piping".format(format_hook_name(_hook))) + event.notice( + "'{}' may not be used in command piping".format( + format_hook_name(_hook) + ) + ) return if _hook.permissions: allowed = await event.check_permissions(_hook.permissions) if not allowed: - event.notice("Sorry, you are not allowed to use '{}'.".format(format_hook_name(_hook))) + event.notice( + "Sorry, you are not allowed to use '{}'.".format( + format_hook_name(_hook) + ) + ) return buffer = "" @@ -220,7 +241,11 @@ def action(msg, target=None): @hook.command(autohelp=False) def chainlist(notice, bot): """- Returns the list of commands allowed in 'chain'""" - hooks = [get_hook_from_command(bot, name) for name, allowed in allow_cache.items() if allowed] + hooks = [ + get_hook_from_command(bot, name) + for name, allowed in allow_cache.items() + if allowed + ] cmds = itertools.chain.from_iterable(map(attrgetter("aliases"), hooks)) cmds = sorted(cmds) for part in chunk_str(", ".join(cmds)): diff --git a/plugins/cheer.py b/plugins/cheer.py index bcac980e..c8e1f830 100644 --- a/plugins/cheer.py +++ b/plugins/cheer.py @@ -1,20 +1,21 @@ import random import re from pathlib import Path +from typing import List from cloudbot import hook -cheer_re = re.compile(r'\\o/', re.IGNORECASE) +cheer_re = re.compile(r"\\o/", re.IGNORECASE) -cheers = [] +cheers: List[str] = [] @hook.on_start def load_cheers(bot): cheers.clear() data_file = Path(bot.data_dir) / "cheers.txt" - with data_file.open(encoding='utf-8') as f: - cheers.extend(line.strip() for line in f if not line.startswith('//')) + with data_file.open(encoding="utf-8") as f: + cheers.extend(line.strip() for line in f if not line.startswith("//")) @hook.regex(cheer_re) diff --git a/plugins/core/autojoin.py b/plugins/core/autojoin.py index ebd1b41d..c3885a12 100644 --- a/plugins/core/autojoin.py +++ b/plugins/core/autojoin.py @@ -2,41 +2,44 @@ from collections import defaultdict from copy import copy from threading import RLock +from typing import Dict, Set -from sqlalchemy import PrimaryKeyConstraint, Column, String, Table, and_ +from sqlalchemy import Column, PrimaryKeyConstraint, String, Table, and_ from sqlalchemy.exc import IntegrityError from cloudbot import hook from cloudbot.util import database table = Table( - 'autojoin', + "autojoin", database.metadata, - Column('conn', String), - Column('chan', String), - PrimaryKeyConstraint('conn', 'chan') + Column("conn", String), + Column("chan", String), + PrimaryKeyConstraint("conn", "chan"), ) -chan_cache = defaultdict(set) +chan_cache: Dict[str, Set[str]] = defaultdict(set) db_lock = RLock() def get_channels(db, conn): - return db.execute(table.select().where(table.c.conn == conn.name.casefold())).fetchall() + return db.execute( + table.select().where(table.c.conn == conn.name.casefold()) + ).fetchall() @hook.on_start def load_cache(db): new_cache = defaultdict(set) for row in db.execute(table.select()): - new_cache[row['conn']].add(row['chan']) + new_cache[row["conn"]].add(row["chan"]) with db_lock: chan_cache.clear() chan_cache.update(new_cache) -@hook.irc_raw('376') +@hook.irc_raw("376") async def do_joins(conn): while not conn.ready: await asyncio.sleep(1) @@ -47,14 +50,18 @@ async def do_joins(conn): await asyncio.sleep(join_throttle) -@hook.irc_raw('JOIN', singlethread=True) +@hook.irc_raw("JOIN", singlethread=True) def add_chan(db, conn, chan, nick): chans = chan_cache[conn.name] chan = chan.casefold() if nick.casefold() == conn.nick.casefold() and chan not in chans: with db_lock: try: - db.execute(table.insert().values(conn=conn.name.casefold(), chan=chan.casefold())) + db.execute( + table.insert().values( + conn=conn.name.casefold(), chan=chan.casefold() + ) + ) except IntegrityError: db.rollback() else: @@ -63,17 +70,23 @@ def add_chan(db, conn, chan, nick): load_cache(db) -@hook.irc_raw('PART', singlethread=True) +@hook.irc_raw("PART", singlethread=True) def on_part(db, conn, chan, nick): if nick.casefold() == conn.nick.casefold(): with db_lock: db.execute( - table.delete().where(and_(table.c.conn == conn.name.casefold(), table.c.chan == chan.casefold()))) + table.delete().where( + and_( + table.c.conn == conn.name.casefold(), + table.c.chan == chan.casefold(), + ) + ) + ) db.commit() load_cache(db) -@hook.irc_raw('KICK', singlethread=True) +@hook.irc_raw("KICK", singlethread=True) def on_kick(db, conn, chan, target): on_part(db, conn, chan, target) diff --git a/plugins/core/cap.py b/plugins/core/cap.py index 7ffbc954..77ceec53 100644 --- a/plugins/core/cap.py +++ b/plugins/core/cap.py @@ -26,7 +26,9 @@ async def handle_available_caps(conn, caplist, event, irc_paramlist, bot): for cap in caplist: name = cap.name name_cf = name.casefold() - cap_event = partial(CapEvent, base_event=event, cap=name, cap_param=cap.value) + cap_event = partial( + CapEvent, base_event=event, cap=name, cap_param=cap.value + ) tasks = [ bot.plugin_manager.internal_launch(_hook, cap_event(hook=_hook)) for _hook in bot.plugin_manager.cap_hooks["on_available"][name_cf] @@ -36,7 +38,7 @@ async def handle_available_caps(conn, caplist, event, irc_paramlist, bot): cap_queue[name_cf] = async_util.create_future(conn.loop) conn.cmd("CAP", "REQ", name) - if irc_paramlist[2] != '*': + if irc_paramlist[2] != "*": await asyncio.gather(*cap_queue.values()) cap_queue.clear() conn.send("CAP END") @@ -63,7 +65,9 @@ async def _launch_handler(subcmd, event, **kwargs): except LookupError: return - await async_util.run_func_with_args(event.loop, handler, ChainMap(event, kwargs)) + await async_util.run_func_with_args( + event.loop, handler, ChainMap(event, kwargs) + ) @_subcmd_handler("LS") @@ -73,7 +77,7 @@ async def cap_ls(conn, caplist, event, irc_paramlist, bot): async def handle_req_resp(enabled, conn, caplist, event, bot): - server_caps = conn.memory.setdefault('server_caps', {}) + server_caps = conn.memory.setdefault("server_caps", {}) cap_queue = conn.memory.get("cap_queue", {}) caps = (cap.name.casefold() for cap in caplist) for cap in caps: @@ -114,8 +118,10 @@ async def cap_new(caplist, conn, event, bot, irc_paramlist): @_subcmd_handler("DEL") def cap_del(conn, caplist): # TODO add hooks for CAP removal - logger.info("[%s|cap] Capabilities removed by server: %s", conn.name, caplist) - server_caps = conn.memory.setdefault('server_caps', {}) + logger.info( + "[%s|cap] Capabilities removed by server: %s", conn.name, caplist + ) + server_caps = conn.memory.setdefault("server_caps", {}) for cap in caplist: server_caps[cap.name.casefold()] = False diff --git a/plugins/core/chan_key_db.py b/plugins/core/chan_key_db.py index 690157f6..c63d16e0 100644 --- a/plugins/core/chan_key_db.py +++ b/plugins/core/chan_key_db.py @@ -34,14 +34,18 @@ def load_keys(conn: IrcClient, db) -> None: """ Load channel keys to the client """ - query = select([table.c.chan, table.c.key], table.c.conn == conn.name.lower()) + query = select( + [table.c.chan, table.c.key], table.c.conn == conn.name.lower() + ) conn.clear_channel_keys() for row in db.execute(query): conn.set_channel_key(row["chan"], row["key"]) @hook.irc_raw("MODE") -def handle_modes(irc_paramlist: List[str], conn: IrcClient, db, chan: str) -> None: +def handle_modes( + irc_paramlist: List[str], conn: IrcClient, db, chan: str +) -> None: """ Handle mode changes """ @@ -51,7 +55,9 @@ def handle_modes(irc_paramlist: List[str], conn: IrcClient, db, chan: str) -> No modes = irc_paramlist[1] mode_params = list(irc_paramlist[2:]) serv_info = get_server_info(conn) - mode_changes = parse_mode_string(modes, mode_params, get_channel_modes(serv_info)) + mode_changes = parse_mode_string( + modes, mode_params, get_channel_modes(serv_info) + ) updated = False for change in mode_changes: if change.char == "k": @@ -82,7 +88,10 @@ def make_clause(conn: Client, chan: str) -> BooleanClauseList: """ Generate a WHERE clause to match keys for this conn+channel """ - return and_(table.c.conn == conn.name.lower(), table.c.chan == chan.lower(),) + return and_( + table.c.conn == conn.name.lower(), + table.c.chan == chan.lower(), + ) def clear_key(db: Session, conn, chan: str) -> None: @@ -92,7 +101,9 @@ def clear_key(db: Session, conn, chan: str) -> None: db.execute(table.delete().where(make_clause(conn, chan))) -def set_key(db: Session, conn: IrcClient, chan: str, key: Optional[str]) -> None: +def set_key( + db: Session, conn: IrcClient, chan: str, key: Optional[str] +) -> None: """ Set the key for a channel """ @@ -106,7 +117,9 @@ def set_key(db: Session, conn: IrcClient, chan: str, key: Optional[str]) -> None @hook.irc_out() -def check_send_key(conn: IrcClient, parsed_line: Message, db: Session) -> Message: +def check_send_key( + conn: IrcClient, parsed_line: Message, db: Session +) -> Message: """ Parse outgoing JOIN messages and store used channel keys """ diff --git a/plugins/core/chan_log.py b/plugins/core/chan_log.py index 0e28d91e..763171a9 100644 --- a/plugins/core/chan_log.py +++ b/plugins/core/chan_log.py @@ -46,7 +46,7 @@ def is_dunder(name: str) -> bool: :param name: The name to check :return: True if the name is a dunder, False otherwise """ - return len(name) > 4 and name.startswith('__') and name.endswith('__') + return len(name) > 4 and name.startswith("__") and name.endswith("__") AttrList = Iterable[Tuple[str, Any]] @@ -86,7 +86,7 @@ def dump_attrs(obj: object, ignore_dunder: bool = False) -> AttrList: yield (name, getattr(obj, name, None)) -def indent(lines: Iterable[str], size: int = 2, char: str = ' '): +def indent(lines: Iterable[str], size: int = 2, char: str = " "): """ Indent each line in an iterable and yield it, ignoring blank lines @@ -134,7 +134,7 @@ def format_error_data(exc: Exception) -> Iterable[str]: if isinstance(exc, typ): yield from indent(func(exc)) - yield '' + yield "" def format_error_chain(exc: Exception) -> Iterable[str]: @@ -149,8 +149,8 @@ def format_error_chain(exc: Exception) -> Iterable[str]: yield from format_error_data(exc) # Get "direct cause of" or # "during handling of ..., another exception occurred" stack - cause = getattr(exc, '__cause__', None) - context = getattr(exc, '__context__', None) + cause = getattr(exc, "__cause__", None) + context = getattr(exc, "__context__", None) exc = cause or context @@ -169,7 +169,7 @@ def format_attrs(obj: object, ignore_dunder: bool = False) -> Iterable[str]: :return: An iterable of lines of formatted data """ for k, v in dump_attrs(obj, ignore_dunder=ignore_dunder): - yield '{} = {!r}'.format(k, v) + yield "{} = {!r}".format(k, v) @hook.post_hook @@ -190,12 +190,10 @@ def on_hook_end(error, launched_hook, launched_event, admin_log): messages.append(last_line.strip()) except Exception: msg = traceback.format_exc()[-1] - messages.append( - "Error occurred while formatting error {}".format(msg) - ) + messages.append("Error occurred while formatting error {}".format(msg)) else: try: - url = web.paste('\n'.join(lines)) + url = web.paste("\n".join(lines)) messages.append("Traceback: " + url) except Exception: msg = traceback.format_exc()[-1] @@ -212,7 +210,7 @@ def on_hook_end(error, launched_hook, launched_event, admin_log): lines.append("Error data:") lines.extend(indent(format_error_chain(exc))) - url = web.paste('\n'.join(lines)) + url = web.paste("\n".join(lines)) messages.append("Event: " + url) except Exception: msg = traceback.format_exc()[-1] diff --git a/plugins/core/check_conn.py b/plugins/core/check_conn.py index 3223a2af..042c1299 100644 --- a/plugins/core/check_conn.py +++ b/plugins/core/check_conn.py @@ -68,17 +68,17 @@ def format_conn(conn): else: out = "$(red){name}$(clear)" - return colors.parse(out.format( - name=conn.name, activity=round(lag * 1000, 3) - )) + return colors.parse( + out.format(name=conn.name, activity=round(lag * 1000, 3)) + ) -@hook.command("connlist", "listconns", autohelp=False, permissions=["botcontrol"]) +@hook.command( + "connlist", "listconns", autohelp=False, permissions=["botcontrol"] +) def list_conns(bot): """- Lists all current connections and their status""" - conns = ', '.join( - map(format_conn, bot.connections.values()) - ) + conns = ", ".join(map(format_conn, bot.connections.values())) return "Current connections: {}".format(conns) @@ -90,7 +90,7 @@ def on_connect(conn): conn.memory["last_activity"] = now conn.memory["lag"] = 0 conn.memory["needs_reconnect"] = False - conn.memory['last_ping_rpl'] = now + conn.memory["last_ping_rpl"] = now @hook.command("lagcheck", autohelp=False, permissions=["botcontrol"]) @@ -134,13 +134,13 @@ async def reconnect_loop(bot): await do_reconnect(conn) -@hook.irc_raw('PONG') +@hook.irc_raw("PONG") def on_pong(conn, irc_paramlist): now = time.time() conn.memory["ping_recv"] = now timestamp = irc_paramlist[-1] is_lag = False - if timestamp.startswith('LAGCHECK'): + if timestamp.startswith("LAGCHECK"): timestamp = timestamp[8:] is_lag = True @@ -153,7 +153,7 @@ def on_pong(conn, irc_paramlist): conn.memory["last_ping_rpl"] = now -@hook.irc_raw('*') +@hook.irc_raw("*") async def on_act(conn): now = time.time() - conn.memory['last_activity'] = now + conn.memory["last_activity"] = now diff --git a/plugins/core/core_hooks.py b/plugins/core/core_hooks.py index e4b7c59c..8c651bdb 100644 --- a/plugins/core/core_hooks.py +++ b/plugins/core/core_hooks.py @@ -4,7 +4,12 @@ @hook.sieve(priority=Priority.LOWEST) def cmd_autohelp(bot, event, _hook): - if _hook.type == "command" and _hook.auto_help and not event.text and _hook.doc is not None: + if ( + _hook.type == "command" + and _hook.auto_help + and not event.text + and _hook.doc is not None + ): event.notice_doc() return None diff --git a/plugins/core/core_out.py b/plugins/core/core_out.py index f21ce40d..f13105ec 100644 --- a/plugins/core/core_out.py +++ b/plugins/core/core_out.py @@ -6,11 +6,13 @@ from cloudbot.hook import Priority from cloudbot.util import colors -NEW_LINE_TRANS_TBL = str.maketrans({ - '\r': None, - '\n': None, - '\0': None, -}) +NEW_LINE_TRANS_TBL = str.maketrans( + { + "\r": None, + "\n": None, + "\0": None, + } +) @hook.irc_out(priority=Priority.HIGHEST) @@ -47,8 +49,15 @@ def encode_line(line, conn): @hook.irc_out(priority=Priority.HIGH) def strip_command_chars(parsed_line, conn, line): chars = conn.config.get("strip_cmd_chars", "!.@;$") - if chars and parsed_line and parsed_line.command == "PRIVMSG" and parsed_line.parameters[-1][0] in chars: - new_msg = colors.parse("$(red)[!!]$(clear) ") + parsed_line.parameters[-1] + if ( + chars + and parsed_line + and parsed_line.command == "PRIVMSG" + and parsed_line.parameters[-1][0] in chars + ): + new_msg = ( + colors.parse("$(red)[!!]$(clear) ") + parsed_line.parameters[-1] + ) parsed_line.parameters[-1] = new_msg parsed_line.has_trail = True return parsed_line diff --git a/plugins/core/core_tracker.py b/plugins/core/core_tracker.py index 89e9afd9..5d699a2c 100644 --- a/plugins/core/core_tracker.py +++ b/plugins/core/core_tracker.py @@ -10,6 +10,7 @@ # functions called for bot state tracking + def bot_left_channel(conn, chan): logger.info("[%s|tracker] Bot left channel %r", conn.name, chan) if chan in conn.channels: @@ -35,10 +36,15 @@ async def on_kick(conn, chan, target, loop): # if the bot has been kicked, remove from the channel list if target == conn.nick: bot_left_channel(conn, chan) - if conn.config.get('auto_rejoin', False): + if conn.config.get("auto_rejoin", False): loop.call_later(5, conn.join, chan) - loop.call_later(5, logger.info, "[%s|tracker] Bot was kicked from %s, " - "rejoining channel.", conn.name, chan) + loop.call_later( + 5, + logger.info, + "[%s|tracker] Bot was kicked from %s, " "rejoining channel.", + conn.name, + chan, + ) @hook.irc_raw("NICK") @@ -53,7 +59,12 @@ async def on_nick(irc_paramlist, conn, nick): if old_nick == conn.nick: conn.nick = new_nick - logger.info("[%s|tracker] Bot nick changed from %r to %r.", conn.name, old_nick, new_nick) + logger.info( + "[%s|tracker] Bot nick changed from %r to %r.", + conn.name, + old_nick, + new_nick, + ) # for channels the host tells us we're joining without us joining it ourselves diff --git a/plugins/core/help.py b/plugins/core/help.py index 553a1908..522aac71 100644 --- a/plugins/core/help.py +++ b/plugins/core/help.py @@ -19,7 +19,9 @@ def get_potential_commands(bot, cmd_name): @hook.command("help", autohelp=False) -async def help_command(text, chan, bot, notice, message, has_permission, triggered_prefix): +async def help_command( + text, chan, bot, notice, message, has_permission, triggered_prefix +): """[command] - gives help for [command], or lists all available commands if no command is specified :type chan: str @@ -38,8 +40,13 @@ async def help_command(text, chan, bot, notice, message, has_permission, trigger return if len(cmds) > 1: - notice("Possible matches: {}".format( - formatting.get_text_list(sorted([command for command, _ in cmds])))) + notice( + "Possible matches: {}".format( + formatting.get_text_list( + sorted([command for command, _ in cmds]) + ) + ) + ) return doc = cmds[0][1].doc @@ -47,11 +54,17 @@ async def help_command(text, chan, bot, notice, message, has_permission, trigger if doc: notice("{}{} {}".format(triggered_prefix, searching_for, doc)) else: - notice("Command {} has no additional documentation.".format(searching_for)) + notice( + "Command {} has no additional documentation.".format( + searching_for + ) + ) else: commands = [] - for plugin in sorted(set(bot.plugin_manager.commands.values()), key=attrgetter("name")): + for plugin in sorted( + set(bot.plugin_manager.commands.values()), key=attrgetter("name") + ): # use set to remove duplicate commands (from multiple aliases), and sorted to sort by name if plugin.permissions: @@ -72,7 +85,9 @@ async def help_command(text, chan, bot, notice, message, has_permission, trigger commands.append(command) # list of lines to send to the user - lines = formatting.chunk_str("Here's a list of commands you can use: " + ", ".join(commands)) + lines = formatting.chunk_str( + "Here's a list of commands you can use: " + ", ".join(commands) + ) for line in lines: if chan[:1] == "#": @@ -81,7 +96,11 @@ async def help_command(text, chan, bot, notice, message, has_permission, trigger # This is an user in this case. message(line) - notice("For detailed help, use {}help , without the brackets.".format(triggered_prefix)) + notice( + "For detailed help, use {}help , without the brackets.".format( + triggered_prefix + ) + ) @hook.command @@ -94,19 +113,24 @@ async def cmdinfo(text, bot, notice): return if len(cmds) > 1: - notice("Possible matches: {}".format( - formatting.get_text_list(sorted([command for command, plugin in cmds])))) + notice( + "Possible matches: {}".format( + formatting.get_text_list( + sorted([command for command, plugin in cmds]) + ) + ) + ) return cmd_hook = cmds[0][1] hook_name = cmd_hook.plugin.title + "." + cmd_hook.function_name info = "Command: {}, Aliases: [{}], Hook name: {}".format( - cmd_hook.name, ', '.join(cmd_hook.aliases), hook_name + cmd_hook.name, ", ".join(cmd_hook.aliases), hook_name ) if cmd_hook.permissions: - info += ", Permissions: [{}]".format(', '.join(cmd_hook.permissions)) + info += ", Permissions: [{}]".format(", ".join(cmd_hook.permissions)) notice(info) @@ -116,7 +140,9 @@ def generatehelp(conn, bot): """- Dumps a list of commands with their help text to the docs directory formatted using markdown.""" message = "{} Command list\n".format(conn.nick) message += "------\n" - for plugin in sorted(set(bot.plugin_manager.commands.values()), key=attrgetter("name")): + for plugin in sorted( + set(bot.plugin_manager.commands.values()), key=attrgetter("name") + ): # use set to remove duplicate commands (from multiple aliases), and sorted to sort by name command = plugin.name aliases = "" @@ -132,15 +158,21 @@ def generatehelp(conn, bot): aliases += alias + ", " aliases = aliases[:-2] if doc: - doc = doc.replace("<", "<").replace(">", ">") \ - .replace("[", "<").replace("]", ">") + doc = ( + doc.replace("<", "<") + .replace(">", ">") + .replace("[", "<") + .replace("]", ">") + ) if aliases: message += "**{} ({}):** {}\n\n".format(command, aliases, doc) else: # No aliases so just print the commands message += "**{}**: {}\n\n".format(command, doc) else: - message += "**{}**: Command has no documentation.\n\n".format(command) + message += "**{}**: Command has no documentation.\n\n".format( + command + ) if permission: message = message[:-2] message += " ( *Permission required:* {})\n\n".format(permission) diff --git a/plugins/core/hook_stats.py b/plugins/core/hook_stats.py index dbbc0e3f..b91bd032 100644 --- a/plugins/core/hook_stats.py +++ b/plugins/core/hook_stats.py @@ -14,7 +14,7 @@ def default_hook_counter(): - return {'success': 0, 'failure': 0} + return {"success": 0, "failure": 0} def hook_sorter(n): @@ -29,9 +29,11 @@ def get_stats(bot): stats = bot.memory["hook_stats"] except LookupError: bot.memory["hook_stats"] = stats = { - 'global': defaultdict(default_hook_counter), - 'network': defaultdict(lambda: defaultdict(default_hook_counter)), - 'channel': defaultdict(lambda: defaultdict(lambda: defaultdict(default_hook_counter))), + "global": defaultdict(default_hook_counter), + "network": defaultdict(lambda: defaultdict(default_hook_counter)), + "channel": defaultdict( + lambda: defaultdict(lambda: defaultdict(default_hook_counter)) + ), } return stats @@ -41,53 +43,60 @@ def get_stats(bot): def stats_sieve(launched_event, error, bot, launched_hook): chan = launched_event.chan conn = launched_event.conn - status = 'success' if error is None else 'failure' + status = "success" if error is None else "failure" stats = get_stats(bot) - name = launched_hook.plugin.title + '.' + launched_hook.function_name - stats['global'][name][status] += 1 + name = launched_hook.plugin.title + "." + launched_hook.function_name + stats["global"][name][status] += 1 if conn: - stats['network'][conn.name.casefold()][name][status] += 1 + stats["network"][conn.name.casefold()][name][status] += 1 if chan: - stats['channel'][conn.name.casefold()][chan.casefold()][name][status] += 1 + stats["channel"][conn.name.casefold()][chan.casefold()][name][ + status + ] += 1 def do_basic_stats(data): table = [ - (hook_name, str(count['success']), str(count['failure'])) - for hook_name, count in sorted(data.items(), key=hook_sorter(1), reverse=True) + (hook_name, str(count["success"]), str(count["failure"])) + for hook_name, count in sorted( + data.items(), key=hook_sorter(1), reverse=True + ) ] return ("Hook", "Uses - Success", "Uses - Errored"), table def do_global_stats(data): - return do_basic_stats(data['global']) + return do_basic_stats(data["global"]) def do_network_stats(data, network): - return do_basic_stats(data['network'][network.casefold()]) + return do_basic_stats(data["network"][network.casefold()]) def do_channel_stats(data, network, channel): - return do_basic_stats(data['channel'][network.casefold()][channel.casefold()]) + return do_basic_stats( + data["channel"][network.casefold()][channel.casefold()] + ) def do_hook_stats(data, hook_name): table = [ - (net, chan, hooks[hook_name]) for net, chans in data['channel'].items() for chan, hooks in chans.items() + (net, chan, hooks[hook_name]) + for net, chans in data["channel"].items() + for chan, hooks in chans.items() + ] + return ("Network", "Channel", "Uses - Success", "Uses - Errored"), [ + (net, chan, str(count["success"]), str(count["failure"])) + for net, chan, count in sorted(table, key=hook_sorter(2), reverse=True) ] - return ("Network", "Channel", "Uses - Success", "Uses - Errored"), \ - [ - (net, chan, str(count['success']), str(count['failure'])) - for net, chan, count in sorted(table, key=hook_sorter(2), reverse=True) - ] stats_funcs = { - 'global': (do_global_stats, 0), - 'network': (do_network_stats, 1), - 'channel': (do_channel_stats, 2), - 'hook': (do_hook_stats, 1), + "global": (do_global_stats, 0), + "network": (do_network_stats, 1), + "channel": (do_channel_stats, 2), + "hook": (do_hook_stats, 1), } @@ -116,4 +125,4 @@ def hookstats(text, bot, notice_doc): table = gen_markdown_table(headers, data) - return web.paste(table, 'md', 'hastebin') + return web.paste(table, "md", "hastebin") diff --git a/plugins/core/log.py b/plugins/core/log.py index 6cf7113a..b41e97d4 100644 --- a/plugins/core/log.py +++ b/plugins/core/log.py @@ -2,6 +2,7 @@ import logging import os import time +from typing import Dict, Tuple import cloudbot from cloudbot import hook @@ -33,14 +34,26 @@ irc_default = "[{server}] {irc_raw}" ctcp_known = "[{server}:{channel}] {nick} [{user}@{host}] has requested CTCP {ctcp_command}" -ctcp_known_with_message = ("[{server}:{channel}] {nick} [{user}@{host}] " - "has requested CTCP {ctcp_command}: {ctcp_message}") +ctcp_known_with_message = ( + "[{server}:{channel}] {nick} [{user}@{host}] " + "has requested CTCP {ctcp_command}: {ctcp_message}" +) ctcp_unknown = "[{server}:{channel}] {nick} [{user}@{host}] has requested unknown CTCP {ctcp_command}" -ctcp_unknown_with_message = ("[{server}:{channel}] {nick} [{user}@{host}] " - "has requested unknown CTCP {ctcp_command}: {ctcp_message}") +ctcp_unknown_with_message = ( + "[{server}:{channel}] {nick} [{user}@{host}] " + "has requested unknown CTCP {ctcp_command}: {ctcp_message}" +) server_info_numerics = ( - "003", "005", "250", "251", "252", "253", "254", "255", "256" + "003", + "005", + "250", + "251", + "252", + "253", + "254", + "255", + "256", ) @@ -48,6 +61,7 @@ # | Formatting | # +------------+ + def format_event(event): """ Format an event @@ -58,8 +72,12 @@ def format_event(event): # Setup arguments args = { - "server": event.conn.name, "target": event.target, "channel": event.chan, "nick": event.nick, - "user": event.user, "host": event.host + "server": event.conn.name, + "target": event.target, + "channel": event.chan, + "nick": event.nick, + "user": event.user, + "host": event.host, } if event.content is not None: @@ -99,7 +117,7 @@ def format_irc_event(event, args): # Try formatting with the CTCP command if event.irc_ctcp_text is not None: - ctcp_command, _, ctcp_message = event.irc_ctcp_text.partition(' ') + ctcp_command, _, ctcp_message = event.irc_ctcp_text.partition(" ") args["ctcp_command"] = ctcp_command args["ctcp_message"] = ctcp_message @@ -120,10 +138,17 @@ def format_irc_event(event, args): logging_config = event.bot.config.get("logging", {}) - if not logging_config.get("show_motd", True) and event.irc_command in ("375", "372", "376"): + if not logging_config.get("show_motd", True) and event.irc_command in ( + "375", + "372", + "376", + ): return None - if not logging_config.get("show_server_info", True) and event.irc_command in server_info_numerics: + if ( + not logging_config.get("show_server_info", True) + and event.irc_command in server_info_numerics + ): return None if event.irc_command == "PING": @@ -144,15 +169,17 @@ def format_irc_event(event, args): folder_format = "%Y" # Stream cache, (server, chan) -> (file_name, stream) -stream_cache = {} +stream_cache: Dict[Tuple[str, str], Tuple[str, codecs.StreamReaderWriter]] = {} # Raw stream cache, server -> (file_name, stream) -raw_cache = {} +raw_cache: Dict[str, Tuple[str, codecs.StreamReaderWriter]] = {} def get_log_filename(server, chan): current_time = time.gmtime() folder_name = time.strftime(folder_format, current_time) - file_name = time.strftime(file_format.format(chan=chan, server=server), current_time).lower() + file_name = time.strftime( + file_format.format(chan=chan, server=server), current_time + ).lower() return cloudbot.logging_info.add_path(folder_name, file_name) @@ -174,7 +201,9 @@ def get_log_stream(server, chan): # a dumb hack to bypass the fact windows does not allow * in file names new_filename = new_filename.replace("*", "server") - log_stream = codecs.open(new_filename, mode="a", encoding="utf-8", buffering=1) + log_stream = codecs.open( + new_filename, mode="a", encoding="utf-8", buffering=1 + ) stream_cache[cache_key] = (new_filename, log_stream) return log_stream @@ -183,13 +212,15 @@ def get_log_stream(server, chan): def get_raw_log_filename(server): current_time = time.gmtime() folder_name = time.strftime(folder_format, current_time) - file_name = time.strftime(raw_file_format.format(server=server), current_time).lower() + file_name = time.strftime( + raw_file_format.format(server=server), current_time + ).lower() return cloudbot.logging_info.add_path("raw", folder_name, file_name) def get_raw_log_stream(server): new_filename = get_raw_log_filename(server) - old_filename, log_stream = stream_cache.get(server, (None, None)) + old_filename, log_stream = raw_cache.get(server, (None, None)) # If the filename has changed since we opened the stream, we should re-open if new_filename != old_filename: @@ -201,7 +232,9 @@ def get_raw_log_stream(server): logging_dir = os.path.dirname(new_filename) os.makedirs(logging_dir, exist_ok=True) - log_stream = codecs.open(new_filename, mode="a", encoding="utf-8", buffering=1) + log_stream = codecs.open( + new_filename, mode="a", encoding="utf-8", buffering=1 + ) stream_cache[server] = (new_filename, log_stream) return log_stream @@ -233,7 +266,11 @@ def log(event): text = format_event(event) if text is not None: - if event.irc_command in ["PRIVMSG", "PART", "JOIN", "MODE", "TOPIC", "QUIT", "NOTICE"] and event.chan: + if ( + event.irc_command + in ["PRIVMSG", "PART", "JOIN", "MODE", "TOPIC", "QUIT", "NOTICE"] + and event.chan + ): stream = get_log_stream(event.conn.name, event.chan) stream.write(text + os.linesep) stream.flush() diff --git a/plugins/core/optout.py b/plugins/core/optout.py index fc7334f4..ad1e5956 100644 --- a/plugins/core/optout.py +++ b/plugins/core/optout.py @@ -6,7 +6,14 @@ from threading import RLock from irclib.util.compare import match_mask -from sqlalchemy import Table, Column, String, Boolean, PrimaryKeyConstraint, and_ +from sqlalchemy import ( + Boolean, + Column, + PrimaryKeyConstraint, + String, + Table, + and_, +) from cloudbot import hook from cloudbot.hook import Priority @@ -16,13 +23,13 @@ from cloudbot.util.text import parse_bool optout_table = Table( - 'optout', + "optout", database.metadata, - Column('network', String), - Column('chan', String), - Column('hook', String), - Column('allow', Boolean, default=False), - PrimaryKeyConstraint('network', 'chan', 'hook') + Column("network", String), + Column("chan", String), + Column("hook", String), + Column("allow", Boolean, default=False), + PrimaryKeyConstraint("network", "chan", "hook"), ) optout_cache = DefaultKeyFoldDict(list) @@ -51,10 +58,14 @@ def __str__(self): return "{} {} {}".format(self.channel, self.hook, self.allow) def __repr__(self): - return "{}({}, {}, {})".format(self.__class__.__name__, self.channel, self.hook, self.allow) + return "{}({}, {}, {})".format( + self.__class__.__name__, self.channel, self.hook, self.allow + ) def match(self, channel, hook_name): - return self.match_chan(channel) and match_mask(hook_name.casefold(), self.hook) + return self.match_chan(channel) and match_mask( + hook_name.casefold(), self.hook + ) def match_chan(self, channel): return match_mask(channel.casefold(), self.channel) @@ -78,14 +89,18 @@ def get_conn_optouts(conn_name): def get_channel_optouts(conn_name, chan=None): with cache_lock: return [ - opt for opt in get_conn_optouts(conn_name) + opt + for opt in get_conn_optouts(conn_name) if not chan or opt.match_chan(chan) ] def format_optout_list(opts): headers = ("Channel Pattern", "Hook Pattern", "Allowed") - table = [(opt.channel, opt.hook, "true" if opt.allow else "false") for opt in opts] + table = [ + (opt.channel, opt.hook, "true" if opt.allow else "false") + for opt in opts + ] return gen_markdown_table(headers, table) @@ -93,10 +108,18 @@ def set_optout(db, conn, chan, pattern, allowed): conn_cf = conn.casefold() chan_cf = chan.casefold() pattern_cf = pattern.casefold() - clause = and_(optout_table.c.network == conn_cf, optout_table.c.chan == chan_cf, optout_table.c.hook == pattern_cf) + clause = and_( + optout_table.c.network == conn_cf, + optout_table.c.chan == chan_cf, + optout_table.c.hook == pattern_cf, + ) res = db.execute(optout_table.update().values(allow=allowed).where(clause)) if not res.rowcount: - db.execute(optout_table.insert().values(network=conn_cf, chan=chan_cf, hook=pattern_cf, allow=allowed)) + db.execute( + optout_table.insert().values( + network=conn_cf, chan=chan_cf, hook=pattern_cf, allow=allowed + ) + ) db.commit() load_cache(db) @@ -106,7 +129,11 @@ def del_optout(db, conn, chan, pattern): conn_cf = conn.casefold() chan_cf = chan.casefold() pattern_cf = pattern.casefold() - clause = and_(optout_table.c.network == conn_cf, optout_table.c.chan == chan_cf, optout_table.c.hook == pattern_cf) + clause = and_( + optout_table.c.network == conn_cf, + optout_table.c.chan == chan_cf, + optout_table.c.hook == pattern_cf, + ) res = db.execute(optout_table.delete().where(clause)) db.commit() @@ -119,7 +146,9 @@ def clear_optout(db, conn, chan=None): conn_cf = conn.casefold() if chan: chan_cf = chan.casefold() - clause = and_(optout_table.c.network == conn_cf, optout_table.c.chan == chan_cf) + clause = and_( + optout_table.c.network == conn_cf, optout_table.c.chan == chan_cf + ) else: clause = optout_table.c.network == conn_cf @@ -134,7 +163,9 @@ def clear_optout(db, conn, chan=None): def load_cache(db): new_cache = defaultdict(list) for row in db.execute(optout_table.select()): - new_cache[row["network"]].append(OptOut(row["chan"], row["hook"], row["allow"])) + new_cache[row["network"]].append( + OptOut(row["chan"], row["hook"], row["allow"]) + ) for opts in new_cache.values(): opts.sort(reverse=True) @@ -150,7 +181,7 @@ def optout_sieve(bot, event, _hook): if not event.chan or not event.conn: return event - if _hook.plugin.title.startswith('core.'): + if _hook.plugin.title.startswith("core."): return event hook_name = _hook.plugin.title + "." + _hook.function_name @@ -160,7 +191,9 @@ def optout_sieve(bot, event, _hook): if _optout.match(event.chan, hook_name): if not _optout.allow: if _hook.type == "command": - event.notice("Sorry, that command is disabled in this channel.") + event.notice( + "Sorry, that command is disabled in this channel." + ) return None @@ -181,10 +214,14 @@ async def optout(text, event, chan, db, conn): if args[0].startswith("#") and len(args) > 1: chan = args.pop(0) - has_perm = await check_channel_permissions(event, chan, "op", "chanop", "snoonetstaff", "botcontrol") + has_perm = await check_channel_permissions( + event, chan, "op", "chanop", "snoonetstaff", "botcontrol" + ) if not has_perm: - event.notice("Sorry, you may not configure optout settings for that channel.") + event.notice( + "Sorry, you may not configure optout settings for that channel." + ) return pattern = args.pop(0) @@ -202,7 +239,7 @@ async def optout(text, event, chan, db, conn): return "{action} hooks matching {pattern} in {channel}.".format( action="Enabled" if allowed else "Disabled", pattern=pattern, - channel=chan + channel=chan, ) @@ -214,10 +251,14 @@ async def deloptout(text, event, chan, db, conn): if len(args) > 1: chan = args.pop(0) - has_perm = await check_channel_permissions(event, chan, "op", "chanop", "snoonetstaff", "botcontrol") + has_perm = await check_channel_permissions( + event, chan, "op", "chanop", "snoonetstaff", "botcontrol" + ) if not has_perm: - event.notice("Sorry, you may not configure optout settings for that channel.") + event.notice( + "Sorry, you may not configure optout settings for that channel." + ) return pattern = args.pop(0) @@ -237,7 +278,9 @@ async def check_global_perms(event): chan = text.split()[0] can_global = await event.check_permissions("snoonetstaff", "botcontrol") - allowed = can_global or (await check_channel_permissions(event, chan, "op", "chanop")) + allowed = can_global or ( + await check_channel_permissions(event, chan, "op", "chanop") + ) if not allowed: event.notice("Sorry, you are not allowed to use this command.") diff --git a/plugins/core/plugin_control.py b/plugins/core/plugin_control.py index ffb99fa6..5cea8ab9 100644 --- a/plugins/core/plugin_control.py +++ b/plugins/core/plugin_control.py @@ -11,7 +11,10 @@ def pluginlist(bot): """- List all currently loaded plugins""" manager = bot.plugin_manager plugins = [ - (plugin.title, str(Path(plugin.file_path).resolve().relative_to(bot.base_dir))) + ( + plugin.title, + str(Path(plugin.file_path).resolve().relative_to(bot.base_dir)), + ) for plugin in manager.plugins.values() ] plugins.sort(key=itemgetter(0)) @@ -33,7 +36,9 @@ async def pluginload(bot, text, reply): reply("Plugin failed to load.") raise else: - return "Plugin {}loaded successfully.".format("re" if was_loaded else "") + return "Plugin {}loaded successfully.".format( + "re" if was_loaded else "" + ) @hook.command(permissions=["botcontrol"]) diff --git a/plugins/core/regex_chans.py b/plugins/core/regex_chans.py index 2ccfc82e..fa5817fc 100644 --- a/plugins/core/regex_chans.py +++ b/plugins/core/regex_chans.py @@ -1,6 +1,7 @@ import logging +from typing import Dict, Tuple -from sqlalchemy import Table, Column, UniqueConstraint, String +from sqlalchemy import Column, String, Table, UniqueConstraint from cloudbot import hook from cloudbot.util import database @@ -11,14 +12,14 @@ Column("connection", String), Column("channel", String), Column("status", String), - UniqueConstraint("connection", "channel") + UniqueConstraint("connection", "channel"), ) # Default value. # If True, all channels without a setting will have regex enabled # If False, all channels without a setting will have regex disabled default_enabled = True -status_cache = {} +status_cache: Dict[Tuple[str, str], str] = {} logger = logging.getLogger("cloudbot") @@ -48,26 +49,52 @@ def set_status(db, conn, chan, status): if (conn, chan) in status_cache: # if we have a set value, update db.execute( - table.update().values(status=status).where(table.c.connection == conn).where(table.c.channel == chan)) + table.update() + .values(status=status) + .where(table.c.connection == conn) + .where(table.c.channel == chan) + ) else: # otherwise, insert - db.execute(table.insert().values(connection=conn, channel=chan, status=status)) + db.execute( + table.insert().values(connection=conn, channel=chan, status=status) + ) db.commit() def delete_status(db, conn, chan): - db.execute(table.delete().where(table.c.connection == conn).where(table.c.channel == chan)) + db.execute( + table.delete() + .where(table.c.connection == conn) + .where(table.c.channel == chan) + ) db.commit() @hook.sieve() def sieve_regex(bot, event, _hook): - if _hook.type == "regex" and event.chan.startswith("#") and _hook.plugin.title != "factoids": + if ( + _hook.type == "regex" + and event.chan.startswith("#") + and _hook.plugin.title != "factoids" + ): status = status_cache.get((event.conn.name, event.chan)) - if status != "ENABLED" and (status == "DISABLED" or not default_enabled): - logger.info("[%s] Denying %s from %s", event.conn.name, _hook.function_name, event.chan) + if status != "ENABLED" and ( + status == "DISABLED" or not default_enabled + ): + logger.info( + "[%s] Denying %s from %s", + event.conn.name, + _hook.function_name, + event.chan, + ) return None - logger.info("[%s] Allowing %s to %s", event.conn.name, _hook.function_name, event.chan) + logger.info( + "[%s] Allowing %s to %s", + event.conn.name, + _hook.function_name, + event.chan, + ) return event @@ -83,13 +110,17 @@ def change_status(db, event, status): action = "Enabling" if status else "Disabling" event.message( - "{} regex matching (youtube, etc) (issued by {})".format(action, event.nick), - target=channel + "{} regex matching (youtube, etc) (issued by {})".format( + action, event.nick + ), + target=channel, + ) + event.notice( + "{} regex matching (youtube, etc) in channel {}".format(action, channel) + ) + set_status( + db, event.conn.name, channel, "ENABLED" if status else "DISABLED" ) - event.notice("{} regex matching (youtube, etc) in channel {}".format( - action, channel - )) - set_status(db, event.conn.name, channel, "ENABLED" if status else "DISABLED") load_cache(db) @@ -116,8 +147,17 @@ def resetregex(text, db, conn, chan, nick, message, notice): else: channel = "#{}".format(text) - message("Resetting regex matching setting (youtube, etc) (issued by {})".format(nick), target=channel) - notice("Resetting regex matching setting (youtube, etc) in channel {}".format(channel)) + message( + "Resetting regex matching setting (youtube, etc) (issued by {})".format( + nick + ), + target=channel, + ) + notice( + "Resetting regex matching setting (youtube, etc) in channel {}".format( + channel + ) + ) delete_status(db, conn.name, channel) load_cache(db) diff --git a/plugins/core/sasl.py b/plugins/core/sasl.py index e16a2e19..12c9dc83 100644 --- a/plugins/core/sasl.py +++ b/plugins/core/sasl.py @@ -9,14 +9,14 @@ @hook.on_cap_available("sasl") def sasl_available(conn): - sasl_conf = conn.config.get('sasl') - return bool(sasl_conf and sasl_conf.get('enabled', True)) + sasl_conf = conn.config.get("sasl") + return bool(sasl_conf and sasl_conf.get("enabled", True)) @hook.on_cap_ack("sasl") async def sasl_ack(conn): - sasl_auth = conn.config.get('sasl') - if sasl_auth and sasl_auth.get('enabled', True): + sasl_auth = conn.config.get("sasl") + if sasl_auth and sasl_auth.get("enabled", True): sasl_mech = sasl_auth.get("mechanism", "PLAIN").upper() auth_fut = async_util.create_future(conn.loop) conn.memory["sasl_auth_future"] = auth_fut @@ -24,7 +24,7 @@ async def sasl_ack(conn): cmd, arg = await auth_fut if cmd == "908": logger.warning("[%s|sasl] SASL mechanism not supported", conn.name) - elif cmd == "AUTHENTICATE" and arg[0] == '+': + elif cmd == "AUTHENTICATE" and arg[0] == "+": num_fut = async_util.create_future(conn.loop) conn.memory["sasl_numeric_future"] = num_fut if sasl_mech == "PLAIN": @@ -33,7 +33,7 @@ async def sasl_ack(conn): ).encode() conn.cmd("AUTHENTICATE", base64.b64encode(auth_str).decode()) else: - conn.cmd("AUTHENTICATE", '+') + conn.cmd("AUTHENTICATE", "+") numeric = await num_fut if numeric == "902": logger.warning("[%s|sasl] SASL nick locked", conn.name) diff --git a/plugins/correction.py b/plugins/correction.py index 286a80ad..11377ea7 100644 --- a/plugins/correction.py +++ b/plugins/correction.py @@ -3,8 +3,10 @@ from cloudbot import hook from cloudbot.util.formatting import ireplace -correction_re = re.compile(r"^[sS]/(?:(.*?)(? {}" - mod_msg = ireplace(re.escape(mod_msg), find_esc, "\x02" + replace_esc + "\x02") + mod_msg = ireplace( + re.escape(mod_msg), find_esc, "\x02" + replace_esc + "\x02" + ) mod_msg = unescape_re.sub(r"\1", mod_msg) diff --git a/plugins/cryptocurrency.py b/plugins/cryptocurrency.py index af76c4a7..727e45ed 100644 --- a/plugins/cryptocurrency.py +++ b/plugins/cryptocurrency.py @@ -15,7 +15,7 @@ from numbers import Number from operator import itemgetter from threading import RLock -from typing import Any, Dict, List, Optional, Type, TypeVar +from typing import Any, Dict, List, Optional, Tuple, Type, TypeVar import requests from requests import Response @@ -45,7 +45,9 @@ def __init__(self, name: str): class APIResponse: - def __init__(self, api, data: "UntypedResponse", response: Response) -> None: + def __init__( + self, api, data: "UntypedResponse", response: Response + ) -> None: self.api = api self.data = data self.response = response @@ -83,7 +85,9 @@ def __new__(cls, name, bases, members): if members.setdefault("_abstract", False): super_fields = () for base in bases: - if not getattr(base, "_abstract", False) and isinstance(base, cls): + if not getattr(base, "_abstract", False) and isinstance( + base, cls + ): super_fields = getattr(base, "_fields") break @@ -94,7 +98,7 @@ def __new__(cls, name, bases, members): return type.__new__(cls, name, bases, members) -T = TypeVar("T") +T = TypeVar("T", bound="Schema") class Schema(metaclass=SchemaMeta): @@ -142,7 +146,9 @@ def __init__(self, status: ResponseStatus, data: Any = None): class Platform(Schema): # noinspection PyShadowingBuiltins - def __init__(self, id: int, name: str, symbol: str, slug: str, token_address: str): + def __init__( + self, id: int, name: str, symbol: str, slug: str, token_address: str + ): super().__init__() self.id = id self.name = name @@ -241,7 +247,9 @@ def __init__(self, data: List[FiatCurrency], status: ResponseStatus): super().__init__(status) self.data = data - self.symbols = {currency.symbol: currency.sign for currency in self.data} + self.symbols = { + currency.symbol: currency.sign for currency in self.data + } class CryptoCurrencyEntry(Schema): @@ -278,7 +286,9 @@ def __init__(self, data: List[CryptoCurrencyEntry], status: ResponseStatus): self.names = set(currency.symbol for currency in self.data) -BAD_FIELD_TYPE_MSG = "field {field!r} expected type {exp_type!r}, got type {act_type!r}" +BAD_FIELD_TYPE_MSG = ( + "field {field!r} expected type {exp_type!r}, got type {act_type!r}" +) def sentinel(name: str): @@ -344,7 +354,9 @@ def _hydrate_object(_value, _cls): _assert_type(_value, dict, _cls) return { - _hydrate_object(k, type_args[0]): _hydrate_object(v, type_args[1]) + _hydrate_object(k, type_args[0]): _hydrate_object( + v, type_args[1] + ) for k, v in _value.items() } @@ -358,12 +370,12 @@ def _hydrate_object(_value, _cls): def read_data(data: Dict, schema_cls: Type[T]) -> T: - fields = schema_cls._fields + fields: Tuple[SchemaField, ...] = schema_cls._fields - out = {} - field_names = [] + out: Dict[str, Any] = {} + field_names: List[str] = [] - for schema_field in fields: # type: SchemaField + for schema_field in fields: try: param_type = schema_field.field_type name = schema_field.name @@ -393,7 +405,7 @@ def read_data(data: Dict, schema_cls: Type[T]) -> T: "Unable to parse schema {!r}".format(schema_cls.__name__) ) from e - obj = schema_cls(**out) + obj = schema_cls(**out) # type: ignore obj.unknown_fields.update( {key: data[key] for key in data if key not in field_names} @@ -498,7 +510,9 @@ def get_quote(self, symbol: str, currency: str = "USD") -> CryptoCurrency: convert = currency data = self.request( - "cryptocurrency/quotes/latest", symbol=symbol.upper(), convert=convert, + "cryptocurrency/quotes/latest", + symbol=symbol.upper(), + convert=convert, ).data.cast_to(QuoteRequestResponse) _, out = data.data.popitem() return out @@ -510,10 +524,15 @@ def get_fiat_currency_map(self) -> FiatCurrencyMap: def get_crypto_currency_map(self) -> CryptoCurrencyMap: return self._request_cache( - "crypto_currency_map", "cryptocurrency/map", CryptoCurrencyMap, 86400 + "crypto_currency_map", + "cryptocurrency/map", + CryptoCurrencyMap, + 86400, ) - def _request_cache(self, name: str, endpoint: str, fmt: Type[T], ttl: int) -> T: + def _request_cache( + self, name: str, endpoint: str, fmt: Type[T], ttl: int + ) -> T: out = self.cache.get(name) if out is None: currencies = self.request(endpoint).data.cast_to(fmt) @@ -523,7 +542,9 @@ def _request_cache(self, name: str, endpoint: str, fmt: Type[T], ttl: int) -> T: def request(self, endpoint: str, **params) -> APIResponse: url = str(self.api_url / endpoint) - with requests.get(url, headers=self.request_headers, params=params) as response: + with requests.get( + url, headers=self.request_headers, params=params + ) as response: api_response = APIResponse.from_response(self, response) self.check(api_response) @@ -585,7 +606,9 @@ def func(text, event): def init_aliases(): for alias in ALIASES: _hook = alias_wrapper(alias) - globals()[_hook.__name__] = hook.command(*alias.cmds, autohelp=False)(_hook) + globals()[_hook.__name__] = hook.command(*alias.cmds, autohelp=False)( + _hook + ) # main command @@ -628,8 +651,15 @@ def crypto_command(text, event): btc = "" return colors.parse( - ("{} ({}) // $(orange){}{:,.2f}$(clear) {} " + btc + "// {} change").format( - data.symbol, data.slug, currency_sign, quote.price, currency, change_str, + ( + "{} ({}) // $(orange){}{:,.2f}$(clear) {} " + btc + "// {} change" + ).format( + data.symbol, + data.slug, + currency_sign, + quote.price, + currency, + change_str, ) ) @@ -639,7 +669,8 @@ def currency_list(): """- List all available currencies from the API""" currency_map = api.get_crypto_currency_map() currencies = sorted( - set((obj.symbol, obj.name) for obj in currency_map.data), key=itemgetter(0) + set((obj.symbol, obj.name) for obj in currency_map.data), + key=itemgetter(0), ) lst = ["{: <10} {}".format(symbol, name) for symbol, name in currencies] lst.insert(0, "Symbol Name") diff --git a/plugins/deals.py b/plugins/deals.py index 39c637c0..1d73dc29 100644 --- a/plugins/deals.py +++ b/plugins/deals.py @@ -4,7 +4,7 @@ from cloudbot.util import web -@hook.command('meh', autohelp=False) +@hook.command("meh", autohelp=False) def meh(): """- List the current meh.com deal.""" url = "https://meh.com/deals.rss" @@ -16,7 +16,7 @@ def meh(): return "meh.com: {} ({})".format(title, link) -@hook.command('slickdeals', autohelp=False) +@hook.command("slickdeals", autohelp=False) def slickdeals(): """- List the top 3 frontpage slickdeals.net deals.""" url = "https://slickdeals.net/newsearch.php?mode=frontpage&searcharea=deals&searchin=first&rss=1" @@ -27,6 +27,6 @@ def slickdeals(): for item in feed.entries[:3] ) - out = "slickdeals.net: " + ' \u2022 '.join(items) + out = "slickdeals.net: " + " \u2022 ".join(items) return out diff --git a/plugins/dogpile.py b/plugins/dogpile.py index 7cd09000..317c7dc0 100644 --- a/plugins/dogpile.py +++ b/plugins/dogpile.py @@ -8,10 +8,10 @@ search_url = "https://www.dogpile.com/search" -CERT_PATH = 'dogpile.crt' +CERT_PATH = "dogpile.crt" HEADERS = { - 'User-Agent': 'Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 ' - '(KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19' + "User-Agent": "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 " + "(KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19" } session = requests.Session() @@ -29,10 +29,12 @@ def check_certs(bot): def query(endpoint, text): - params = {'q': " ".join(text.split())} + params = {"q": " ".join(text.split())} with requests.get( - search_url + "/" + endpoint, params=params, headers=HEADERS, - verify=session.verify + search_url + "/" + endpoint, + params=params, + headers=HEADERS, + verify=session.verify, ) as r: r.raise_for_status() return parse_soup(r.content) @@ -41,28 +43,32 @@ def query(endpoint, text): @hook.command("dpis", "gis") def dogpileimage(text): """ - Uses the dogpile search engine to search for images.""" - soup = query('images', text) - results_container = soup.find('div', {'class': 'images-bing__list'}) + soup = query("images", text) + results_container = soup.find("div", {"class": "images-bing__list"}) if not results_container: return "No results found." - results_list = results_container.find_all('div', {'class': 'image'}) + results_list = results_container.find_all("div", {"class": "image"}) if not results_list: return "No results found." image = random.choice(results_list) - return image.find('a', {'class': 'link'})['href'] + return image.find("a", {"class": "link"})["href"] @hook.command("dp", "g", "dogpile") def dogpile(text): """ - Uses the dogpile search engine to find shit on the web.""" - soup = query('web', text) - results = soup.find_all('div', {'class': 'web-bing__result'}) + soup = query("web", text) + results = soup.find_all("div", {"class": "web-bing__result"}) if not results: return "No results found." result = results[0] - result_url = result.find('a', {'class': 'web-bing__title', 'href': True})['href'] - result_description = result.find('span', {'class': 'web-bing__description'}).text + result_url = result.find("a", {"class": "web-bing__title", "href": True})[ + "href" + ] + result_description = result.find( + "span", {"class": "web-bing__description"} + ).text return "{} -- \x02{}\x02".format(result_url, result_description) diff --git a/plugins/domainr.py b/plugins/domainr.py index 7d5e37ed..89110c45 100644 --- a/plugins/domainr.py +++ b/plugins/domainr.py @@ -1,4 +1,4 @@ -from urllib.error import URLError, HTTPError +from urllib.error import HTTPError, URLError from cloudbot import hook from cloudbot.util import http @@ -6,7 +6,7 @@ formats = { "taken": "\x034{domain}\x0f{path}", "available": "\x033{domain}\x0f{path}", - "other": "\x031{domain}\x0f{path}" + "other": "\x031{domain}\x0f{path}", } @@ -28,11 +28,11 @@ def domainr(text): :type text: str """ try: - data = http.get_json('http://domai.nr/api/json/search?q=' + text) + data = http.get_json("http://domai.nr/api/json/search?q=" + text) except (URLError, HTTPError): return "Unable to get data for some reason. Try again later." - if data['query'] == "": - return "An error occurred: {status} - {message}".format(**data['error']) + if data["query"] == "": + return "An error occurred: {status} - {message}".format(**data["error"]) domains = [format_domain(domain) for domain in data["results"]] return "Domains: {}".format(", ".join(domains)) diff --git a/plugins/dragonvale.py b/plugins/dragonvale.py index af1d0e5a..5c13659e 100644 --- a/plugins/dragonvale.py +++ b/plugins/dragonvale.py @@ -13,17 +13,14 @@ def striphtml(data): - string = re.compile(r'<.*?>') - return string.sub('', data) + string = re.compile(r"<.*?>") + return string.sub("", data) @hook.command("dragon", "ds") def dragonsearch(text, reply): """ - Searches the dragonvale wiki for the specified text.""" - params = { - "query": text.strip(), - "limit": 1 - } + params = {"query": text.strip(), "limit": 1} r = requests.get(search_url, params=params) @@ -37,8 +34,11 @@ def dragonsearch(text, reply): return "The API returned error code {}.".format(r.status_code) data = r.json()["items"][0] - out = "\x02{}\x02 -- {}: {}".format(data["title"], striphtml(data["snippet"]).split("…")[0].strip(), - data["url"]) + out = "\x02{}\x02 -- {}: {}".format( + data["title"], + striphtml(data["snippet"]).split("…")[0].strip(), + data["url"], + ) return out @@ -59,15 +59,11 @@ def egg_calculator(text): time = time_parse(timer.strip()) if not time: return "invalid time format" - params = { - 'time': time, - 'time2': time2, - 'avail': 1 - } + params = {"time": time, "time2": time2, "avail": 1} r = requests.get(egg_calc_url, params=params, timeout=5) soup = parse_soup(r.text) dragons = [] - for line in soup.findAll('td', {'class': 'views-field views-field-title'}): + for line in soup.findAll("td", {"class": "views-field views-field-title"}): dragons.append(line.text.replace("\n", "").strip()) return ", ".join(dragons) diff --git a/plugins/dramatica.py b/plugins/dramatica.py index 7b9d9c17..0de185ae 100644 --- a/plugins/dramatica.py +++ b/plugins/dramatica.py @@ -16,7 +16,9 @@ def drama(text, reply): """ - gets the first paragraph of the Encyclopedia Dramatica article on """ - search_response = requests.get(api_url, params={"action": "opensearch", "search": text}) + search_response = requests.get( + api_url, params={"action": "opensearch", "search": text} + ) try: search_response.raise_for_status() @@ -31,9 +33,9 @@ def drama(text, reply): if not data[1]: return "No results found." - article_name = data[1][0].replace(' ', '_') + article_name = data[1][0].replace(" ", "_") - url = ed_url + parse.quote(article_name, '') + url = ed_url + parse.quote(article_name, "") page_response = requests.get(url) @@ -51,7 +53,7 @@ def drama(text, reply): for p in page.xpath('//div[@id="bodyContent"]/p'): if p.text_content(): summary = " ".join(p.text_content().splitlines()) - summary = re.sub(r'\[\d+\]', '', summary) + summary = re.sub(r"\[\d+\]", "", summary) summary = formatting.truncate(summary, 220) return "{} - {}".format(summary, url) diff --git a/plugins/drinks.py b/plugins/drinks.py index 40f8b22f..9bb2e271 100644 --- a/plugins/drinks.py +++ b/plugins/drinks.py @@ -1,11 +1,12 @@ import json import os import random +from typing import Any, Dict, List from cloudbot import hook from cloudbot.util import web -drink_data = [] +drink_data: List[Dict[str, Any]] = [] @hook.onload() @@ -16,24 +17,26 @@ def load_drinks(bot): drink_data.extend(json.load(json_data)) -@hook.command('drink') +@hook.command("drink") def drink_cmd(text, chan, action): """ - makes the user a random cocktail.""" index = random.randint(0, len(drink_data) - 1) - drink = drink_data[index]['title'] - url = web.try_shorten(drink_data[index]['url']) - if drink.endswith(' recipe'): + drink = drink_data[index]["title"] + url = web.try_shorten(drink_data[index]["url"]) + if drink.endswith(" recipe"): drink = drink[:-7] - contents = drink_data[index]['ingredients'] + contents = drink_data[index]["ingredients"] out = "grabs some" for x in contents: if x == contents[len(contents) - 1]: out += " and {}".format(x) else: out += " {},".format(x) - if drink[0].lower() in ['a', 'e', 'i', 'o', 'u']: - article = 'an' + if drink[0].lower() in ["a", "e", "i", "o", "u"]: + article = "an" else: - article = 'a' - out += "\x0F and makes {} {} \x02{}\x02. {}".format(text, article, drink, url) + article = "a" + out += "\x0F and makes {} {} \x02{}\x02. {}".format( + text, article, drink, url + ) action(out, chan) diff --git a/plugins/eightball.py b/plugins/eightball.py index 3b53e989..c3936d91 100644 --- a/plugins/eightball.py +++ b/plugins/eightball.py @@ -1,11 +1,12 @@ import codecs import os import random +from typing import List from cloudbot import hook from cloudbot.util import colors -responses = [] +responses: List[str] = [] @hook.on_start() @@ -14,8 +15,7 @@ def load_responses(bot): responses.clear() with codecs.open(path, encoding="utf-8") as f: responses.extend( - line.strip() for line in f.readlines() - if not line.startswith("//") + line.strip() for line in f.readlines() if not line.startswith("//") ) diff --git a/plugins/etymology.py b/plugins/etymology.py index 8ad82d6b..8d2a67c8 100644 --- a/plugins/etymology.py +++ b/plugins/etymology.py @@ -23,7 +23,7 @@ def etymology(text, reply): :type text: str """ - url = 'http://www.etymonline.com/index.php' + url = "http://www.etymonline.com/index.php" response = requests.get(url, params={"term": text}) @@ -32,7 +32,9 @@ def etymology(text, reply): except HTTPError as e: if e.response.status_code == 404: return "No etymology found for {} :(".format(text) - reply("Error reaching etymonline.com: {}".format(e.response.status_code)) + reply( + "Error reaching etymonline.com: {}".format(e.response.status_code) + ) raise if response.status_code != requests.codes.ok: @@ -40,13 +42,13 @@ def etymology(text, reply): soup = parse_soup(response.text) - block = soup.find('div', class_=re.compile("word--.+")) + block = soup.find("div", class_=re.compile("word--.+")) - etym = ' '.join(e.text for e in block.div) + etym = " ".join(e.text for e in block.div) - etym = ' '.join(etym.splitlines()) + etym = " ".join(etym.splitlines()) - etym = ' '.join(etym.split()) + etym = " ".join(etym.split()) etym = formatting.truncate(etym, 200) diff --git a/plugins/fact.py b/plugins/fact.py index 70c3d16d..aeffcbe6 100644 --- a/plugins/fact.py +++ b/plugins/fact.py @@ -3,7 +3,7 @@ from cloudbot import hook from cloudbot.util import http -types = ['trivia', 'math', 'date', 'year'] +types = ["trivia", "math", "date", "year"] @hook.command(autohelp=False) @@ -11,10 +11,12 @@ def fact(reply): """- Gets a random fact about numbers or dates.""" fact_type = random.choice(types) try: - json = http.get_json('http://numbersapi.com/random/{}?json'.format(fact_type)) + json = http.get_json( + "http://numbersapi.com/random/{}?json".format(fact_type) + ) except Exception: reply("There was an error contacting the numbersapi.com API.") raise - response = json['text'] + response = json["text"] return response diff --git a/plugins/factoids.py b/plugins/factoids.py index a0c7f2dd..359756f0 100644 --- a/plugins/factoids.py +++ b/plugins/factoids.py @@ -1,6 +1,7 @@ import re import string from collections import defaultdict +from typing import Dict from sqlalchemy import Column, PrimaryKeyConstraint, String, Table, and_ @@ -11,9 +12,7 @@ # below is the default factoid in every channel you can modify it however you like default_dict = {"commands": "https://snoonet.org/gonzobot"} -factoid_cache = defaultdict(default_dict.copy) - -re_lineends = re.compile(r'[\r\n]*') +factoid_cache: Dict[str, Dict[str, str]] = defaultdict(default_dict.copy) FACTOID_CHAR = "?" # TODO: config @@ -24,7 +23,7 @@ Column("data", String), Column("nick", String), Column("chan", String), - PrimaryKeyConstraint('word', 'chan') + PrimaryKeyConstraint("word", "chan"), ) @@ -54,12 +53,18 @@ def add_factoid(db, word, chan, data, nick): """ if word in factoid_cache[chan]: # if we have a set value, update - db.execute(table.update().values(data=data, nick=nick, chan=chan).where(table.c.chan == chan).where( - table.c.word == word)) + db.execute( + table.update() + .values(data=data, nick=nick, chan=chan) + .where(table.c.chan == chan) + .where(table.c.word == word) + ) db.commit() else: # otherwise, insert - db.execute(table.insert().values(word=word, data=data, nick=nick, chan=chan)) + db.execute( + table.insert().values(word=word, data=data, nick=nick, chan=chan) + ) db.commit() load_cache(db) @@ -97,19 +102,23 @@ def remember(text, nick, db, chan, notice, event): except LookupError: old_data = None - if data.startswith('+') and old_data: + if data.startswith("+") and old_data: # remove + symbol new_data = data[1:] # append new_data to the old_data - if len(new_data) > 1 and new_data[1] in (string.punctuation + ' '): + if len(new_data) > 1 and new_data[1] in (string.punctuation + " "): data = old_data + new_data else: - data = old_data + ' ' + new_data + data = old_data + " " + new_data notice("Appending \x02{}\x02 to \x02{}\x02".format(new_data, old_data)) else: - notice('Remembering \x02{0}\x02 for \x02{1}\x02. Type {2}{1} to see it.'.format(data, word, FACTOID_CHAR)) + notice( + "Remembering \x02{0}\x02 for \x02{1}\x02. Type {2}{1} to see it.".format( + data, word, FACTOID_CHAR + ) + ) if old_data: - notice('Previous data was \x02{}\x02'.format(old_data)) + notice("Previous data was \x02{}\x02".format(old_data)) add_factoid(db, word, chan, data, nick) @@ -117,8 +126,8 @@ def remember(text, nick, db, chan, notice, event): def paste_facts(facts, raise_on_no_paste=False): headers = ("Command", "Output") data = [(FACTOID_CHAR + fact[0], fact[1]) for fact in sorted(facts.items())] - tbl = gen_markdown_table(headers, data).encode('UTF-8') - return web.paste(tbl, 'md', 'hastebin', raise_on_no_paste=raise_on_no_paste) + tbl = gen_markdown_table(headers, data).encode("UTF-8") + return web.paste(tbl, "md", "hastebin", raise_on_no_paste=raise_on_no_paste) def remove_fact(chan, names, db, notice): @@ -132,9 +141,11 @@ def remove_fact(chan, names, db, notice): missing.append(name) if missing: - notice("Unknown factoids: {}".format( - get_text_list([repr(s) for s in missing], 'and') - )) + notice( + "Unknown factoids: {}".format( + get_text_list([repr(s) for s in missing], "and") + ) + ) if found: try: @@ -158,7 +169,9 @@ def forget(text, chan, db, notice): remove_fact(chan, text.split(), db, notice) -@hook.command('forgetall', 'clearfacts', autohelp=False, permissions=['op', 'chanop']) +@hook.command( + "forgetall", "clearfacts", autohelp=False, permissions=["op", "chanop"] +) def forget_all(chan, db): """- Remove all factoids in the current channel @@ -181,7 +194,7 @@ def info(text, chan, notice): notice("Unknown Factoid.") -factoid_re = re.compile(r'^{} ?(.+)'.format(re.escape(FACTOID_CHAR)), re.I) +factoid_re = re.compile(r"^{} ?(.+)".format(re.escape(FACTOID_CHAR)), re.I) @hook.regex(factoid_re) diff --git a/plugins/feeds.py b/plugins/feeds.py index 1378cdaa..c8cfd685 100644 --- a/plugins/feeds.py +++ b/plugins/feeds.py @@ -1,7 +1,7 @@ import feedparser from cloudbot import hook -from cloudbot.util import web, formatting +from cloudbot.util import formatting, web class FeedAlias: @@ -11,35 +11,34 @@ def __init__(self, url, limit=3): ALIASES = { - 'xkcd': FeedAlias('http://xkcd.com/rss.xml'), - - 'ars': FeedAlias('http://feeds.arstechnica.com/arstechnica/index'), - - 'pip': FeedAlias('https://pypi.python.org/pypi?%3Aaction=rss', 6), - 'pypi': FeedAlias('https://pypi.python.org/pypi?%3Aaction=rss', 6), - 'py': FeedAlias('https://pypi.python.org/pypi?%3Aaction=rss', 6), - - 'pipnew': FeedAlias('https://pypi.python.org/pypi?%3Aaction=packages_rss', 5), - 'pypinew': FeedAlias('https://pypi.python.org/pypi?%3Aaction=packages_rss', 5), - 'pynew': FeedAlias('https://pypi.python.org/pypi?%3Aaction=packages_rss', 5), - - 'world': FeedAlias( - 'https://news.google.com/news?cf=all&ned=us&hl=en&topic=w&output=rss' + "xkcd": FeedAlias("http://xkcd.com/rss.xml"), + "ars": FeedAlias("http://feeds.arstechnica.com/arstechnica/index"), + "pip": FeedAlias("https://pypi.python.org/pypi?%3Aaction=rss", 6), + "pypi": FeedAlias("https://pypi.python.org/pypi?%3Aaction=rss", 6), + "py": FeedAlias("https://pypi.python.org/pypi?%3Aaction=rss", 6), + "pipnew": FeedAlias( + "https://pypi.python.org/pypi?%3Aaction=packages_rss", 5 ), - - 'us': FeedAlias( - 'https://news.google.com/news?cf=all&ned=us&hl=en&topic=n&output=rss' + "pypinew": FeedAlias( + "https://pypi.python.org/pypi?%3Aaction=packages_rss", 5 ), - 'usa': FeedAlias( - 'https://news.google.com/news?cf=all&ned=us&hl=en&topic=n&output=rss' + "pynew": FeedAlias( + "https://pypi.python.org/pypi?%3Aaction=packages_rss", 5 ), - - 'nz': FeedAlias( - 'https://news.google.com/news?pz=1&cf=all&ned=nz&hl=en&topic=n&output=rss' + "world": FeedAlias( + "https://news.google.com/news?cf=all&ned=us&hl=en&topic=w&output=rss" ), - - 'anand': FeedAlias('http://www.anandtech.com/rss/'), - 'anandtech': FeedAlias('http://www.anandtech.com/rss/'), + "us": FeedAlias( + "https://news.google.com/news?cf=all&ned=us&hl=en&topic=n&output=rss" + ), + "usa": FeedAlias( + "https://news.google.com/news?cf=all&ned=us&hl=en&topic=n&output=rss" + ), + "nz": FeedAlias( + "https://news.google.com/news?pz=1&cf=all&ned=nz&hl=en&topic=n&output=rss" + ), + "anand": FeedAlias("http://www.anandtech.com/rss/"), + "anandtech": FeedAlias("http://www.anandtech.com/rss/"), } @@ -69,7 +68,7 @@ def rss(text): for item in feed.entries[:limit]: out.append(format_item(item)) - if 'title' in feed.feed: + if "title" in feed.feed: start = "\x02{}\x02: ".format(feed.feed.title) else: start = "" diff --git a/plugins/flip.py b/plugins/flip.py index 6e9ae951..182b5082 100644 --- a/plugins/flip.py +++ b/plugins/flip.py @@ -1,48 +1,49 @@ import random from collections import defaultdict +from typing import Dict, Optional from cloudbot import hook from cloudbot.util import formatting -table_status = defaultdict(lambda: None) +table_status: Dict[str, Optional[bool]] = defaultdict(lambda: None) USE_FLIPPERS = True replacements = { - 'a': 'ɐ', - 'b': 'q', - 'c': 'ɔ', - 'd': 'p', - 'e': 'ǝ', - 'f': 'ɟ', - 'g': 'ƃ', - 'h': 'ɥ', - 'i': 'ᴉ', - 'j': 'ɾ', - 'k': 'ʞ', - 'l': 'ן', - 'm': 'ɯ', - 'n': 'u', - 'o': 'o', - 'p': 'd', - 'q': 'b', - 'r': 'ɹ', - 's': 's', - 't': 'ʇ', - 'u': 'n', - 'v': 'ʌ', - 'w': 'ʍ', - 'x': 'x', - 'y': 'ʎ', - 'z': 'z', - '?': '¿', - '.': '˙', - ',': '\'', - '(': ')', - '<': '>', - '[': ']', - '{': '}', - '\'': ',', - '_': '‾', + "a": "ɐ", + "b": "q", + "c": "ɔ", + "d": "p", + "e": "ǝ", + "f": "ɟ", + "g": "ƃ", + "h": "ɥ", + "i": "ᴉ", + "j": "ɾ", + "k": "ʞ", + "l": "ן", + "m": "ɯ", + "n": "u", + "o": "o", + "p": "d", + "q": "b", + "r": "ɹ", + "s": "s", + "t": "ʇ", + "u": "n", + "v": "ʌ", + "w": "ʍ", + "x": "x", + "y": "ʎ", + "z": "z", + "?": "¿", + ".": "˙", + ",": "'", + "(": ")", + "<": ">", + "[": "]", + "{": "}", + "'": ",", + "_": "‾", } # append an inverted form of replacements to itself, so flipping works both ways @@ -56,8 +57,15 @@ def flip(text, reply, message, chan): """ - Flips over.""" if USE_FLIPPERS: - if text in ['table', 'tables']: - message(random.choice([random.choice(flippers) + " ︵ " + "\u253B\u2501\u253B", table_flipper])) + if text in ["table", "tables"]: + message( + random.choice( + [ + random.choice(flippers) + " ︵ " + "\u253B\u2501\u253B", + table_flipper, + ] + ) + ) table_status[chan] = True elif text == "5318008": out = "BOOBIES" @@ -66,7 +74,11 @@ def flip(text, reply, message, chan): out = "5318008" message(random.choice(flippers) + " ︵ " + out) else: - message(random.choice(flippers) + " ︵ " + formatting.multi_replace(text[::-1], replacements)) + message( + random.choice(flippers) + + " ︵ " + + formatting.multi_replace(text[::-1], replacements) + ) else: reply(formatting.multi_replace(text[::-1], replacements)) @@ -74,16 +86,24 @@ def flip(text, reply, message, chan): @hook.command def table(text, message): """ - (╯°□°)╯︵ <ʇxǝʇ>""" - message(random.choice(flippers) + " ︵ " + formatting.multi_replace(text[::-1].lower(), replacements)) + message( + random.choice(flippers) + + " ︵ " + + formatting.multi_replace(text[::-1].lower(), replacements) + ) @hook.command def fix(text, reply, message, chan): """ - fixes a flipped over table. ┬─┬ノ(ಠ_ಠノ)""" - if text in ['table', 'tables']: + if text in ["table", "tables"]: if table_status.pop(chan, False) is True: message("┬─┬ノ(ಠ_ಠノ)") else: - message("no tables have been turned over in {}, thanks for checking!".format(chan)) + message( + "no tables have been turned over in {}, thanks for checking!".format( + chan + ) + ) else: flip(text, reply, message, chan) diff --git a/plugins/fmk.py b/plugins/fmk.py index 8ae20060..74af2688 100644 --- a/plugins/fmk.py +++ b/plugins/fmk.py @@ -1,10 +1,11 @@ import codecs import os import random +from typing import List from cloudbot import hook -fmklist = [] +fmklist: List[str] = [] @hook.on_start() @@ -13,12 +14,22 @@ def load_fmk(bot): :type bot: cloudbot.bot.Cloudbot """ fmklist.clear() - with codecs.open(os.path.join(bot.data_dir, "fmk.txt"), encoding="utf-8") as f: - fmklist.extend(line.strip() for line in f.readlines() if not line.startswith("//")) + with codecs.open( + os.path.join(bot.data_dir, "fmk.txt"), encoding="utf-8" + ) as f: + fmklist.extend( + line.strip() for line in f.readlines() if not line.startswith("//") + ) @hook.command("fmk", autohelp=False) def fmk(text, message): """[nick] - Fuck, Marry, Kill""" - message(" {} FMK - {}, {}, {}".format((text.strip() if text.strip() else ""), random.choice(fmklist), - random.choice(fmklist), random.choice(fmklist))) + message( + " {} FMK - {}, {}, {}".format( + (text.strip() if text.strip() else ""), + random.choice(fmklist), + random.choice(fmklist), + random.choice(fmklist), + ) + ) diff --git a/plugins/foaas.py b/plugins/foaas.py index d54f73da..f5a92ec9 100644 --- a/plugins/foaas.py +++ b/plugins/foaas.py @@ -1,6 +1,7 @@ import json import random from pathlib import Path +from typing import Dict, List import requests @@ -8,17 +9,17 @@ BASE_URL = "http://www.foaas.com/{fuck}/{target}" -headers = {'Accept': 'text/plain'} +headers = {"Accept": "text/plain"} -fuck_offs = {} +fuck_offs: Dict[str, List[str]] = {} def format_url(fucker, fuckee=None): if fuckee: - fucks = fuck_offs['fuck_offs'] + fucks = fuck_offs["fuck_offs"] target = "\2{fuckee}\2/{fucker}".format(fuckee=fuckee, fucker=fucker) else: - fucks = fuck_offs['single_fucks'] + fucks = fuck_offs["single_fucks"] target = fucker return BASE_URL.format(fuck=random.choice(fucks), target=target) @@ -35,11 +36,11 @@ def get_fuck_off(fucker, fuckee): def load_fuck_offs(bot): fuck_offs.clear() data_file = Path(bot.data_dir) / "foaas.json" - with data_file.open(encoding='utf-8') as f: + with data_file.open(encoding="utf-8") as f: fuck_offs.update(json.load(f)) -@hook.command('fos', 'fuckoff', 'foaas', autohelp=False) +@hook.command("fos", "fuckoff", "foaas", autohelp=False) def foaas(text, nick, message): """[name] - tell some one to fuck off or just .fos for a generic fuckoff""" out = get_fuck_off(nick, text) diff --git a/plugins/fortune.py b/plugins/fortune.py index 5835f3e9..2e4631a4 100644 --- a/plugins/fortune.py +++ b/plugins/fortune.py @@ -1,10 +1,11 @@ import codecs import os import random +from typing import List from cloudbot import hook -fortunes = [] +fortunes: List[str] = [] @hook.on_start() @@ -12,7 +13,9 @@ def load_fortunes(bot): path = os.path.join(bot.data_dir, "fortunes.txt") fortunes.clear() with codecs.open(path, encoding="utf-8") as f: - fortunes.extend(line.strip() for line in f.readlines() if not line.startswith("//")) + fortunes.extend( + line.strip() for line in f.readlines() if not line.startswith("//") + ) @hook.command(autohelp=False) diff --git a/plugins/gaming.py b/plugins/gaming.py index 0a6d1e69..578331e1 100644 --- a/plugins/gaming.py +++ b/plugins/gaming.py @@ -15,10 +15,12 @@ from cloudbot import hook -whitespace_re = re.compile(r'\s+') -valid_diceroll = re.compile(r'^([+-]?(?:\d+|\d*d(?:\d+|F))(?:[+-](?:\d+|\d*d(?:\d+|F)))*)( .+)?$', re.I) -sign_re = re.compile(r'[+-]?(?:\d*d)?(?:\d+|F)', re.I) -split_re = re.compile(r'([\d+-]*)d?(F|\d*)', re.I) +whitespace_re = re.compile(r"\s+") +valid_diceroll = re.compile( + r"^([+-]?(?:\d+|\d*d(?:\d+|F))(?:[+-](?:\d+|\d*d(?:\d+|F)))*)( .+)?$", re.I +) +sign_re = re.compile(r"[+-]?(?:\d*d)?(?:\d+|F)", re.I) +split_re = re.compile(r"([\d+-]*)d?(F|\d*)", re.I) def clamp(n, min_value, max_value): @@ -37,7 +39,7 @@ def n_rolls(count, n): :type count: int :type n: int | str """ - if n in ('f', 'F'): + if n in ("f", "F"): return [random.randint(-1, 1) for _ in range(min(count, 100))] if count < 100: @@ -45,7 +47,7 @@ def n_rolls(count, n): # Calculate a random sum approximated using a randomized normal variate with the midpoint used as the mu # and an approximated standard deviation based on variance as the sigma - mid = .5 * (n + 1) * count + mid = 0.5 * (n + 1) * count var = (n ** 2 - 1) / 12 adj_var = (var * count) ** 0.5 @@ -72,7 +74,7 @@ def dice(text, notice): if "d" not in text: return - spec = whitespace_re.sub('', text) + spec = whitespace_re.sub("", text) if not valid_diceroll.match(spec): notice("Invalid dice roll '{}'".format(text)) return @@ -122,9 +124,9 @@ def choose(text, event): :type text: str """ - choices = re.findall(r'([^,]+)', text.strip()) + choices = re.findall(r"([^,]+)", text.strip()) if len(choices) == 1: - choices = choices[0].split(' or ') + choices = choices[0].split(" or ") if len(choices) == 1: event.notice_doc() return @@ -149,13 +151,21 @@ def coin(text, notice, action): amount = 1 if amount == 1: - action("flips a coin and gets {}.".format(random.choice(["heads", "tails"]))) + action( + "flips a coin and gets {}.".format( + random.choice(["heads", "tails"]) + ) + ) elif amount == 0: action("makes a coin flipping motion") else: - mu = .5 * amount - sigma = (.75 * amount) ** .5 + mu = 0.5 * amount + sigma = (0.75 * amount) ** 0.5 n = random.normalvariate(mu, sigma) heads = clamp(int(round(n)), 0, amount) tails = amount - heads - action("flips {} coins and gets {} heads and {} tails.".format(amount, heads, tails)) + action( + "flips {} coins and gets {} heads and {} tails.".format( + amount, heads, tails + ) + ) diff --git a/plugins/geoip.py b/plugins/geoip.py index c1d655b8..f102b186 100644 --- a/plugins/geoip.py +++ b/plugins/geoip.py @@ -14,7 +14,9 @@ logger = logging.getLogger("cloudbot") -DB_URL = "http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz" +DB_URL = ( + "http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz" +) PATH = "./data/GeoLite2-City.mmdb" @@ -31,8 +33,8 @@ def fetch_db(): os.remove(PATH) r = requests.get(DB_URL, stream=True) if r.status_code == 200: - with gzip.open(r.raw, 'rb') as infile: - with open(PATH, 'wb') as outfile: + with gzip.open(r.raw, "rb") as infile: + with open(PATH, "wb") as outfile: shutil.copyfileobj(infile, outfile) @@ -87,18 +89,22 @@ async def geoip(text, reply, loop): return "Invalid input." try: - location_data = await loop.run_in_executor(None, geoip_reader.reader.city, ip) + location_data = await loop.run_in_executor( + None, geoip_reader.reader.city, ip + ) except geoip2.errors.AddressNotFoundError: return "Sorry, I can't locate that in my database." data = { "cc": location_data.country.iso_code or "N/A", "country": location_data.country.name or "Unknown", - "city": location_data.city.name or "Unknown" + "city": location_data.city.name or "Unknown", } # add a region to the city if one is listed if location_data.subdivisions.most_specific.name: data["city"] += ", " + location_data.subdivisions.most_specific.name - reply("\x02Country:\x02 {country} ({cc}), \x02City:\x02 {city}".format(**data)) + reply( + "\x02Country:\x02 {country} ({cc}), \x02City:\x02 {city}".format(**data) + ) diff --git a/plugins/giphy.py b/plugins/giphy.py index 81165d21..ef4cdad5 100644 --- a/plugins/giphy.py +++ b/plugins/giphy.py @@ -5,7 +5,7 @@ from cloudbot import hook from cloudbot.bot import bot -api_url = 'http://api.giphy.com/v1/gifs' +api_url = "http://api.giphy.com/v1/gifs" @hook.command("gif", "giphy") @@ -13,21 +13,18 @@ def giphy(text): """ - Searches giphy.com for a gif using the provided search term.""" api_key = bot.config.get_api_key("giphy") term = text.strip() - search_url = api_url + '/search' - params = { - 'q': term, - 'limit': 10, - 'fmt': "json", - 'api_key': api_key - } + search_url = api_url + "/search" + params = {"q": term, "limit": 10, "fmt": "json", "api_key": api_key} results = requests.get(search_url, params=params) results.raise_for_status() r = results.json() - if not r['data']: + if not r["data"]: return "no results found." - gif = random.choice(r['data']) - if gif['rating']: - out = "{} content rating: \x02{}\x02. (Powered by GIPHY)".format(gif['embed_url'], gif['rating'].upper()) + gif = random.choice(r["data"]) + if gif["rating"]: + out = "{} content rating: \x02{}\x02. (Powered by GIPHY)".format( + gif["embed_url"], gif["rating"].upper() + ) else: - out = "{} - (Powered by GIPHY)".format(gif['embed_url']) + out = "{} - (Powered by GIPHY)".format(gif["embed_url"]) return out diff --git a/plugins/github.py b/plugins/github.py index d9efb5de..8f389974 100644 --- a/plugins/github.py +++ b/plugins/github.py @@ -6,7 +6,9 @@ from cloudbot.util import formatting, web shortcuts = {} -url_re = re.compile(r"(?:https?://github\.com/)?(?P[^/]+)/(?P[^/]+)") +url_re = re.compile( + r"(?:https?://github\.com/)?(?P[^/]+)/(?P[^/]+)" +) def parse_url(url): @@ -31,12 +33,16 @@ def load_shortcuts(bot): def issue_cmd(text): """ [number] - gets issue [number]'s summary, or the open issue count if no issue is specified""" args = text.split() - owner, repo = parse_url(args[0] if args[0] not in shortcuts else shortcuts[args[0]]) + owner, repo = parse_url( + args[0] if args[0] not in shortcuts else shortcuts[args[0]] + ) issue = args[1] if len(args) > 1 else None if issue: r = requests.get( - "https://api.github.com/repos/{}/{}/issues/{}".format(owner, repo, issue) + "https://api.github.com/repos/{}/{}/issues/{}".format( + owner, repo, issue + ) ) r.raise_for_status() j = r.json() @@ -48,10 +54,16 @@ def issue_cmd(text): if j["state"] == "open": state = "\x033\x02Opened\x02\x0f by {}".format(j["user"]["login"]) else: - state = "\x034\x02Closed\x02\x0f by {}".format(j["closed_by"]["login"]) + state = "\x034\x02Closed\x02\x0f by {}".format( + j["closed_by"]["login"] + ) - return "Issue #{} ({}): {} | {}: {}".format(number, state, url, title, summary) - r = requests.get("https://api.github.com/repos/{}/{}/issues".format(owner, repo)) + return "Issue #{} ({}): {} | {}: {}".format( + number, state, url, title, summary + ) + r = requests.get( + "https://api.github.com/repos/{}/{}/issues".format(owner, repo) + ) r.raise_for_status() j = r.json() diff --git a/plugins/gnomeagainsthumanity.py b/plugins/gnomeagainsthumanity.py index 13493c50..5b969cfb 100644 --- a/plugins/gnomeagainsthumanity.py +++ b/plugins/gnomeagainsthumanity.py @@ -3,33 +3,36 @@ import os import random import re +from typing import Dict, List from cloudbot import hook -gnomecards = {} +gnomecards: Dict[str, List[str]] = {} @hook.on_start() def shuffle_deck(bot): gnomecards.clear() - with codecs.open(os.path.join(bot.data_dir, "gnomecards.json"), encoding="utf-8") as f: + with codecs.open( + os.path.join(bot.data_dir, "gnomecards.json"), encoding="utf-8" + ) as f: gnomecards.update(json.load(f)) -@hook.command('cah') +@hook.command("cah") def CAHwhitecard(text): """ - Submit text to be used as a CAH whitecard""" - return random.choice(gnomecards['black']).format(text) + return random.choice(gnomecards["black"]).format(text) -@hook.command('cahb') +@hook.command("cahb") def CAHblackcard(text): """ - Submit text with _ for the bot to fill in the rest. You can submit text with multiple _""" CardText = text.strip() # noinspection PyUnusedLocal def blankfiller(matchobj): - return random.choice(gnomecards['white']) + return random.choice(gnomecards["white"]) - out = re.sub(r'\b_\b', blankfiller, CardText) + out = re.sub(r"\b_\b", blankfiller, CardText) return out diff --git a/plugins/google_cse.py b/plugins/google_cse.py index c3bf24fd..a1a9255c 100644 --- a/plugins/google_cse.py +++ b/plugins/google_cse.py @@ -15,12 +15,12 @@ from cloudbot import hook from cloudbot.bot import bot -from cloudbot.util import formatting, filesize +from cloudbot.util import filesize, formatting -API_CS = 'https://www.googleapis.com/customsearch/v1' +API_CS = "https://www.googleapis.com/customsearch/v1" -@hook.command('gse') +@hook.command("gse") def gse(text): """ - Returns first Google search result for .""" dev_key = bot.config.get_api_key("google_dev_key") @@ -30,25 +30,27 @@ def gse(text): if not cx: return "This command requires a custom Google Search Engine ID." - parsed = requests.get(API_CS, params={"cx": cx, "q": text, "key": dev_key}).json() + parsed = requests.get( + API_CS, params={"cx": cx, "q": text, "key": dev_key} + ).json() try: - result = parsed['items'][0] + result = parsed["items"][0] except KeyError: return "No results found." - title = formatting.truncate_str(result['title'], 60) - content = result['snippet'] + title = formatting.truncate_str(result["title"], 60) + content = result["snippet"] if not content: content = "No description available." else: - content = formatting.truncate_str(content.replace('\n', ''), 150) + content = formatting.truncate_str(content.replace("\n", ""), 150) - return u'{} -- \x02{}\x02: "{}"'.format(result['link'], title, content) + return '{} -- \x02{}\x02: "{}"'.format(result["link"], title, content) -@hook.command('gseis', 'image') +@hook.command("gseis", "image") def gse_gis(text): """ - Returns first Google Images result for .""" dev_key = bot.config.get_api_key("google_dev_key") @@ -58,15 +60,20 @@ def gse_gis(text): if not cx: return "This command requires a custom Google Search Engine ID." - parsed = requests.get(API_CS, params={"cx": cx, "q": text, "searchType": "image", "key": dev_key}).json() + parsed = requests.get( + API_CS, + params={"cx": cx, "q": text, "searchType": "image", "key": dev_key}, + ).json() try: - result = parsed['items'][0] - metadata = parsed['items'][0]['image'] + result = parsed["items"][0] + metadata = parsed["items"][0]["image"] except KeyError: return "No results found." - dimens = '{}x{}px'.format(metadata['width'], metadata['height']) - size = filesize.size(int(metadata['byteSize'])) + dimens = "{}x{}px".format(metadata["width"], metadata["height"]) + size = filesize.size(int(metadata["byteSize"])) - return u'{} [{}, {}, {}]'.format(result['link'], dimens, result['mime'], size) + return "{} [{}, {}, {}]".format( + result["link"], dimens, result["mime"], size + ) diff --git a/plugins/google_translate.py b/plugins/google_translate.py index 22bfea40..d8a7272b 100644 --- a/plugins/google_translate.py +++ b/plugins/google_translate.py @@ -8,34 +8,31 @@ def goog_trans(text, source, target): api_key = bot.config.get_api_key("google_dev_key") - url = 'https://www.googleapis.com/language/translate/v2' + url = "https://www.googleapis.com/language/translate/v2" if len(text) > max_length: return "This command only supports input of less then 100 characters." - params = { - 'q': text, - 'key': api_key, - 'target': target, - 'format': 'text' - } + params = {"q": text, "key": api_key, "target": target, "format": "text"} if source: - params['source'] = source + params["source"] = source request = requests.get(url, params=params) parsed = request.json() - if parsed.get('error'): - if parsed['error']['code'] == 403: + if parsed.get("error"): + if parsed["error"]["code"] == 403: return "The Translate API is off in the Google Developers Console." return "Google API error." if not source: - return '(%(detectedSourceLanguage)s) %(translatedText)s' % (parsed['data']['translations'][0]) + return "(%(detectedSourceLanguage)s) %(translatedText)s" % ( + parsed["data"]["translations"][0] + ) - return '%(translatedText)s' % parsed['data']['translations'][0] + return "%(translatedText)s" % parsed["data"]["translations"][0] def match_language(fragment): @@ -54,28 +51,28 @@ def match_language(fragment): @hook.command("google_translate") def translate(text): """[source language [target language]] - translates from source language (default autodetect) - to target language (default English) using Google Translate""" + to target language (default English) using Google Translate""" api_key = bot.config.get_api_key("google_dev_key") if not api_key: return "This command requires a Google Developers Console API key." - args = text.split(' ', 2) + args = text.split(" ", 2) try: if len(args) >= 2: sl = match_language(args[0]) if not sl: - return goog_trans(text, '', 'en') + return goog_trans(text, "", "en") if len(args) == 2: - return goog_trans(args[1], sl, 'en') + return goog_trans(args[1], sl, "en") if len(args) >= 3: tl = match_language(args[1]) if not tl: - if sl == 'en': - return 'unable to determine desired target language' - return goog_trans(args[1] + ' ' + args[2], sl, 'en') + if sl == "en": + return "unable to determine desired target language" + return goog_trans(args[1] + " " + args[2], sl, "en") return goog_trans(args[2], sl, tl) - return goog_trans(text, '', 'en') + return goog_trans(text, "", "en") except IOError as e: return e @@ -140,5 +137,5 @@ def translate(text): ("ur", "Urdu"), ("vi", "Vietnamese"), ("cy", "Welsh"), - ("yi", "Yiddish") + ("yi", "Yiddish"), ] diff --git a/plugins/googleurlparse.py b/plugins/googleurlparse.py index 130e4125..bb8b3613 100644 --- a/plugins/googleurlparse.py +++ b/plugins/googleurlparse.py @@ -3,7 +3,7 @@ from cloudbot import hook -spamurl = re.compile(r'.*(((www\.)?google\.com/url\?)[^ ]+)', re.I) +spamurl = re.compile(r".*(((www\.)?google\.com/url\?)[^ ]+)", re.I) @hook.regex(spamurl) @@ -12,6 +12,14 @@ def google_url(match): url = matches url = "http://{}".format(url) - out = "".join([(unquote(a[4:]) if a[:4] == "url=" else "") for a in url.split("&")]) \ - .replace(", ,", "").strip() + out = ( + "".join( + [ + (unquote(a[4:]) if a[:4] == "url=" else "") + for a in url.split("&") + ] + ) + .replace(", ,", "") + .strip() + ) return out diff --git a/plugins/grab.py b/plugins/grab.py index fe57821b..91ce4f5f 100644 --- a/plugins/grab.py +++ b/plugins/grab.py @@ -2,27 +2,28 @@ import random from collections import defaultdict from threading import RLock +from typing import Dict, List -from sqlalchemy import Table, Column, String +from sqlalchemy import Column, String, Table from sqlalchemy.exc import SQLAlchemyError from cloudbot import hook from cloudbot.util import database -from cloudbot.util.pager import paginated_list, CommandPager +from cloudbot.util.pager import CommandPager, paginated_list -search_pages = defaultdict(dict) +search_pages: Dict[str, Dict[str, CommandPager]] = defaultdict(dict) table = Table( - 'grab', + "grab", database.metadata, - Column('name', String), - Column('time', String), - Column('quote', String), - Column('chan', String) + Column("name", String), + Column("time", String), + Column("quote", String), + Column("chan", String), ) -grab_cache = {} -grab_locks = defaultdict(dict) +grab_cache: Dict[str, Dict[str, List[str]]] = {} +grab_locks: Dict[str, Dict[str, RLock]] = defaultdict(dict) grab_locks_lock = RLock() cache_lock = RLock() @@ -70,7 +71,9 @@ def check_grabs(name, quote, chan): def grab_add(nick, time, msg, chan, db): # Adds a quote to the grab table - db.execute(table.insert().values(name=nick, time=time, quote=msg, chan=chan)) + db.execute( + table.insert().values(name=nick, time=time, quote=msg, chan=chan) + ) db.commit() load_cache(db) @@ -90,20 +93,28 @@ def grab(text, nick, chan, db, conn): return "Didn't your mother teach you not to grab yourself?" with grab_locks_lock: - grab_lock = grab_locks[conn.name.casefold()].setdefault(chan.casefold(), RLock()) + grab_lock = grab_locks[conn.name.casefold()].setdefault( + chan.casefold(), RLock() + ) with grab_lock: name, timestamp, msg = get_latest_line(conn, chan, text) if not msg: - return "I couldn't find anything from {} in recent history.".format(text) + return "I couldn't find anything from {} in recent history.".format( + text + ) if check_grabs(text.casefold(), msg, chan): - return "I already have that quote from {} in the database".format(text) + return "I already have that quote from {} in the database".format( + text + ) try: grab_add(name.casefold(), timestamp, msg, chan, db) except SQLAlchemyError: - logger.exception("Error occurred when grabbing %s in %s", name, chan) + logger.exception( + "Error occurred when grabbing %s in %s", name, chan + ) return "Error occurred." if check_grabs(name.casefold(), msg, chan): @@ -114,7 +125,7 @@ def grab(text, nick, chan, db, conn): def format_grab(name, quote): # add nonbreaking space to nicks to avoid highlighting people with printed grabs - name = "{}{}{}".format(name[0], u"\u200B", name[1:]) + name = "{}{}{}".format(name[0], "\u200B", name[1:]) if quote.startswith("\x01ACTION") or quote.startswith("*"): quote = quote.replace("\x01ACTION", "").replace("\x01", "") out = "* {}{}".format(name, quote) @@ -158,7 +169,9 @@ def grabrandom(text, chan, message): matching_quotes.extend((nick, quote) for quote in quotes) else: matching_quotes.extend( - (name, quote) for name, quotes in chan_grabs.items() for quote in quotes + (name, quote) + for name, quotes in chan_grabs.items() + for quote in quotes ) if not matching_quotes: @@ -189,7 +202,11 @@ def grabsearch(text, chan, conn): for name, quotes in chan_grabs.items(): if name != lower_text: - result.extend((name, quote) for quote in quotes if lower_text in quote.lower()) + result.extend( + (name, quote) + for quote in quotes + if lower_text in quote.lower() + ) if not result: return "I couldn't find any matches for {}.".format(text) diff --git a/plugins/herald.py b/plugins/herald.py index 35b1739a..cc68fa86 100644 --- a/plugins/herald.py +++ b/plugins/herald.py @@ -2,24 +2,29 @@ import random import re from collections import defaultdict +from typing import Dict -from sqlalchemy import Table, Column, String, PrimaryKeyConstraint +from sqlalchemy import Column, PrimaryKeyConstraint, String, Table from cloudbot import hook from cloudbot.util import database table = Table( - 'herald', + "herald", database.metadata, - Column('name', String), - Column('chan', String), - Column('quote', String), - PrimaryKeyConstraint('name', 'chan') + Column("name", String), + Column("chan", String), + Column("quote", String), + PrimaryKeyConstraint("name", "chan"), ) class ChannelData: - def __init__(self, chan_interval=datetime.timedelta(seconds=10), user_interval=datetime.timedelta(minutes=5)): + def __init__( + self, + chan_interval=datetime.timedelta(seconds=10), + user_interval=datetime.timedelta(minutes=5), + ): self.user_interval = user_interval self.chan_interval = chan_interval self.next_send = datetime.datetime.min @@ -38,8 +43,10 @@ def can_send(self, nick: str, join_time: datetime.datetime) -> bool: return True -user_join = defaultdict(lambda: defaultdict(ChannelData)) -herald_cache = defaultdict(dict) +user_join: Dict[str, Dict[str, ChannelData]] = defaultdict( + lambda: defaultdict(ChannelData) +) +herald_cache: Dict[str, Dict[str, str]] = defaultdict(dict) @hook.on_start @@ -68,19 +75,30 @@ def herald(text, nick, chan, db, reply): if greeting is None: return "no herald set, unable to delete." - query = table.delete().where(table.c.name == nick.lower()).where(table.c.chan == chan.lower()) + query = ( + table.delete() + .where(table.c.name == nick.lower()) + .where(table.c.chan == chan.lower()) + ) db.execute(query) db.commit() - reply("greeting \'{}\' for {} has been removed".format(greeting, nick)) + reply("greeting '{}' for {} has been removed".format(greeting, nick)) load_cache(db) else: res = db.execute( - table.update().where(table.c.name == nick.lower()).where(table.c.chan == chan.lower()).values(quote=text) + table.update() + .where(table.c.name == nick.lower()) + .where(table.c.chan == chan.lower()) + .values(quote=text) ) if res.rowcount == 0: - db.execute(table.insert().values(name=nick.lower(), chan=chan.lower(), quote=text)) + db.execute( + table.insert().values( + name=nick.lower(), chan=chan.lower(), quote=text + ) + ) db.commit() reply("greeting successfully added") @@ -88,14 +106,18 @@ def herald(text, nick, chan, db, reply): load_cache(db) -@hook.command(permissions=["botcontrol", "snoonetstaff", "deleteherald", "chanop"]) +@hook.command( + permissions=["botcontrol", "snoonetstaff", "deleteherald", "chanop"] +) def deleteherald(text, chan, db, reply): """ - Delete [nickname]'s herald.""" nick = text.strip() res = db.execute( - table.delete().where(table.c.name == nick.lower()).where(table.c.chan == chan.lower()) + table.delete() + .where(table.c.name == nick.lower()) + .where(table.c.chan == chan.lower()) ) db.commit() @@ -115,10 +137,10 @@ def should_send(conn, chan, nick, join_time) -> bool: @hook.irc_raw("JOIN", singlethread=True) def welcome(nick, message, bot, chan, conn): - decoy = re.compile('[Òo○O0öøóȯôőŏᴏōο][<><]') - colors_re = re.compile(r'\x02|\x03(?:\d{1,2}(?:,\d{1,2})?)?', re.UNICODE) - bino_re = re.compile('b+i+n+o+', re.IGNORECASE) - offensive_re = re.compile('卐') + decoy = re.compile("[Òo○O0öøóȯôőŏᴏōο][<><]") + colors_re = re.compile(r"\x02|\x03(?:\d{1,2}(?:,\d{1,2})?)?", re.UNICODE) + bino_re = re.compile("b+i+n+o+", re.IGNORECASE) + offensive_re = re.compile("卐") grab = bot.plugin_manager.find_plugin("grab") @@ -129,10 +151,12 @@ def welcome(nick, message, bot, chan, conn): if not should_send(conn.name, chan, nick, datetime.datetime.now()): return - stripped = greet.translate(dict.fromkeys(map(ord, ["\u200b", " ", "\u202f", "\x02"]))) + stripped = greet.translate( + dict.fromkeys(map(ord, ["\u200b", " ", "\u202f", "\x02"])) + ) stripped = colors_re.sub("", stripped) - greet = re.sub(bino_re, 'flenny', greet) - greet = re.sub(offensive_re, ' freespeech oppression ', greet) + greet = re.sub(bino_re, "flenny", greet) + greet = re.sub(offensive_re, " freespeech oppression ", greet) words = greet.lower().split() cmd = words.pop(0) diff --git a/plugins/hookup.py b/plugins/hookup.py index 425b74d8..ac225516 100644 --- a/plugins/hookup.py +++ b/plugins/hookup.py @@ -3,28 +3,32 @@ import os import random import time +from typing import Any, Dict -from sqlalchemy import table, column, String, Float, and_, select +from sqlalchemy import Float, String, and_, column, select, table from cloudbot import hook from cloudbot.util.database import metadata from cloudbot.util.textgen import TextGenerator -hookups = {} +hookups: Dict[str, Any] = {} + seen_table = table( - 'seen_user', - column('name', String), - column('time', Float), - column('quote', String), - column('chan', String), - column('host', String), + "seen_user", + column("name", String), + column("time", Float), + column("quote", String), + column("chan", String), + column("host", String), ) @hook.on_start def load_data(bot): hookups.clear() - with codecs.open(os.path.join(bot.data_dir, "hookup.json"), encoding="utf-8") as f: + with codecs.open( + os.path.join(bot.data_dir, "hookup.json"), encoding="utf-8" + ) as f: hookups.update(json.load(f)) @@ -35,10 +39,12 @@ def hookup(db, chan): return times = time.time() - 86400 - results = db.execute(select( - [seen_table.c.name], - and_(seen_table.c.chan == chan, seen_table.c.time > times) - )).fetchall() + results = db.execute( + select( + [seen_table.c.name], + and_(seen_table.c.chan == chan, seen_table.c.time > times), + ) + ).fetchall() if not results or len(results) < 2: return "something went wrong" @@ -48,10 +54,10 @@ def hookup(db, chan): random.shuffle(people) person1, person2 = people[:2] variables = { - 'user1': person1, - 'user2': person2, + "user1": person1, + "user2": person2, } generator = TextGenerator( - hookups['templates'], hookups['parts'], variables=variables + hookups["templates"], hookups["parts"], variables=variables ) return generator.generate_string() diff --git a/plugins/imdb.py b/plugins/imdb.py index 8cae0eea..138efa23 100644 --- a/plugins/imdb.py +++ b/plugins/imdb.py @@ -4,65 +4,66 @@ from cloudbot import hook -id_re = re.compile(r'tt\d+') -imdb_re = re.compile(r'https?://(?:www\.)?imdb\.com/+title/+(tt[0-9]+)', re.I) +id_re = re.compile(r"tt\d+") +imdb_re = re.compile(r"https?://(?:www\.)?imdb\.com/+title/+(tt[0-9]+)", re.I) @hook.command def imdb(text, bot): """ - gets information about from IMDb""" - headers = {'User-Agent': bot.user_agent} + headers = {"User-Agent": bot.user_agent} strip = text.strip() if id_re.match(strip): - endpoint = 'title' - params = {'id': strip} + endpoint = "title" + params = {"id": strip} else: - endpoint = 'search' - params = {'q': strip, 'limit': 1} + endpoint = "search" + params = {"q": strip, "limit": 1} request = requests.get( "https://imdb-scraper.herokuapp.com/" + endpoint, params=params, - headers=headers) + headers=headers, + ) request.raise_for_status() content = request.json() - if content['success'] is False: - return 'Unknown error' + if content["success"] is False: + return "Unknown error" - if not content['result']: - return 'No movie found' + if not content["result"]: + return "No movie found" - result = content['result'] - if endpoint == 'search': + result = content["result"] + if endpoint == "search": result = result[0] # part of the search results, not 1 record - url = 'http://www.imdb.com/title/{}'.format(result['id']) - return movie_str(result) + ' ' + url + url = "http://www.imdb.com/title/{}".format(result["id"]) + return movie_str(result) + " " + url @hook.regex(imdb_re) def imdb_url(match, bot): - headers = {'User-Agent': bot.user_agent} + headers = {"User-Agent": bot.user_agent} - params = {'id': match.group(1)} + params = {"id": match.group(1)} request = requests.get( "https://imdb-scraper.herokuapp.com/title", params=params, - headers=headers) + headers=headers, + ) content = request.json() - if content['success'] is True: - return movie_str(content['result']) + if content["success"] is True: + return movie_str(content["result"]) def movie_str(movie): - movie['genre'] = ', '.join(movie['genres']) - out = '\x02%(title)s\x02 (%(year)s) (%(genre)s): %(plot)s' - if movie['runtime'] != 'N/A': - out += ' \x02%(runtime)s\x02.' - if movie['rating'] != 'N/A' and movie['votes'] != 'N/A': - out += ' \x02%(rating)s/10\x02 with \x02%(votes)s\x02' \ - ' votes.' + movie["genre"] = ", ".join(movie["genres"]) + out = "\x02%(title)s\x02 (%(year)s) (%(genre)s): %(plot)s" + if movie["runtime"] != "N/A": + out += " \x02%(runtime)s\x02." + if movie["rating"] != "N/A" and movie["votes"] != "N/A": + out += " \x02%(rating)s/10\x02 with \x02%(votes)s\x02" " votes." return out % movie diff --git a/plugins/imgur.py b/plugins/imgur.py index 6980fd50..ee54871f 100644 --- a/plugins/imgur.py +++ b/plugins/imgur.py @@ -71,7 +71,7 @@ def get_items(text): @hook.command(autohelp=False) def imgur(text): """[search term] / [/r/subreddit] / [/user/username] / memes / random - returns a link to a random imgur image based - on your input. if no input is given the bot will get an image from the imgur frontpage """ + on your input. if no input is given the bot will get an image from the imgur frontpage""" text = text.strip().lower() if not container.api: @@ -102,7 +102,9 @@ def imgur(text): # if it's an imgur meme, add the meme name # if not, AttributeError will trigger and code will carry on with suppress(AttributeError): - title = "\x02{}\x02 - {}".format(item.meme_metadata["meme_name"].lower(), title) + title = "\x02{}\x02 - {}".format( + item.meme_metadata["meme_name"].lower(), title + ) # if the item has a tag, show that if item.section: @@ -127,7 +129,7 @@ def imgur(text): @hook.command("imguralbum", "multiimgur", "imgalbum", "album", autohelp=False) def imguralbum(text, conn): """[search term] / [/r/subreddit] / [/user/username] / memes / random - returns a link to lots of random images - based on your input. if no input is given the bot will get images from the imgur frontpage """ + based on your input. if no input is given the bot will get images from the imgur frontpage""" text = text.strip().lower() if not container.api: @@ -147,10 +149,10 @@ def imguralbum(text, conn): nsfw = any([item.nsfw for item in items]) params = { - 'title': '{} presents: "{}"'.format(conn.nick, text or "random images"), - 'ids': ",".join([item.id for item in items]), - 'layout': 'blog', - 'account_url': None + "title": '{} presents: "{}"'.format(conn.nick, text or "random images"), + "ids": ",".join([item.id for item in items]), + "layout": "blog", + "account_url": None, } album = container.api.create_album(params) diff --git a/plugins/issafe.py b/plugins/issafe.py index cc578bfc..d1c039be 100644 --- a/plugins/issafe.py +++ b/plugins/issafe.py @@ -24,16 +24,26 @@ @hook.command() def issafe(text): """ - Checks the website against Google's Safe Browsing List.""" - if urlparse(text).scheme not in ['https', 'http']: + if urlparse(text).scheme not in ["https", "http"]: return "Check your URL (it should be a complete URI)." dev_key = bot.config.get_api_key("google_dev_key") - parsed = requests.get(API_SB, params={"url": text, "client": "cloudbot", "key": dev_key, "pver": "3.1", - "appver": str(cloudbot.__version__)}) + parsed = requests.get( + API_SB, + params={ + "url": text, + "client": "cloudbot", + "key": dev_key, + "pver": "3.1", + "appver": str(cloudbot.__version__), + }, + ) parsed.raise_for_status() if parsed.status_code == 204: condition = "\x02{}\x02 is safe.".format(text) else: - condition = "\x02{}\x02 is known to contain: {}".format(text, parsed.text) + condition = "\x02{}\x02 is known to contain: {}".format( + text, parsed.text + ) return condition diff --git a/plugins/jokes.py b/plugins/jokes.py index 63013dec..0151e5fd 100644 --- a/plugins/jokes.py +++ b/plugins/jokes.py @@ -11,10 +11,12 @@ def load_joke_file(path): :type path: Path :rtype: List[str] """ - with path.open(encoding='utf-8') as f: - return [line.strip() for line in f - if line.strip() - and not line.startswith('//')] + with path.open(encoding="utf-8") as f: + return [ + line.strip() + for line in f + if line.strip() and not line.startswith("//") + ] @hook.on_start() @@ -25,15 +27,15 @@ def load_jokes(bot): """ data_directory = Path(bot.data_dir) file_list = [ - 'yo_momma.txt', - 'do_it.txt', - 'puns.txt', - 'confucious.txt', - 'one_liners.txt', - 'wisdom.txt', - 'book_puns.txt', - 'lawyerjoke.txt', - 'kero.txt', + "yo_momma.txt", + "do_it.txt", + "puns.txt", + "confucious.txt", + "one_liners.txt", + "wisdom.txt", + "book_puns.txt", + "lawyerjoke.txt", + "kero.txt", ] for file_name in file_list: file_path = data_directory / file_name @@ -46,20 +48,20 @@ def yomomma(text, nick, conn, is_nick_valid): target = text.strip() if not is_nick_valid(target) or target.lower() == conn.nick.lower(): target = nick - joke = random.choice(joke_lines['yo_momma']).lower() - return '{}, {}'.format(target, joke) + joke = random.choice(joke_lines["yo_momma"]).lower() + return "{}, {}".format(target, joke) @hook.command(autohelp=False) def doit(message): """- Prints a do it line, example: mathematicians do with a pencil.""" - message(random.choice(joke_lines['do_it'])) + message(random.choice(joke_lines["do_it"])) @hook.command(autohelp=False) def pun(message): """- Come on everyone loves puns right?""" - message(random.choice(joke_lines['puns'])) + message(random.choice(joke_lines["puns"])) @hook.command(autohelp=False) @@ -67,27 +69,27 @@ def confucious(message): """- Confucious say man standing on toilet is high on pot. (Note that the spelling is deliberate: https://www.urbandictionary.com/define.php?term=Confucious) """ - saying = random.choice(joke_lines['confucious']).lower() - message('Confucious say {}'.format(saying)) + saying = random.choice(joke_lines["confucious"]).lower() + message("Confucious say {}".format(saying)) @hook.command(autohelp=False) def dadjoke(message): """- Love em or hate em, bring on the dad jokes.""" - message(random.choice(joke_lines['one_liners'])) + message(random.choice(joke_lines["one_liners"])) @hook.command(autohelp=False) def wisdom(message): """- Words of wisdom from various bathroom stalls.""" - message(random.choice(joke_lines['wisdom'])) + message(random.choice(joke_lines["wisdom"])) @hook.command(autohelp=False) def bookpun(message): """- Suggests a pun of a book title/author.""" # suggestions = ["Why not try", "You should read", "You gotta check out"] - message(random.choice(joke_lines['book_puns'])) + message(random.choice(joke_lines["book_puns"])) @hook.command("boobs", "boobies") @@ -95,9 +97,13 @@ def boobies(text): """ - Everything is better with boobies!""" boob = "\u2299" out = text.strip() - out = out.replace('o', boob).replace('O', boob).replace('0', boob) + out = out.replace("o", boob).replace("O", boob).replace("0", boob) if out == text.strip(): - return "Sorry I couldn't turn anything in '{}' into boobs for you.".format(out) + return ( + "Sorry I couldn't turn anything in '{}' into boobs for you.".format( + out + ) + ) return out @@ -113,11 +119,13 @@ def awesome(text, is_nick_valid): """ - Returns a link to show how awesome they are. See https://github.com/sebastianbarfurth/is-awesome.cool """ - target = text.split(' ')[0] + target = text.split(" ")[0] if not is_nick_valid(target): return "Sorry I can't tell {} how awesome they are.".format(target) - link = 'http://{}.is-awesome.cool/'.format(target) - return "{}: I am blown away by your recent awesome action(s). Please read \x02{}\x02".format(target, link) + link = "http://{}.is-awesome.cool/".format(target) + return "{}: I am blown away by your recent awesome action(s). Please read \x02{}\x02".format( + target, link + ) @hook.command(autohelp=False) @@ -132,7 +140,7 @@ def triforce(message): @hook.command("kero", "kerowhack") def kero(text): """ - Returns the text input the way kerouac5 would say it.""" - keror = random.choice(joke_lines['kero']).upper() + keror = random.choice(joke_lines["kero"]).upper() if keror == "???? WTF IS": out = keror + " " + text.upper() else: @@ -143,7 +151,7 @@ def kero(text): @hook.command(autohelp=False) def lawyerjoke(message): """- Returns a lawyer joke, so lawyers know how much we hate them.""" - message(random.choice(joke_lines['lawyerjoke'])) + message(random.choice(joke_lines["lawyerjoke"])) @hook.command(autohelp=False) diff --git a/plugins/karma.py b/plugins/karma.py index 6bfb21d8..e1f890fb 100644 --- a/plugins/karma.py +++ b/plugins/karma.py @@ -3,29 +3,41 @@ from collections import defaultdict import sqlalchemy -from sqlalchemy import Table, String, Column, Integer, PrimaryKeyConstraint, select, and_ +from sqlalchemy import ( + Column, + Integer, + PrimaryKeyConstraint, + String, + Table, + and_, + select, +) from cloudbot import hook from cloudbot.util import database -karmaplus_re = re.compile(r'^.*\+\+$') -karmaminus_re = re.compile('^.*--$') +karmaplus_re = re.compile(r"^.*\+\+$") +karmaminus_re = re.compile("^.*--$") karma_table = Table( - 'karma', + "karma", database.metadata, - Column('name', String), - Column('chan', String), - Column('thing', String), - Column('score', Integer), - PrimaryKeyConstraint('name', 'chan', 'thing') + Column("name", String), + Column("chan", String), + Column("thing", String), + Column("score", Integer), + PrimaryKeyConstraint("name", "chan", "thing"), ) @hook.on_start def remove_non_channel_points(db): """Temporary on_start hook to remove non-channel points""" - db.execute(karma_table.delete().where(sqlalchemy.not_(karma_table.c.chan.startswith('#')))) + db.execute( + karma_table.delete().where( + sqlalchemy.not_(karma_table.c.chan.startswith("#")) + ) + ) db.commit() @@ -35,13 +47,19 @@ def update_score(nick, chan, thing, score, db): return thing = thing.strip() - clause = and_(karma_table.c.name == nick, karma_table.c.chan == chan, karma_table.c.thing == thing.lower()) + clause = and_( + karma_table.c.name == nick, + karma_table.c.chan == chan, + karma_table.c.thing == thing.lower(), + ) karma = db.execute(select([karma_table.c.score]).where(clause)).fetchone() if karma: score += int(karma[0]) query = karma_table.update().values(score=score).where(clause) else: - query = karma_table.insert().values(name=nick, chan=chan, thing=thing.lower(), score=score) + query = karma_table.insert().values( + name=nick, chan=chan, thing=thing.lower(), score=score + ) db.execute(query) db.commit() @@ -56,7 +74,7 @@ def addpoint(text, nick, chan, db): @hook.regex(karmaplus_re) def re_addpt(match, nick, chan, db, notice): """no useful help txt""" - thing = match.group().split('++')[0] + thing = match.group().split("++")[0] if thing: addpoint(thing, nick, chan, db) else: @@ -73,8 +91,16 @@ def rmpoint(text, nick, chan, db): def pluspts(nick, chan, db): """- prints the things you have liked and their scores""" output = "" - clause = and_(karma_table.c.name == nick, karma_table.c.chan == chan, karma_table.c.score >= 0) - query = select([karma_table.c.thing, karma_table.c.score]).where(clause).order_by(karma_table.c.score.desc()) + clause = and_( + karma_table.c.name == nick, + karma_table.c.chan == chan, + karma_table.c.score >= 0, + ) + query = ( + select([karma_table.c.thing, karma_table.c.score]) + .where(clause) + .order_by(karma_table.c.score.desc()) + ) likes = db.execute(query).fetchall() for like in likes: @@ -87,8 +113,16 @@ def pluspts(nick, chan, db): def minuspts(nick, chan, db): """- prints the things you have disliked and their scores""" output = "" - clause = and_(karma_table.c.name == nick, karma_table.c.chan == chan, karma_table.c.score <= 0) - query = select([karma_table.c.thing, karma_table.c.score]).where(clause).order_by(karma_table.c.score) + clause = and_( + karma_table.c.name == nick, + karma_table.c.chan == chan, + karma_table.c.score <= 0, + ) + query = ( + select([karma_table.c.thing, karma_table.c.score]) + .where(clause) + .order_by(karma_table.c.score) + ) likes = db.execute(query).fetchall() for like in likes: @@ -100,7 +134,7 @@ def minuspts(nick, chan, db): @hook.regex(karmaminus_re) def re_rmpt(match, nick, chan, db, notice): """no useful help txt""" - thing = match.group().split('--')[0] + thing = match.group().split("--")[0] if thing: rmpoint(thing, nick, chan, db) else: @@ -114,11 +148,16 @@ def points_cmd(text, chan, db): thing = "" if text.endswith(("-global", " global")): thing = text[:-7].strip() - query = select([karma_table.c.score]).where(karma_table.c.thing == thing.lower()) + query = select([karma_table.c.score]).where( + karma_table.c.thing == thing.lower() + ) else: text = text.strip() - query = select([karma_table.c.score]).where(karma_table.c.thing == text.lower()).where( - karma_table.c.chan == chan) + query = ( + select([karma_table.c.score]) + .where(karma_table.c.thing == text.lower()) + .where(karma_table.c.chan == chan) + ) karma = db.execute(query).fetchall() if karma: @@ -131,20 +170,27 @@ def points_cmd(text, chan, db): pos += int(k[0]) score += int(k[0]) if thing: - return "{} has a total score of {} (+{}/{}) across all channels I know about.".format(thing, score, pos, - neg) - return "{} has a total score of {} (+{}/{}) in {}.".format(text, score, pos, neg, chan) + return "{} has a total score of {} (+{}/{}) across all channels I know about.".format( + thing, score, pos, neg + ) + return "{} has a total score of {} (+{}/{}) in {}.".format( + text, score, pos, neg, chan + ) return "I couldn't find {} in the database.".format(text) def parse_lookup(text, db, chan, name): - if text in ('global', '-global'): - items = db.execute(select([karma_table.c.thing, karma_table.c.score])).fetchall() + if text in ("global", "-global"): + items = db.execute( + select([karma_table.c.thing, karma_table.c.score]) + ).fetchall() out = "The {{}} most {} things in all channels are: ".format(name) else: items = db.execute( - select([karma_table.c.thing, karma_table.c.score]).where(karma_table.c.chan == chan) + select([karma_table.c.thing, karma_table.c.score]).where( + karma_table.c.chan == chan + ) ).fetchall() out = "The {{}} most {} things in {{}} are: ".format(name) @@ -153,7 +199,7 @@ def parse_lookup(text, db, chan, name): def do_list(text, db, chan, loved=True): counts = defaultdict(int) - out, items = parse_lookup(text, db, chan, 'loved' if loved else 'hated') + out, items = parse_lookup(text, db, chan, "loved" if loved else "hated") if items: for item in items: thing = item[0] @@ -162,9 +208,8 @@ def do_list(text, db, chan, loved=True): scores = counts.items() sorts = sorted(scores, key=operator.itemgetter(1), reverse=loved)[:10] - out = out.format(len(sorts), chan) + ' \u2022 '.join( - "{} with {} points".format(thing[0], thing[1]) - for thing in sorts + out = out.format(len(sorts), chan) + " \u2022 ".join( + "{} with {} points".format(thing[0], thing[1]) for thing in sorts ) return out diff --git a/plugins/kenm.py b/plugins/kenm.py index fff3309e..77ff9b2c 100644 --- a/plugins/kenm.py +++ b/plugins/kenm.py @@ -1,10 +1,11 @@ import codecs import os import random +from typing import List from cloudbot import hook -kenm_data = [] +kenm_data: List[str] = [] @hook.on_start() @@ -13,8 +14,12 @@ def load_kenm(bot): :type bot: cloudbot.bot.Cloudbot """ kenm_data.clear() - with codecs.open(os.path.join(bot.data_dir, "kenm.txt"), encoding="utf-8") as f: - kenm_data.extend(line.strip() for line in f.readlines() if not line.startswith("//")) + with codecs.open( + os.path.join(bot.data_dir, "kenm.txt"), encoding="utf-8" + ) as f: + kenm_data.extend( + line.strip() for line in f.readlines() if not line.startswith("//") + ) @hook.command("kenm", autohelp=False) diff --git a/plugins/lenny.py b/plugins/lenny.py index 3513c6bc..f5a3000f 100644 --- a/plugins/lenny.py +++ b/plugins/lenny.py @@ -1,27 +1,28 @@ import json import random from pathlib import Path +from typing import Dict, List from cloudbot import hook -lenny_data = {} +lenny_data: Dict[str, List[str]] = {} @hook.on_start def load_faces(bot): lenny_data.clear() data_file = Path(bot.data_dir) / "lenny.json" - with data_file.open(encoding='utf-8') as f: + with data_file.open(encoding="utf-8") as f: lenny_data.update(json.load(f)) @hook.command(autohelp=False) def lenny(message): """- why the shit not lennyface""" - message(random.choice(lenny_data['lenny'])) + message(random.choice(lenny_data["lenny"])) @hook.command(autohelp=False) def flenny(message): """- flenny is watching.""" - message(random.choice(lenny_data['flenny'])) + message(random.choice(lenny_data["flenny"])) diff --git a/plugins/librefm.py b/plugins/librefm.py index b6a8f950..e7d1f3bb 100644 --- a/plugins/librefm.py +++ b/plugins/librefm.py @@ -1,10 +1,11 @@ from datetime import datetime +from typing import List, Tuple import requests -from sqlalchemy import Table, Column, PrimaryKeyConstraint, String +from sqlalchemy import Column, PrimaryKeyConstraint, String, Table from cloudbot import hook -from cloudbot.util import timeformat, web, database +from cloudbot.util import database, timeformat, web api_url = "https://libre.fm/2.0/?format=json" @@ -14,12 +15,12 @@ table = Table( "librefm", database.metadata, - Column('nick', String), - Column('acc', String), - PrimaryKeyConstraint('nick') + Column("nick", String), + Column("acc", String), + PrimaryKeyConstraint("nick"), ) -last_cache = [] +last_cache: List[Tuple[str, str]] = [] def api_request(method, **params): @@ -75,40 +76,49 @@ def librefm(text, nick, db, event): event.notice_doc() return - response, err = api_request('user.getrecenttracks', user=user, limit=1) + response, err = api_request("user.getrecenttracks", user=user, limit=1) if err: return err - if 'error' in response: + if "error" in response: # return "libre.fm Error: {}.".format(response["message"]) - return "libre.fm Error: {} Code: {}.".format(response["error"]["#text"], response["error"]["code"]) + return "libre.fm Error: {} Code: {}.".format( + response["error"]["#text"], response["error"]["code"] + ) - if 'track' not in response['recenttracks'] or response['recenttracks']['track']: - return "No recent tracks for user \"{}\" found.".format(user) + if ( + "track" not in response["recenttracks"] + or response["recenttracks"]["track"] + ): + return 'No recent tracks for user "{}" found.'.format(user) tracks = response["recenttracks"]["track"] if isinstance(tracks, list): track = tracks[0] - if "@attr" in track and "nowplaying" in track["@attr"] and track["@attr"]["nowplaying"] == "true": + if ( + "@attr" in track + and "nowplaying" in track["@attr"] + and track["@attr"]["nowplaying"] == "true" + ): # if the user is listening to something, the first track (a dict) of the # tracks list will contain an item with the "@attr" key. # this item will will contain another item with the "nowplaying" key # which value will be "true" - status = 'is listening to' - ending = '.' + status = "is listening to" + ending = "." else: return elif isinstance(tracks, dict): track = tracks # otherwise, the user is not listening to anything right now - status = 'last listened to' + status = "last listened to" # lets see how long ago they listened to it time_listened = datetime.fromtimestamp(int(track["date"]["uts"])) time_since = timeformat.time_since(time_listened) - ending = ' ({} ago)'.format(time_since) + ending = " ({} ago)".format(time_since) else: return "error: could not parse track listing" @@ -133,7 +143,9 @@ def librefm(text, nick, db, event): out += ending if text and not dontsave: - res = db.execute(table.update().values(acc=user).where(table.c.nick == nick.lower())) + res = db.execute( + table.update().values(acc=user).where(table.c.nick == nick.lower()) + ) if res.rowcount <= 0: db.execute(table.insert().values(nick=nick.lower(), acc=user)) @@ -143,27 +155,27 @@ def librefm(text, nick, db, event): def getartisttags(artist): - tags, err = api_request('artist.getTopTags', artist=artist) + tags, err = api_request("artist.getTopTags", artist=artist) if err: return "error returning tags ({})".format(err) try: - tag = tags['toptags']['tag'] + tag = tags["toptags"]["tag"] except LookupError: - return 'no tags' + return "no tags" if isinstance(tag, dict): - tag_list = tag['name'] + tag_list = tag["name"] elif isinstance(tag, list): tag_list = [] for item in tag: - tag_list.append(item['name']) + tag_list.append(item["name"]) else: return "error returning tags" if isinstance(tag_list, list): tag_list = tag_list[0:4] - return ', '.join(tag_list) + return ", ".join(tag_list) return tag_list @@ -181,10 +193,10 @@ def displaybandinfo(text, bot): if err: return err - if 'error' in artist: - return 'No such artist.' + if "error" in artist: + return "No such artist." - a = artist['artist'] + a = artist["artist"] summary = a["bio"]["summary"] tags = getartisttags(a) @@ -198,12 +210,12 @@ def displaybandinfo(text, bot): return out -def getartistinfo(artist, user=''): +def getartistinfo(artist, user=""): params = {} if user: - params['username'] = user + params["username"] = user - return api_request('artist.getInfo', artist=artist, autocorrect=1, **params) + return api_request("artist.getInfo", artist=artist, autocorrect=1, **params) @hook.command("librecompare", "librelc") @@ -212,7 +224,9 @@ def librefmcompare(): return unsupported_msg -@hook.command("libretoptrack", "libretoptracks", "libretop", "librett", autohelp=False) +@hook.command( + "libretoptrack", "libretoptracks", "libretop", "librett", autohelp=False +) def toptrack(text, nick): """[username] - Grabs a list of the top tracks for a libre.fm username""" if text: @@ -225,11 +239,11 @@ def toptrack(text, nick): if not username: return "No librefm username specified and no librefm username is set in the database." - data, err = api_request('user.gettoptracks', user=username, limit=5) + data, err = api_request("user.gettoptracks", user=username, limit=5) if err: return err - if 'error' in data: + if "error" in data: return "Error: {}.".format(data["message"]) out = "{}'s favorite songs: ".format(username) @@ -237,7 +251,9 @@ def toptrack(text, nick): track_name = data["toptracks"]["track"][r]["name"] artist_name = data["toptracks"]["track"][r]["artist"]["name"] play_count = data["toptracks"]["track"][r]["playcount"] - out += "{} by {} listened to {:,} times. ".format(track_name, artist_name, int(play_count)) + out += "{} by {} listened to {:,} times. ".format( + track_name, artist_name, int(play_count) + ) return out @@ -255,18 +271,20 @@ def libretopartists(text, nick): if not username: return "No libre.fm username specified and no libre.fm username is set in the database." - data, err = api_request('user.gettopartists', user=username, limit=5) + data, err = api_request("user.gettopartists", user=username, limit=5) if err: return err - if 'error' in data: + if "error" in data: return "Error: {}.".format(data["message"]) out = "{}'s favorite artists: ".format(username) for r in range(5): artist_name = data["topartists"]["artist"][r]["name"] play_count = data["topartists"]["artist"][r]["playcount"] - out += "{} listened to {:,} times. ".format(artist_name, int(play_count)) + out += "{} listened to {:,} times. ".format( + artist_name, int(play_count) + ) return out @@ -274,21 +292,21 @@ def libretopartists(text, nick): def topweek(text, nick): """[username] - Grabs a list of the top artists in the last week for a libre.fm username. You can set your librefm username with .l username""" - return topartists(text, nick, '7day') + return topartists(text, nick, "7day") @hook.command("libreltm", "libretopmonth", autohelp=False) def topmonth(text, nick): """[username] - Grabs a list of the top artists in the last month for a libre.fm username. You can set your librefm username with .l username""" - return topartists(text, nick, '1month') + return topartists(text, nick, "1month") @hook.command("librelibrelta", "libretopall", autohelp=False) def topall(text, nick): """[username] - Grabs a list of the top artists in the last year for a libre.fm username. You can set your librefm username with .l username""" - return topartists(text, nick, '12month') + return topartists(text, nick, "12month") def topartists(text, nick, period): @@ -303,13 +321,13 @@ def topartists(text, nick, period): return "No librefm username specified and no librefm username is set in the database." data, err = api_request( - 'user.gettopartists', user=username, period=period, limit=10 + "user.gettopartists", user=username, period=period, limit=10 ) if err: return err - if 'error' in data: + if "error" in data: return data # return "Error: {}.".format(data["message"]) diff --git a/plugins/link_announcer.py b/plugins/link_announcer.py index 29754c49..55e67f72 100644 --- a/plugins/link_announcer.py +++ b/plugins/link_announcer.py @@ -3,7 +3,7 @@ import requests from cloudbot import hook -from cloudbot.hook import Priority, Action +from cloudbot.hook import Action, Priority from cloudbot.util.http import parse_soup MAX_TITLE = 100 @@ -44,35 +44,47 @@ def no_parens(pattern): (?::\d*)? # port - (?:/(?:""" + no_parens(PATH_SEG_CHARS) + r""")*(? - gets information about the Minecraft user """ - headers = {'User-Agent': bot.user_agent} + headers = {"User-Agent": bot.user_agent} text = text.strip() # check if we are looking up a UUID - cleaned = text.replace('-', '') - if re.search(r'^[0-9a-f]{32}$', cleaned, re.I): + cleaned = text.replace("-", "") + if re.search(r"^[0-9a-f]{32}$", cleaned, re.I): # we are looking up a UUID, get a name. try: name = get_name(cleaned) - except (requests.exceptions.HTTPError, requests.exceptions.ConnectionError, KeyError) as e: + except ( + requests.exceptions.HTTPError, + requests.exceptions.ConnectionError, + KeyError, + ) as e: reply("Could not get username from UUID: {}".format(e)) raise else: @@ -37,9 +41,14 @@ def mcuser(text, bot, reply): # get user data from fishbans try: - request = requests.get(HIST_API.format(requests.utils.quote(name)), headers=headers) + request = requests.get( + HIST_API.format(requests.utils.quote(name)), headers=headers + ) request.raise_for_status() - except (requests.exceptions.HTTPError, requests.exceptions.ConnectionError) as e: + except ( + requests.exceptions.HTTPError, + requests.exceptions.ConnectionError, + ) as e: reply("Could not get profile status: {}".format(e)) raise @@ -50,14 +59,18 @@ def mcuser(text, bot, reply): return "Could not parse profile status" # check for errors from fishbans and handle them - if not results['success']: - if results['error'] == "User is not premium.": - return "The account \x02{}\x02 is not premium or does not exist.".format(text) + if not results["success"]: + if results["error"] == "User is not premium.": + return "The account \x02{}\x02 is not premium or does not exist.".format( + text + ) - return results['error'] + return results["error"] - username = results['data']['username'] - uid = uuid.UUID(results['data']['uuid']) + username = results["data"]["username"] + uid = uuid.UUID(results["data"]["uuid"]) - return 'The account \x02{}\x02 ({}) exists. It is a \x02paid\x02' \ - ' account.'.format(username, uid) + return ( + "The account \x02{}\x02 ({}) exists. It is a \x02paid\x02" + " account.".format(username, uid) + ) diff --git a/plugins/minecraft_wiki.py b/plugins/minecraft_wiki.py index edc239b7..88799f45 100644 --- a/plugins/minecraft_wiki.py +++ b/plugins/minecraft_wiki.py @@ -15,10 +15,13 @@ def mcwiki(text, reply): """ - gets the first paragraph of the Minecraft Wiki article on """ try: - request = requests.get(api_url, params={'search': text.strip()}) + request = requests.get(api_url, params={"search": text.strip()}) request.raise_for_status() j = request.json() - except (requests.exceptions.HTTPError, requests.exceptions.ConnectionError) as e: + except ( + requests.exceptions.HTTPError, + requests.exceptions.ConnectionError, + ) as e: reply("Error fetching search results: {}".format(e)) raise except ValueError as e: @@ -34,17 +37,20 @@ def mcwiki(text, reply): items = [item for item in j[1] if "/" not in item] if items: - article_name = items[0].replace(' ', '_').encode('utf8') + article_name = items[0].replace(" ", "_").encode("utf8") else: # there are no items without /, just return a / one - article_name = j[1][0].replace(' ', '_').encode('utf8') + article_name = j[1][0].replace(" ", "_").encode("utf8") - url = mc_url + requests.utils.quote(article_name, '') + url = mc_url + requests.utils.quote(article_name, "") try: request_ = requests.get(url) request_.raise_for_status() - except (requests.exceptions.HTTPError, requests.exceptions.ConnectionError) as e: + except ( + requests.exceptions.HTTPError, + requests.exceptions.ConnectionError, + ) as e: reply("Error fetching wiki page: {}".format(e)) raise @@ -53,7 +59,7 @@ def mcwiki(text, reply): for p in page.xpath('//div[@class="mw-content-ltr"]/p'): if p.text_content(): summary = " ".join(p.text_content().splitlines()) - summary = re.sub(r'\[\d+\]', '', summary) + summary = re.sub(r"\[\d+\]", "", summary) summary = formatting.truncate(summary, 200) return "{} :: {}".format(summary, url) diff --git a/plugins/mock.py b/plugins/mock.py index a96636d8..882979bd 100644 --- a/plugins/mock.py +++ b/plugins/mock.py @@ -19,10 +19,12 @@ def mock(text, chan, conn, message): if line.startswith("\x01ACTION"): fmt = "* {nick} {msg}" - line = line[8:].strip(' \x01') + line = line[8:].strip(" \x01") else: fmt = "<{nick}> {msg}" # Return the message in aLtErNaTiNg cApS - line = "".join(c.upper() if i & 1 else c.lower() for i, c in enumerate(line)) + line = "".join( + c.upper() if i & 1 else c.lower() for i, c in enumerate(line) + ) message(fmt.format(nick=nick, msg=line)) diff --git a/plugins/myfitnesspal.py b/plugins/myfitnesspal.py index 42e83e65..f1035a8c 100644 --- a/plugins/myfitnesspal.py +++ b/plugins/myfitnesspal.py @@ -10,7 +10,7 @@ scrape_url = "http://www.myfitnesspal.com/food/diary/{}" -@hook.command('mfp', 'myfitnesspal') +@hook.command("mfp", "myfitnesspal") def mfp(text, reply): """ - returns macros from the MyFitnessPal food diary of """ request = requests.get(scrape_url.format(text)) @@ -29,29 +29,30 @@ def mfp(text, reply): try: soup = parse_soup(request.text) - title = soup.find('h1', {'class': 'main-title'}) + title = soup.find("h1", {"class": "main-title"}) if title: - if title.text == 'This Food Diary is Private': + if title.text == "This Food Diary is Private": return "{}'s food diary is private.".format(text) - if title.text == 'This Username is Invalid': + if title.text == "This Username is Invalid": return "User {} does not exist.".format(text) # the output of table depends on the user's MFP profile configuration headers = get_headers(soup) - totals = get_values(soup, 'total') - remaining = get_values(soup, 'alt') + totals = get_values(soup, "total") + remaining = get_values(soup, "alt") - for idx, val in enumerate(headers['captions']): + for idx, val in enumerate(headers["captions"]): kwargs = { - 'caption': val, - 'total': totals[idx], - 'remain': remaining[idx], - 'units': headers['units'][idx], - 'pct': math.floor((totals[idx] / remaining[idx]) * 100) + "caption": val, + "total": totals[idx], + "remain": remaining[idx], + "units": headers["units"][idx], + "pct": math.floor((totals[idx] / remaining[idx]) * 100), } - output += ("{caption}: {total}/{remain}{units} ({pct}%) " - .format(**kwargs)) + output += "{caption}: {total}/{remain}{units} ({pct}%) ".format( + **kwargs + ) output += " ({})".format(scrape_url.format(text)) @@ -64,35 +65,35 @@ def mfp(text, reply): def get_headers(soup): """get nutrient headers from the soup""" - headers = {'captions': [], 'units': []} + headers = {"captions": [], "units": []} - footer = soup.find('tfoot') - for cell in footer.findAll('td', {'class': 'nutrient-column'}): - div = cell.find('div') - headers['units'].append(div.text) - headers['captions'].append(div.previous_sibling.strip()) + footer = soup.find("tfoot") + for cell in footer.findAll("td", {"class": "nutrient-column"}): + div = cell.find("div") + headers["units"].append(div.text) + headers["captions"].append(div.previous_sibling.strip()) return headers def get_values(soup, row_class): """get values from a specific summary row based on the row class""" - locale.setlocale(locale.LC_ALL, 'en_US.UTF-8') # for number parsing + locale.setlocale(locale.LC_ALL, "en_US.UTF-8") # for number parsing values = [] - cells = soup.find('tr', {'class': row_class}).find_all('td') + cells = soup.find("tr", {"class": row_class}).find_all("td") for elem in cells[1:]: # if there's a child span with class "macro-value", use its value # otherwise use the cell text - span = elem.find('span', {'class': 'macro-value'}) + span = elem.find("span", {"class": "macro-value"}) if span: value = span.text else: value = elem.text - if value.strip() != '': + if value.strip() != "": values.append(locale.atoi(value)) return values diff --git a/plugins/name_generator.py b/plugins/name_generator.py index 0ba3b789..b38338eb 100644 --- a/plugins/name_generator.py +++ b/plugins/name_generator.py @@ -20,8 +20,11 @@ def get_generator(_json): data = json.loads(_json) - return textgen.TextGenerator(data["templates"], - data["parts"], default_templates=data["default_templates"]) + return textgen.TextGenerator( + data["templates"], + data["parts"], + default_templates=data["default_templates"], + ) @hook.command(autohelp=False) @@ -37,13 +40,17 @@ def namegen(text, bot, notice): # get a list of available name generators files = os.listdir(os.path.join(bot.data_dir, "name_files")) - all_modules = [os.path.splitext(i)[0] for i in files if os.path.splitext(i)[1] == ".json"] + all_modules = [ + os.path.splitext(i)[0] + for i in files + if os.path.splitext(i)[1] == ".json" + ] all_modules.sort() # command to return a list of all available generators if inp == "list": message = "Available generators: " - message += formatting.get_text_list(all_modules, 'and') + message += formatting.get_text_list(all_modules, "and") notice(message) return @@ -58,7 +65,9 @@ def namegen(text, bot, notice): return "{} is not a valid name generator.".format(inp) # load the name generator - path = os.path.join(bot.data_dir, "name_files", "{}.json".format(selected_module)) + path = os.path.join( + bot.data_dir, "name_files", "{}.json".format(selected_module) + ) with codecs.open(path, encoding="utf-8") as f: try: @@ -70,4 +79,6 @@ def namegen(text, bot, notice): name_list = generator.generate_strings(10) # and finally return the final message :D - return "Some names to ponder: {}.".format(formatting.get_text_list(name_list, 'and')) + return "Some names to ponder: {}.".format( + formatting.get_text_list(name_list, "and") + ) diff --git a/plugins/notes.py b/plugins/notes.py index 31ea9c2d..6f520d86 100644 --- a/plugins/notes.py +++ b/plugins/notes.py @@ -1,71 +1,91 @@ from datetime import datetime import sqlalchemy -from sqlalchemy import Table, Column, String, Boolean, Integer, DateTime, PrimaryKeyConstraint, not_ +from sqlalchemy import ( + Boolean, + Column, + DateTime, + Integer, + PrimaryKeyConstraint, + String, + Table, + not_, +) from sqlalchemy.sql import select from cloudbot import hook from cloudbot.util import database table = Table( - 'notes', + "notes", database.metadata, - Column('note_id', Integer), - Column('connection', String), - Column('user', String), - Column('text', String), - Column('priority', Integer), - Column('deleted', Boolean), - Column('added', DateTime), - PrimaryKeyConstraint('note_id', 'connection', 'user') + Column("note_id", Integer), + Column("connection", String), + Column("user", String), + Column("text", String), + Column("priority", Integer), + Column("deleted", Boolean), + Column("added", DateTime), + PrimaryKeyConstraint("note_id", "connection", "user"), ) def read_all_notes(db, server, user, show_deleted=False): if show_deleted: - query = select([table.c.note_id, table.c.text, table.c.added]) \ - .where(table.c.connection == server) \ - .where(table.c.user == user.lower()) \ + query = ( + select([table.c.note_id, table.c.text, table.c.added]) + .where(table.c.connection == server) + .where(table.c.user == user.lower()) .order_by(table.c.added) + ) else: - query = select([table.c.note_id, table.c.text, table.c.added]) \ - .where(table.c.connection == server) \ - .where(table.c.user == user.lower()) \ - .where(not_(table.c.deleted)) \ + query = ( + select([table.c.note_id, table.c.text, table.c.added]) + .where(table.c.connection == server) + .where(table.c.user == user.lower()) + .where(not_(table.c.deleted)) .order_by(table.c.added) + ) return db.execute(query).fetchall() def delete_all_notes(db, server, user): - query = table.update() \ - .where(table.c.connection == server) \ - .where(table.c.user == user.lower()) \ + query = ( + table.update() + .where(table.c.connection == server) + .where(table.c.user == user.lower()) .values(deleted=True) + ) db.execute(query) db.commit() def read_note(db, server, user, note_id): - query = select([table.c.note_id, table.c.text, table.c.added]) \ - .where(table.c.connection == server) \ - .where(table.c.user == user.lower()) \ + query = ( + select([table.c.note_id, table.c.text, table.c.added]) + .where(table.c.connection == server) + .where(table.c.user == user.lower()) .where(table.c.note_id == note_id) + ) return db.execute(query).fetchone() def delete_note(db, server, user, note_id): - query = table.update() \ - .where(table.c.connection == server) \ - .where(table.c.user == user.lower()) \ - .where(table.c.note_id == note_id) \ + query = ( + table.update() + .where(table.c.connection == server) + .where(table.c.user == user.lower()) + .where(table.c.note_id == note_id) .values(deleted=True) + ) db.execute(query) db.commit() def add_note(db, server, user, text): - id_query = select([sqlalchemy.sql.expression.func.max(table.c.note_id).label("maxid")]) \ - .where(table.c.user == user.lower()) + id_query = select( + [sqlalchemy.sql.expression.func.max(table.c.note_id).label("maxid")] + ).where(table.c.user == user.lower()) max_id = db.execute(id_query).scalar() if max_id is None: @@ -79,7 +99,7 @@ def add_note(db, server, user, text): user=user.lower(), text=text, deleted=False, - added=datetime.today() + added=datetime.today(), ) db.execute(query) db.commit() @@ -89,9 +109,11 @@ def format_note(data): note_id, note_text, added = data # format timestamp - added_string = added.strftime('%d %b, %Y') + added_string = added.strftime("%d %b, %Y") - return "\x02Note #{}:\x02 {} - \x02{}\x02".format(note_id, note_text, added_string) + return "\x02Note #{}:\x02 {} - \x02{}\x02".format( + note_id, note_text, added_string + ) @hook.command("note", "notes", "todo") @@ -106,7 +128,7 @@ def note(text, conn, nick, db, notice): cmd = parts[0].lower() args = parts[1:] - if cmd in ['add', 'new']: + if cmd in ["add", "new"]: # user is adding a note if not args: return "No text provided!" @@ -119,7 +141,7 @@ def note(text, conn, nick, db, notice): notice("Note added!") return - if cmd in ['del', 'delete', 'remove']: + if cmd in ["del", "delete", "remove"]: # user is deleting a note if not args: return "No note ID provided!" @@ -138,14 +160,14 @@ def note(text, conn, nick, db, notice): notice("Note #{} deleted!".format(note_id)) return - if cmd == 'clear': + if cmd == "clear": # user is deleting all notes delete_all_notes(db, conn.name, nick) notice("All notes deleted!") return - if cmd == 'get': + if cmd == "get": # user is getting a single note if not args: return "No note ID provided!" @@ -162,7 +184,7 @@ def note(text, conn, nick, db, notice): notice(text) return - if cmd in ['share', 'show']: + if cmd in ["share", "show"]: # user is sharing a single note if not args: return "No note ID provided!" @@ -178,7 +200,7 @@ def note(text, conn, nick, db, notice): text = format_note(n) return text - if cmd == 'list': + if cmd == "list": # user is getting all notes notes = read_all_notes(db, conn.name, nick) @@ -192,7 +214,7 @@ def note(text, conn, nick, db, notice): # show the note text = format_note(n) notice(text) - elif cmd == 'listall': + elif cmd == "listall": # user is getting all notes including deleted ones notes = read_all_notes(db, conn.name, nick, show_deleted=True) diff --git a/plugins/octopart.py b/plugins/octopart.py index 66f080e2..4774245c 100644 --- a/plugins/octopart.py +++ b/plugins/octopart.py @@ -28,30 +28,32 @@ def octopart(text, reply): if not api_key: return "Octopart API key required." - params = { - 'apikey': api_key, - 'q': text, - 'start': 0, - 'limit': 1 - } + params = {"apikey": api_key, "q": text, "start": 0, "limit": 1} try: request = requests.get(API_URL, params=params) request.raise_for_status() - except (requests.exceptions.HTTPError, requests.exceptions.ConnectionError) as e: + except ( + requests.exceptions.HTTPError, + requests.exceptions.ConnectionError, + ) as e: reply("Could not fetch part data: {}".format(e)) raise response = request.json() - if not response['results']: + if not response["results"]: return "No results." # get part - results = response['results'] + results = response["results"] for result in results: - part = result['item'] + part = result["item"] # print matched part - reply("{} - {} - {}".format(part['brand']['name'], part['mpn'], part['octopart_url'])) + reply( + "{} - {} - {}".format( + part["brand"]["name"], part["mpn"], part["octopart_url"] + ) + ) diff --git a/plugins/pagecheck.py b/plugins/pagecheck.py index 9f498523..135dfd95 100644 --- a/plugins/pagecheck.py +++ b/plugins/pagecheck.py @@ -15,17 +15,17 @@ def down(text): """ if "://" not in text: - text = 'http://' + text + text = "http://" + text - text = 'http://' + urllib.parse.urlparse(text).netloc + text = "http://" + urllib.parse.urlparse(text).netloc try: r = requests.get(text) r.raise_for_status() except requests.exceptions.ConnectionError: - return '{} seems to be down'.format(text) + return "{} seems to be down".format(text) else: - return '{} seems to be up'.format(text) + return "{} seems to be up".format(text) @hook.command() @@ -42,7 +42,7 @@ def isup(text): domain = auth or path try: - response = requests.get('http://isup.me/' + domain) + response = requests.get("http://isup.me/" + domain) response.raise_for_status() except requests.exceptions.ConnectionError: return "Failed to get status." @@ -51,10 +51,12 @@ def isup(text): soup = parse_soup(response.text) - content = soup.find('div', id="domain-main-content").text.strip() + content = soup.find("div", id="domain-main-content").text.strip() if "not just you" in content: - return "It's not just you. {} looks \x02\x034down\x02\x0f from here!".format(url) + return "It's not just you. {} looks \x02\x034down\x02\x0f from here!".format( + url + ) if "is up" in content: return "It's just you. {} is \x02\x033up\x02\x0f.".format(url) diff --git a/plugins/password.py b/plugins/password.py index 88803001..64c8a311 100644 --- a/plugins/password.py +++ b/plugins/password.py @@ -83,4 +83,8 @@ def word_password(text, notice): for _ in range(length): words.append(gen.choice(common_words)) - notice("Your password is '{}'. Feel free to remove the spaces when using it.".format(" ".join(words))) + notice( + "Your password is '{}'. Feel free to remove the spaces when using it.".format( + " ".join(words) + ) + ) diff --git a/plugins/pastebins/sprunge.py b/plugins/pastebins/sprunge.py index 4b54855b..2e9e46f3 100644 --- a/plugins/pastebins/sprunge.py +++ b/plugins/pastebins/sprunge.py @@ -2,7 +2,12 @@ from requests import HTTPError, RequestException from cloudbot import hook -from cloudbot.util.web import Pastebin, pastebins, ServiceHTTPError, ServiceError +from cloudbot.util.web import ( + Pastebin, + ServiceError, + ServiceHTTPError, + pastebins, +) class Sprunge(Pastebin): @@ -17,7 +22,7 @@ def paste(self, data, ext): encoded = data params = { - 'sprunge': encoded, + "sprunge": encoded, } try: @@ -31,16 +36,16 @@ def paste(self, data, ext): raise ServiceError(e.request, "Connection error occurred") from e if ext: - url += '?{}'.format(ext) + url += "?{}".format(ext) return url @hook.on_start() def register(): - pastebins.register('sprunge', Sprunge('http://sprunge.us')) + pastebins.register("sprunge", Sprunge("http://sprunge.us")) @hook.on_stop() def unregister(): - pastebins.remove('sprunge') + pastebins.remove("sprunge") diff --git a/plugins/penis.py b/plugins/penis.py index d166ad34..320ebd99 100644 --- a/plugins/penis.py +++ b/plugins/penis.py @@ -2,25 +2,51 @@ from cloudbot import hook -balls = ['(_)_)', '8', 'B', '(___)__)', '(_)(_)', '(@)@)', '3'] +balls = ["(_)_)", "8", "B", "(___)__)", "(_)(_)", "(@)@)", "3"] shaft = [ - '=', '==', '===', '====', '=====', '========', - '/////////////////////////', '|||||||||||||', - '\u2248\u2248\u2248' + "=", + "==", + "===", + "====", + "=====", + "========", + "/////////////////////////", + "|||||||||||||", + "\u2248\u2248\u2248", +] +head = ["D", "Q", ">", "|\u2283" "\u22d1", "\u22d9", "\u22d7"] +emission = ["~ ~ ~ ~", "~ * ~ &", "", "*~* *~* %"] +bodypart = [ + "face", + "glasses", + "thigh", + "tummy", + "back", + "hiney", + "hair", + "boobs", + "tongue", ] -head = ['D', 'Q', '>', '|\u2283' '\u22d1', '\u22d9', '\u22d7'] -emission = ['~ ~ ~ ~', '~ * ~ &', '', '*~* *~* %'] -bodypart = ['face', 'glasses', 'thigh', 'tummy', 'back', 'hiney', 'hair', 'boobs', 'tongue'] @hook.command("penis", "bepis", autohelp=False) def penis(text, message): """[nick] - much dongs, very ween, add a user nick as an arguement for slightly different 'output'""" if not text: - message("{}{}{}".format(random.choice(balls), random.choice(shaft), random.choice(head))) + message( + "{}{}{}".format( + random.choice(balls), random.choice(shaft), random.choice(head) + ) + ) else: - person = text.split(' ')[0] - message("{}{}{}{} all over {}'s {}".format( - random.choice(balls), random.choice(shaft), random.choice(head), - random.choice(emission), person, random.choice(bodypart) - )) + person = text.split(" ")[0] + message( + "{}{}{}{} all over {}'s {}".format( + random.choice(balls), + random.choice(shaft), + random.choice(head), + random.choice(emission), + person, + random.choice(bodypart), + ) + ) diff --git a/plugins/piglatin.py b/plugins/piglatin.py index d704759c..9c2f378d 100644 --- a/plugins/piglatin.py +++ b/plugins/piglatin.py @@ -15,18 +15,20 @@ """ import string +from typing import Dict, List import nltk from cloudbot import hook -pronunciations = {} +pronunciations: Dict[str, List[List[str]]] = {} # Translate functions by J.F. Sebastian # -def translate(word): + +def translate(word: str): word = word.lower() # NOTE: ignore Unicode casefold i = 0 # find out whether the word start with a vowel sound using @@ -63,7 +65,7 @@ def translate_basic(word, vowels="aeiou", start=0): @hook.on_start() def load_nltk(): - nltk.download('cmudict') + nltk.download("cmudict") pronunciations.clear() pronunciations.update(nltk.corpus.cmudict.dict()) diff --git a/plugins/ping.py b/plugins/ping.py index 3bb37c92..33595a83 100644 --- a/plugins/ping.py +++ b/plugins/ping.py @@ -20,14 +20,16 @@ from cloudbot import hook unix_ping_regex = re.compile(r"(\d+.\d+)/(\d+.\d+)/(\d+.\d+)/(\d+.\d+)") -win_ping_regex = re.compile(r"Minimum = (\d+)ms, Maximum = (\d+)ms, Average = (\d+)ms") +win_ping_regex = re.compile( + r"Minimum = (\d+)ms, Maximum = (\d+)ms, Average = (\d+)ms" +) @hook.command() def ping(text, reply): """ [count] - pings [count] times""" - args = text.split(' ') + args = text.split(" ") host = args[0] # check for a second argument and set the ping count @@ -57,9 +59,19 @@ def ping(text, reply): if os.name == "nt": m = re.search(win_ping_regex, pingcmd) r = int(m.group(2)) - int(m.group(1)) - return "min: %sms, max: %sms, average: %sms, range: %sms, count: %s" \ - % (m.group(1), m.group(2), m.group(3), r, count) + return "min: %sms, max: %sms, average: %sms, range: %sms, count: %s" % ( + m.group(1), + m.group(2), + m.group(3), + r, + count, + ) m = re.search(unix_ping_regex, pingcmd) - return "min: %sms, max: %sms, average: %sms, range: %sms, count: %s" \ - % (m.group(1), m.group(3), m.group(2), m.group(4), count) + return "min: %sms, max: %sms, average: %sms, range: %sms, count: %s" % ( + m.group(1), + m.group(3), + m.group(2), + m.group(4), + count, + ) diff --git a/plugins/plpaste.py b/plugins/plpaste.py index 4fd44f72..eae66400 100644 --- a/plugins/plpaste.py +++ b/plugins/plpaste.py @@ -14,9 +14,9 @@ def plpaste(text, bot): if text in bot.plugin_manager.commands: file_path = bot.plugin_manager.commands[text].plugin.file_path with open(file_path) as f: - return web.paste(f.read(), ext='py') - elif text + ".py" in listdir('plugins/'): - with open('plugins/{}.py'.format(text)) as f: - return web.paste(f.read(), ext='py') + return web.paste(f.read(), ext="py") + elif text + ".py" in listdir("plugins/"): + with open("plugins/{}.py".format(text)) as f: + return web.paste(f.read(), ext="py") else: return "Could not find specified plugin." diff --git a/plugins/poll.py b/plugins/poll.py index c31def47..50e6c3eb 100644 --- a/plugins/poll.py +++ b/plugins/poll.py @@ -1,9 +1,10 @@ from re import findall +from typing import Dict from cloudbot import hook from cloudbot.util.formatting import get_text_list -polls = {} +polls: Dict[str, "Poll"] = {} class PollError(Exception): @@ -73,7 +74,11 @@ def poll(text, conn, nick, chan, message, reply): return "You have no active poll to close." p = polls.get(uid) - reply("Your poll has been closed. Final results for \x02\"{}\"\x02:".format(p.question)) + reply( + 'Your poll has been closed. Final results for \x02"{}"\x02:'.format( + p.question + ) + ) message(p.format_results()) del polls[uid] return @@ -81,11 +86,11 @@ def poll(text, conn, nick, chan, message, reply): if uid in polls.keys(): return "You already have an active poll in this channel, you must close it before you can create a new one." - if ':' in text: - question, options = text.strip().split(':') - c = findall(r'([^,]+)', options) + if ":" in text: + question, options = text.strip().split(":") + c = findall(r"([^,]+)", options) if len(c) == 1: - c = findall(r'(\S+)', options) + c = findall(r"(\S+)", options) options = list(set(x.strip() for x in c)) _poll = Poll(question, nick, options) else: @@ -95,19 +100,27 @@ def poll(text, conn, nick, chan, message, reply): # store poll in list polls[uid] = _poll - option_str = get_text_list([option.title for option in _poll.options.values()], "and") - message('Created poll \x02\"{}\"\x02 with the following options: {}'.format(_poll.question, option_str)) + option_str = get_text_list( + [option.title for option in _poll.options.values()], "and" + ) + message( + 'Created poll \x02"{}"\x02 with the following options: {}'.format( + _poll.question, option_str + ) + ) message("Use .vote {}