@@ -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( | ||||