| @@ -61,8 +61,6 @@ build-backend = "setuptools.build_meta" | |||||
| [tool.pyright] | [tool.pyright] | ||||
| exclude = [ | exclude = [ | ||||
| # TODO | # TODO | ||||
| "src/earwigbot/commands", | |||||
| "src/earwigbot/tasks", | |||||
| "src/earwigbot/wiki/copyvios" | "src/earwigbot/wiki/copyvios" | ||||
| ] | ] | ||||
| pythonVersion = "3.11" | pythonVersion = "3.11" | ||||
| @@ -62,8 +62,7 @@ class CIDR(Command): | |||||
| try: | try: | ||||
| ips = [self._parse_ip(arg) for arg in data.args] | ips = [self._parse_ip(arg) for arg in data.args] | ||||
| except ValueError as exc: | except ValueError as exc: | ||||
| msg = "Can't parse IP address \x0302{0}\x0f." | |||||
| self.reply(data, msg.format(exc.message)) | |||||
| self.reply(data, f"Can't parse IP address: \x0302{exc}\x0f") | |||||
| return | return | ||||
| if any(ip.family == AF_INET for ip in ips) and any( | if any(ip.family == AF_INET for ip in ips) and any( | ||||
| @@ -112,8 +111,9 @@ class CIDR(Command): | |||||
| return _IP(AF_INET6, socket.inet_pton(AF_INET6, arg), size) | return _IP(AF_INET6, socket.inet_pton(AF_INET6, arg), size) | ||||
| except OSError: | except OSError: | ||||
| raise ValueError(oldarg) | raise ValueError(oldarg) | ||||
| if size > 32: | |||||
| raise ValueError(oldarg) | |||||
| else: | |||||
| if size and size > 32: | |||||
| raise ValueError(oldarg) | |||||
| return ip | return ip | ||||
| def _parse_arg(self, arg): | def _parse_arg(self, arg): | ||||
| @@ -180,7 +180,10 @@ class CIDR(Command): | |||||
| """Convert an IP's binary representation to presentation format.""" | """Convert an IP's binary representation to presentation format.""" | ||||
| return socket.inet_ntop( | return socket.inet_ntop( | ||||
| family, | family, | ||||
| "".join(chr(int(binary[i : i + 8], 2)) for i in range(0, len(binary), 8)), | |||||
| b"".join( | |||||
| chr(int(binary[i : i + 8], 2)).encode() | |||||
| for i in range(0, len(binary), 8) | |||||
| ), | |||||
| ) | ) | ||||
| @staticmethod | @staticmethod | ||||
| @@ -76,6 +76,7 @@ class Crypt(Command): | |||||
| data, | data, | ||||
| "This command requires the 'cryptography' package: https://cryptography.io/", | "This command requires the 'cryptography' package: https://cryptography.io/", | ||||
| ) | ) | ||||
| return | |||||
| try: | try: | ||||
| if data.command == "encrypt": | if data.command == "encrypt": | ||||
| @@ -163,7 +163,7 @@ class Notes(Command): | |||||
| except IndexError: | except IndexError: | ||||
| self.reply(data, "Please specify an entry to edit.") | self.reply(data, "Please specify an entry to edit.") | ||||
| return | return | ||||
| content = " ".join(data.args[2:]).strip().decode("utf8") | |||||
| content = " ".join(data.args[2:]).strip() | |||||
| if not content: | if not content: | ||||
| self.reply(data, "Please give some content to put in the entry.") | self.reply(data, "Please give some content to put in the entry.") | ||||
| return | return | ||||
| @@ -22,8 +22,10 @@ import ast | |||||
| import operator | import operator | ||||
| import random | import random | ||||
| import time | import time | ||||
| from collections.abc import Callable | |||||
| from itertools import chain | from itertools import chain | ||||
| from threading import RLock, Thread | from threading import RLock, Thread | ||||
| from typing import Any | |||||
| from earwigbot.commands import Command | from earwigbot.commands import Command | ||||
| from earwigbot.irc import Data | from earwigbot.irc import Data | ||||
| @@ -69,11 +71,12 @@ class Remind(Command): | |||||
| return "snooze" | return "snooze" | ||||
| if command in SNOOZE: # "adjust" == snoozing active reminders | if command in SNOOZE: # "adjust" == snoozing active reminders | ||||
| return "adjust" | return "adjust" | ||||
| raise ValueError(command) | |||||
| @staticmethod | @staticmethod | ||||
| def _parse_time(arg): | def _parse_time(arg): | ||||
| """Parse the wait time for a reminder.""" | """Parse the wait time for a reminder.""" | ||||
| ast_to_op = { | |||||
| ast_to_op: dict[type[ast.operator], Callable[[Any, Any], Any]] = { | |||||
| ast.Add: operator.add, | ast.Add: operator.add, | ||||
| ast.Sub: operator.sub, | ast.Sub: operator.sub, | ||||
| ast.Mult: operator.mul, | ast.Mult: operator.mul, | ||||
| @@ -126,6 +126,8 @@ class Stalk(Command): | |||||
| return | return | ||||
| else: | else: | ||||
| stalkinfo = (data.nick, data.chan, modifiers) | stalkinfo = (data.nick, data.chan, modifiers) | ||||
| else: | |||||
| stalkinfo = None | |||||
| if data.command == "stalk": | if data.command == "stalk": | ||||
| self._add_stalk("user", data, target, stalkinfo) | self._add_stalk("user", data, target, stalkinfo) | ||||
| @@ -330,10 +332,8 @@ class Stalk(Command): | |||||
| users = self._get_stalks_by_nick(nick, self._users) | users = self._get_stalks_by_nick(nick, self._users) | ||||
| pages = self._get_stalks_by_nick(nick, self._pages) | pages = self._get_stalks_by_nick(nick, self._pages) | ||||
| if users: | |||||
| uinfo = f" Users: {_format_stalks(users)}." | |||||
| if pages: | |||||
| pinfo = f" Pages: {_format_stalks(pages)}." | |||||
| uinfo = f" Users: {_format_stalks(users)}." if users else None | |||||
| pinfo = f" Pages: {_format_stalks(pages)}." if pages else None | |||||
| msg = "Currently stalking {0} user{1} and watching {2} page{3} for you.{4}{5}" | msg = "Currently stalking {0} user{1} and watching {2} page{3} for you.{4}{5}" | ||||
| return msg.format( | return msg.format( | ||||
| @@ -368,10 +368,8 @@ class Stalk(Command): | |||||
| ) | ) | ||||
| users, pages = self._users, self._pages | users, pages = self._users, self._pages | ||||
| if users: | |||||
| uinfo = f" Users: {_format_stalks(users)}." | |||||
| if pages: | |||||
| pinfo = f" Pages: {_format_stalks(pages)}." | |||||
| uinfo = f" Users: {_format_stalks(users)}." if users else None | |||||
| pinfo = f" Pages: {_format_stalks(pages)}." if pages else None | |||||
| msg = "Currently stalking {0} user{1} and watching {2} page{3}.{4}{5}" | msg = "Currently stalking {0} user{1} and watching {2} page{3}.{4}{5}" | ||||
| return msg.format( | return msg.format( | ||||
| @@ -69,7 +69,7 @@ class Threads(Command): | |||||
| for thread in threads: | for thread in threads: | ||||
| tname = thread.name | tname = thread.name | ||||
| ident = thread.ident % 10000 | |||||
| ident = thread.ident % 10000 if thread.ident else "unknown" | |||||
| if tname == "MainThread": | if tname == "MainThread": | ||||
| t = "\x0302main\x0f (id {0})" | t = "\x0302main\x0f (id {0})" | ||||
| normal_threads.append(t.format(ident)) | normal_threads.append(t.format(ident)) | ||||
| @@ -40,7 +40,7 @@ class Trout(Command): | |||||
| target = " ".join(data.args) or data.nick | target = " ".join(data.args) or data.nick | ||||
| target = "himself" if target == "yourself" else target | target = "himself" if target == "yourself" else target | ||||
| normal = normalize("NFKD", target.decode("utf8")).lower() | |||||
| normal = normalize("NFKD", target).lower() | |||||
| if normal in self.exceptions: | if normal in self.exceptions: | ||||
| self.reply(data, self.exceptions[normal]) | self.reply(data, self.exceptions[normal]) | ||||
| else: | else: | ||||
| @@ -1,4 +1,4 @@ | |||||
| # Copyright (C) 2009-2015 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
| # Copyright (C) 2009-2024 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
| # | # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to deal | # of this software and associated documentation files (the "Software"), to deal | ||||
| @@ -18,6 +18,8 @@ | |||||
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
| # SOFTWARE. | # SOFTWARE. | ||||
| from typing import Any | |||||
| from earwigbot import exceptions | from earwigbot import exceptions | ||||
| __all__ = ["Task"] | __all__ = ["Task"] | ||||
| @@ -76,7 +78,7 @@ class Task: | |||||
| """ | """ | ||||
| pass | pass | ||||
| def run(self, **kwargs): | |||||
| def run(self, **kwargs: Any) -> None: | |||||
| """Main entry point to run a given task. | """Main entry point to run a given task. | ||||
| This is called directly by :py:meth:`tasks.start() | This is called directly by :py:meth:`tasks.start() | ||||
| @@ -177,7 +177,9 @@ class WikiProjectTagger(Task): | |||||
| except IndexError: | except IndexError: | ||||
| return text | return text | ||||
| def run(self, **kwargs: Unpack[JobKwargs]) -> None: | |||||
| def run( # pyright: ignore[reportIncompatibleMethodOverride] | |||||
| self, **kwargs: Unpack[JobKwargs] | |||||
| ) -> None: | |||||
| """ | """ | ||||
| Main entry point for the bot task. | Main entry point for the bot task. | ||||
| """ | """ | ||||
| @@ -364,6 +366,7 @@ class WikiProjectTagger(Task): | |||||
| self.logger.error(f"Skipping invalid page: [[{page.title}]]") | self.logger.error(f"Skipping invalid page: [[{page.title}]]") | ||||
| return | return | ||||
| banner = None | |||||
| is_update = False | is_update = False | ||||
| for template in code.ifilter_templates(recursive=True): | for template in code.ifilter_templates(recursive=True): | ||||
| if template.name.matches(job.names): | if template.name.matches(job.names): | ||||
| @@ -389,6 +392,7 @@ class WikiProjectTagger(Task): | |||||
| return | return | ||||
| if is_update: | if is_update: | ||||
| assert banner is not None | |||||
| updated = self.update_banner(banner, job, code) | updated = self.update_banner(banner, job, code) | ||||
| if not updated: | if not updated: | ||||
| self.logger.info( | self.logger.info( | ||||