diff --git a/constants/_meta.py b/constants/_meta.py index 87de858..2f9f67c 100644 --- a/constants/_meta.py +++ b/constants/_meta.py @@ -51,10 +51,12 @@ def __new__(mcs, name: str, bases: tuple[type, ...], attrs: dict[str, Any]) -> t return super().__new__(mcs, name, bases, attrs) def __setattr__(self, attr: str, nv: Any) -> NoReturn: - raise RuntimeError(f"Constant <{attr}> cannot be assigned to.") + msg = f"Constant <{attr}> cannot be assigned to." + raise RuntimeError(msg) def __delattr__(self, attr: str) -> NoReturn: - raise RuntimeError(f"Constant <{attr}> cannot be deleted.") + msg = f"Constant <{attr}> cannot be deleted." + raise RuntimeError(msg) class CONSTANTS(metaclass=ConstantsMeta): diff --git a/constants/constants.py b/constants/constants.py index e4b3645..0ea594a 100644 --- a/constants/constants.py +++ b/constants/constants.py @@ -24,10 +24,10 @@ from ._meta import CONSTANTS __all__ = ( - "Roles", - "Colours", "Channels", + "Colours", "ForumTags", + "Roles", ) diff --git a/core/bot.py b/core/bot.py index 71480d3..53bfb26 100644 --- a/core/bot.py +++ b/core/bot.py @@ -47,7 +47,7 @@ import aiohttp import asyncpg import mystbin - from discord.ext.commands.cog import Cog # type: ignore # stubs + from discord.ext.commands.cog import Cog # pyright: ignore[reportMissingTypeStubs] # stubs from .utils import LogHandler @@ -60,12 +60,12 @@ class Bot(commands.Bot): logging_queue: Queue[LogRecord] __slots__ = ( - "session", - "pool", + "_previous_websocket_events", "log_handler", - "mb_client", "logging_queue", - "_previous_websocket_events", + "mb_client", + "pool", + "session", ) def __init__(self) -> None: @@ -77,11 +77,15 @@ def __init__(self) -> None: self._previous_websocket_events: deque[Any] = deque(maxlen=10) async def get_context( - self, message: discord.Message | discord.Interaction, /, *, cls: type[commands.Context[commands.Bot]] | None = None + self, + message: discord.Message | discord.Interaction, + /, + *, + cls: type[commands.Context[commands.Bot]] | None = None, ) -> Context: return await super().get_context(message, cls=Context) - async def add_cog(self, cog: Cog, /, *, override: bool = False) -> None: # type: ignore + async def add_cog(self, cog: Cog, /, *, override: bool = False) -> None: # pyright: ignore[reportIncompatibleMethodOverride] # weird narrowing on Context generic # we patch this since we're a single guild bot. # it allows for guild syncing only. return await super().add_cog(cog, override=override, guild=discord.Object(id=GUILD_ID)) @@ -111,13 +115,12 @@ async def on_error(self, event_name: str, /, *args: Any, **kwargs: Any) -> None: args_str = ["```py"] for index, arg in enumerate(args): - args_str.append(f"[{index}]: {arg!r}") - args_str.append("```") + args_str.extend((f"[{index}]: {arg!r}", "```")) embed.add_field(name="Args", value="\n".join(args_str), inline=False) self.log_handler.error("Event Error", extra={"embed": embed}) - async def on_command_error(self, ctx: Context, error: commands.CommandError) -> None: # type: ignore # weird narrowing + async def on_command_error(self, ctx: Context, error: commands.CommandError) -> None: # pyright: ignore[reportIncompatibleMethodOverride] # weird narrowing on Context generic assert ctx.command # wouldn't be here otherwise if not isinstance(error, (commands.CommandInvokeError, commands.ConversionError)): @@ -158,7 +161,7 @@ async def get_or_fetch_user( try: user = await guild.fetch_member(target_id) except discord.HTTPException: - return + return None if cache: cache[target_id] = user @@ -169,7 +172,7 @@ async def get_or_fetch_user( try: user = await self.fetch_user(target_id) except discord.HTTPException: - return + return None if cache: cache[target_id] = user @@ -182,11 +185,11 @@ async def start(self, token: str, *, reconnect: bool = True) -> None: finally: path = pathlib.Path("logs/prev_events.log") - with path.open("w+", encoding="utf-8") as f: + with path.open("w+", encoding="utf-8") as f: # noqa: ASYNC230 # this is okay as we're in cleanup phase for event in self._previous_websocket_events: try: last_log = json.dumps(event, ensure_ascii=True, indent=2) - except Exception: + except (ValueError, TypeError): f.write(f"{event}\n") else: f.write(f"{last_log}\n") diff --git a/core/checks.py b/core/checks.py index af7f5a8..4d3192b 100644 --- a/core/checks.py +++ b/core/checks.py @@ -30,7 +30,7 @@ import constants if TYPE_CHECKING: - from discord.ext.commands._types import Check # type: ignore # why would this need stubs + from discord.ext.commands._types import Check # pyright: ignore[reportMissingTypeStubs] # why would this need stubs from core.context import GuildContext @@ -42,7 +42,8 @@ def predicate(ctx: GuildContext) -> bool: # This should never be a problem, but just in case... if not role: # TODO: Change this to a custom exception. - raise commands.CheckFailure(f"Role with ID <{role_id}> does not exist.") + msg = f"Role with ID <{role_id}> does not exist." + raise commands.CheckFailure(msg) ignored = (constants.Roles.NITRO_BOOSTER, constants.Roles.MUTED) roles = [r for r in ctx.author.roles if r.id not in ignored and ctx.author.top_role >= r] @@ -51,6 +52,7 @@ def predicate(ctx: GuildContext) -> bool: return True # TODO: Change this to a custom exception. - raise commands.CheckFailure(f"{ctx.author} is not in or higher than role <{role.name}(ID: {role.id})>.") + msg = f"{ctx.author} is not in or higher than role <{role.name}(ID: {role.id})>." + raise commands.CheckFailure(msg) return commands.check(predicate) diff --git a/core/context.py b/core/context.py index 2e8f6da..b683acd 100644 --- a/core/context.py +++ b/core/context.py @@ -30,7 +30,7 @@ def replied_reference(self) -> discord.MessageReference | discord.Message: return self.message @discord.utils.cached_property - def replied_message(self) -> discord.Message | discord.Message: + def replied_message(self) -> discord.Message: ref = self.message.reference if ref and isinstance(ref.resolved, discord.Message): return ref.resolved @@ -53,12 +53,12 @@ def author_is_mod(self) -> bool: return False else: - member = self.author # type: ignore + member = self.author # pyright: ignore[reportAssignmentType] # type lie for a shortcut - roles = member._roles # type: ignore # we know this won't change for a while + roles = member._roles # pyright: ignore[reportPrivateUsage] # we know this won't change for a while return roles.has(Roles.ADMIN) or roles.has(Roles.MODERATOR) class GuildContext(Context): - guild: discord.Guild # type: ignore # type lie due to narrowing - author: discord.Member # type: ignore # type lie due to narrowing + guild: discord.Guild # pyright: ignore[reportIncompatibleVariableOverride] # type lie due to narrowing + author: discord.Member # pyright: ignore[reportIncompatibleVariableOverride] # type lie due to narrowing diff --git a/core/converters.py b/core/converters.py index 532443d..bb4727f 100644 --- a/core/converters.py +++ b/core/converters.py @@ -43,7 +43,7 @@ class CodeblockConverter(commands.Converter[Codeblock]): :attr:`Codeblock.language` will be ``None`` if the input was not a complete codeblock. """ - async def convert(self, ctx: Context, argument: str) -> Codeblock: # type: ignore # this is something to do with the typevar + async def convert(self, ctx: Context, argument: str) -> Codeblock: # pyright: ignore[reportIncompatibleMethodOverride] # generic narrowing on Context if not argument.startswith("`"): return Codeblock(None, argument) @@ -58,12 +58,8 @@ async def convert(self, ctx: Context, argument: str) -> Codeblock: # type: igno for character in argument: if character == "`" and not within_code and not within_language: backticks += 1 - if ( - previous_characters - and previous_characters[-1] == "`" - and character != "`" - or within_code - and "".join(previous_characters) != "`" * backticks + if (previous_characters and previous_characters[-1] == "`" and character != "`") or ( + within_code and "".join(previous_characters) != "`" * backticks ): within_code = True code.append(character) diff --git a/core/core.py b/core/core.py index 372f365..18050ee 100644 --- a/core/core.py +++ b/core/core.py @@ -38,7 +38,7 @@ ) -CONFIG: Config = toml.load("config.toml") # type: ignore # weird non-assertion +CONFIG: Config = toml.load("config.toml") # pyright: ignore[reportAssignmentType] # weirdly narrow library type class Cog(commands.Cog): diff --git a/core/utils/formatters.py b/core/utils/formatters.py index 4225ccf..bad9116 100644 --- a/core/utils/formatters.py +++ b/core/utils/formatters.py @@ -27,14 +27,15 @@ from discord.utils import escape_markdown __all__ = ( - "to_codeblock", "random_pastel_colour", + "to_codeblock", ) def to_codeblock( content: str, language: str = "py", + *, replace_existing: bool = True, escape_md: bool = True, new: str = "'''", @@ -50,4 +51,4 @@ def to_codeblock( def random_pastel_colour() -> Colour: - return Colour.from_hsv(random.random(), 0.28, 0.97) + return Colour.from_hsv(random.random(), 0.28, 0.97) # noqa: S311 # not for crypto usage diff --git a/core/utils/logging.py b/core/utils/logging.py index ca5513f..f3598ff 100644 --- a/core/utils/logging.py +++ b/core/utils/logging.py @@ -1,11 +1,14 @@ -from __future__ import annotations +from __future__ import annotations # noqa: A005 # we access this as a namespace import logging import pathlib from logging.handlers import RotatingFileHandler -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING -from discord.utils import _ColourFormatter as ColourFormatter, stream_supports_colour # type: ignore # shh, I need it +from discord.utils import ( + _ColourFormatter as ColourFormatter, # pyright: ignore[reportPrivateUsage] # shh, I need it # noqa: PLC2701 + stream_supports_colour, +) import core @@ -70,10 +73,10 @@ def __enter__(self: Self) -> Self: return self - async def __aexit__(self, *args: Any) -> None: + async def __aexit__(self, *args: object) -> None: return self.__exit__(*args) - def __exit__(self, *args: Any) -> None: + def __exit__(self, *args: object) -> None: handlers = self.log.handlers[:] for hdlr in handlers: hdlr.close() diff --git a/core/utils/paginator.py b/core/utils/paginator.py index 4d889b4..63f6cf7 100644 --- a/core/utils/paginator.py +++ b/core/utils/paginator.py @@ -28,7 +28,10 @@ import discord from discord import ui # shortcut because I'm lazy -from discord.ext.commands import CommandError, Paginator as _Paginator # type: ignore # why does this need a stub file? +from discord.ext.commands import ( # pyright: ignore[reportMissingTypeStubs] # why does this need a stub file? + CommandError, + Paginator as _Paginator, +) from discord.utils import MISSING if TYPE_CHECKING: @@ -39,7 +42,7 @@ from core import Bot, Context -__all__ = ("CannotPaginate", "Pager", "KVPager", "TextPager") +__all__ = ("CannotPaginate", "KVPager", "Pager", "TextPager") class CannotPaginate(CommandError): @@ -57,7 +60,7 @@ def __init__( per_page: int = 12, show_entry_count: bool = True, title: str | None = None, - embed_color: discord.Colour = discord.Colour.blurple(), + embed_color: discord.Colour | None = None, nocount: bool = False, delete_after: bool = True, author: discord.User | discord.Member | None = None, @@ -101,7 +104,7 @@ def __init__( ] if stop: - self.reaction_emojis.append(("\N{BLACK SQUARE FOR STOP}", self.stop_pages)) # type: ignore + self.reaction_emojis.append(("\N{BLACK SQUARE FOR STOP}", self.stop_pages)) if ctx.guild: self.permissions = self.channel.permissions_for(ctx.guild.me) @@ -119,7 +122,7 @@ def setup_buttons(self) -> None: self.clear_items() for emoji, button in self.reaction_emojis: btn = ui.Button["Self"](emoji=emoji) - btn.callback = button # type: ignore + btn.callback = button self.add_item(btn) def get_page(self, page: int) -> list[Any]: @@ -159,7 +162,11 @@ def prepare_embed(self, entries: list[Any], page: int, *, first: bool = False) - self.embed.title = self.title or MISSING async def show_page( - self, page: int, *, first: bool = False, msg_kwargs: dict[str, Any] | None = None + self, + page: int, + *, + first: bool = False, + msg_kwargs: dict[str, Any] | None = None, ) -> discord.Message | None: self.current_page = page entries = self.get_page(page) @@ -172,28 +179,30 @@ async def show_page( if not first: if self.message: await self.message.edit(content=content, embed=embed, view=self) - return + return None self.message = await self.channel.send(content=content, embed=embed, view=self) + return None + async def checked_show_page(self, page: int) -> None: if page != 0 and page <= self.maximum_pages: await self.show_page(page) - async def first_page(self, inter: discord.Interaction) -> None: - await inter.response.defer() + async def first_page(self, interaction: discord.Interaction) -> None: + await interaction.response.defer() await self.show_page(1) - async def last_page(self, inter: discord.Interaction) -> None: - await inter.response.defer() + async def last_page(self, interaction: discord.Interaction) -> None: + await interaction.response.defer() await self.show_page(self.maximum_pages) - async def next_page(self, inter: discord.Interaction) -> None: - await inter.response.defer() + async def next_page(self, interaction: discord.Interaction) -> None: + await interaction.response.defer() await self.checked_show_page(self.current_page + 1) - async def previous_page(self, inter: discord.Interaction) -> None: - await inter.response.defer() + async def previous_page(self, interaction: discord.Interaction) -> None: + await interaction.response.defer() await self.checked_show_page(self.current_page - 1) async def show_current_page(self, inter: discord.Interaction) -> None: @@ -223,7 +232,7 @@ def message_check(m: discord.Message) -> bool: await asyncio.sleep(5) try: - await self.channel.delete_messages(to_delete) # type: ignore # we handle the attribute error or http since exception handling is free + await self.channel.delete_messages(to_delete) # pyright: ignore[reportUnknownMemberType,reportAttributeAccessIssue] # we handle the attribute error or http since exception handling is free except (AttributeError, discord.HTTPException): pass @@ -234,7 +243,7 @@ async def stop_pages(self, interaction: discord.Interaction | None = None) -> No super().stop() - stop = stop_pages # type: ignore + stop = stop_pages # pyright: ignore[reportAssignmentType] # cheating to shortcut the stop method def _check(self, interaction: discord.Interaction) -> bool: return interaction.user.id == self.author.id @@ -272,7 +281,7 @@ def __init__( show_entry_count: bool = True, description: str | None = None, title: str | None = None, - embed_color: discord.Colour = discord.Colour.blurple(), + embed_color: discord.Colour | None = None, **kwargs: Any, ) -> None: super().__init__( @@ -281,7 +290,7 @@ def __init__( per_page=per_page, show_entry_count=show_entry_count, title=title, - embed_color=embed_color, + embed_color=embed_color or discord.Colour.blurple(), **kwargs, ) self.description = description @@ -324,9 +333,6 @@ def __init__( def get_page(self, page: int) -> Any: return self.entries[page - 1] - def get_embed(self, entries: list[str], page: int, *, first: bool = False) -> discord.Embed: - return None # type: ignore - def get_content(self, entry: str, page: int, *, first: bool = False) -> str: if self.maximum_pages > 1: return f"{entry}\nPage {page}/{self.maximum_pages}" diff --git a/launcher.py b/launcher.py index 6b8c789..8b50b52 100644 --- a/launcher.py +++ b/launcher.py @@ -76,4 +76,4 @@ async def main() -> None: try: asyncio.run(main()) except KeyboardInterrupt: - print("Fix this later, but you killed bot with KeyboardInterrupt...") + print("Fix this later, but you killed bot with KeyboardInterrupt...") # noqa: T201 # this is okay diff --git a/modules/__init__.py b/modules/__init__.py index 6ff2c10..19caf7b 100644 --- a/modules/__init__.py +++ b/modules/__init__.py @@ -9,7 +9,7 @@ module for module in iter_modules(__path__, prefix=__package__ + ".") if not module.name.rsplit(".")[-1].startswith("_") - ] + ], ) private_path = pathlib.Path(__package__ + "/private") @@ -19,7 +19,7 @@ module for module in iter_modules([str(private_path.absolute())], prefix=__package__ + ".private.") if not module.name.rsplit(".")[-1].startswith("_") - ] + ], ) diff --git a/modules/admin.py b/modules/admin.py index aca797f..e852a97 100644 --- a/modules/admin.py +++ b/modules/admin.py @@ -39,7 +39,7 @@ def __init__(self, bot: core.Bot, /) -> None: LOGGER.info("[Ownership] Setting new owners to:\n%s", ", ".join([str(o) for o in owners])) bot.loop.create_task(self.populate_owners(owners)) - async def cog_check(self, ctx: Context) -> bool: # type: ignore # maybecoro override woes + async def cog_check(self, ctx: Context) -> bool: # pyright: ignore[reportIncompatibleMethodOverride] # maybecoro override woes return await self.bot.is_owner(ctx.author) async def populate_owners(self, owner_ids: list[int]) -> None: diff --git a/modules/eval.py b/modules/eval.py index 0f915d6..ed2c37d 100644 --- a/modules/eval.py +++ b/modules/eval.py @@ -91,7 +91,10 @@ async def perform_eval(self, code: core.Codeblock) -> str: @commands.max_concurrency(1, per=commands.BucketType.user, wait=False) @commands.cooldown(rate=1, per=10.0, type=commands.BucketType.user) async def eval( - self, ctx: core.Context, *, code: core.Codeblock = commands.param(converter=core.CodeblockConverter) + self, + ctx: core.Context, + *, + code: core.Codeblock = commands.param(converter=core.CodeblockConverter), # noqa: B008 # this is how converters work ) -> None: """Evaluates your code in the form of a Discord message. @@ -108,7 +111,7 @@ async def eval( if len(output) > 1000: try: codeblock = await self.bot.mb_client.create_paste( - files=[mystbin.File(content=output, filename="eval.py")] + files=[mystbin.File(content=output, filename="eval.py")], ) except mystbin.APIException: await ctx.send("Your output was too long to provide in any sensible manner.") @@ -132,9 +135,10 @@ async def eval_error_handler(self, ctx: core.Context, error: commands.CommandErr # let's silently suppress these error, don't want to spam the reaction / message delete return - elif isinstance(error, core.InvalidEval): + if isinstance(error, core.InvalidEval): await ctx.send( - f"Hey! Your eval job failed with status code: {error.error_code}. Error message is below:-\n{error}.\nDon't worry, the team here know!" + f"Hey! Your eval job failed with status code: {error.error_code}. " + f"Error message is below:-\n{error}.\nDon't worry, the team here know!", ) LOGGER.error("Eval Cog raised an error during eval:\n%s", str(error)) diff --git a/modules/github.py b/modules/github.py index 545363f..c5e0fca 100644 --- a/modules/github.py +++ b/modules/github.py @@ -34,7 +34,7 @@ GITHUB_ISSUE_URL = "https://github.com/{}/issues/{}" LIB_ISSUE_REGEX = re.compile(r"(?P[a-z]+)?(?P#{2,})(?P[0-9]+)", flags=re.IGNORECASE) GITHUB_CODE_REGION_REGEX = re.compile( - r"https?://github\.com/(?P.*)/(?P.*)/blob/(?P[a-zA-Z0-9]+)/(?P.*)/(?P.*)(?:\#L)(?P[0-9]+)(?:-L)?(?P[0-9]+)?" + r"https?://github\.com/(?P.*)/(?P.*)/blob/(?P[a-zA-Z0-9]+)/(?P.*)/(?P.*)(?:\#L)(?P[0-9]+)(?:-L)?(?P[0-9]+)?", ) GITHUB_BASE_URL = "https://github.com/" @@ -68,19 +68,18 @@ def __init__(self, bot: core.Bot) -> None: self.highlight_timeout = 10 def _strip_content_path(self, url: str) -> str: - file_path = url[len(GITHUB_BASE_URL) :] - return file_path + return url[len(GITHUB_BASE_URL) :] async def format_highlight_block(self, url: str, line_adjustment: int = 10) -> dict[str, str | int] | None: match = GITHUB_CODE_REGION_REGEX.search(url) if not match: - return + return None try: highlighted_line = int(match["linestart"]) # separate the #L{n} highlight except IndexError: - return + return None file_path = self._strip_content_path(url) raw_url = GITHUB_RAW_CONTENT_URL + file_path.replace("blob/", "") # Convert it to a raw user content URL @@ -88,7 +87,7 @@ async def format_highlight_block(self, url: str, line_adjustment: int = 10) -> d code = "" async with self.bot.session.get(raw_url) as resp: if resp.status == 404: - return + return None code += await resp.text() @@ -163,7 +162,7 @@ async def format_highlight_block(self, url: str, line_adjustment: int = 10) -> d path = match["path"] file_path = f"{path}/{file}" - github_dict = { + return { "path": file_path, "min": (_min_boundary if _min_boundary > 0 else highlighted_line - 1) + 1, # Do not display negative numbers if <0 @@ -171,10 +170,9 @@ async def format_highlight_block(self, url: str, line_adjustment: int = 10) -> d "msg": msg, } - return github_dict - def _smart_guess_lib(self, msg: discord.Message) -> LibEnum | None: - # this is mostly the same as the function in manuals.py, however the enum is entirely different so the code isn't reusable. + # this is mostly the same as the function in manuals.py + # however the enum is entirely different so the code isn't reusable. assert msg.channel if msg.channel.id == constants.Channels.HELP_CHANNEL: @@ -185,16 +183,16 @@ def _smart_guess_lib(self, msg: discord.Message) -> LibEnum | None: if "twitchio-help" in tags: return LibEnum.twitchio - elif "wavelink-help" in tags: + if "wavelink-help" in tags: return LibEnum.wavelink - elif "discord.py-help" in tags: + if "discord.py-help" in tags: return LibEnum.discordpy return None if msg.channel.id == constants.Channels.WAVELINK_DEV: return LibEnum.wavelink - elif msg.channel.id == constants.Channels.TWITCHIO_DEV: + if msg.channel.id == constants.Channels.TWITCHIO_DEV: return LibEnum.twitchio return None @@ -231,9 +229,10 @@ async def on_message(self, message: discord.Message) -> None: _min = code_segment["min"] _max = code_segment["max"] code_fmt = code_segment["msg"] + assert isinstance(code_fmt, str) max_message_size = 2002 - segment_len = len(code_fmt) # type: ignore + segment_len = len(code_fmt) # is our msg too big for the embed? if segment_len > max_message_size: diff --git a/modules/help.py b/modules/help.py index 35537c7..6ce8425 100644 --- a/modules/help.py +++ b/modules/help.py @@ -32,7 +32,7 @@ from constants import Channels, ForumTags if TYPE_CHECKING: - from discord.ext.commands._types import Check # type: ignore # why does this need a stub? + from discord.ext.commands._types import Check # pyright: ignore[reportMissingTypeStubs] # why does this need a stub? from core.context import Context @@ -48,15 +48,15 @@ - what you've tried so far {{remarks}} -Once your issue has been solved type `{core.CONFIG['prefix']}solved` to close the thread. +Once your issue has been solved type `{core.CONFIG["prefix"]}solved` to close the thread. """.strip() # fmt: off -FORUM_BLURB_GENERIC = _FORUM_BLURB.format(remarks="") -FORUM_BLURB_PYTHON = _FORUM_BLURB.format(remarks="- your Python version") +FORUM_BLURB_GENERIC = _FORUM_BLURB.format(remarks="") +FORUM_BLURB_PYTHON = _FORUM_BLURB.format(remarks="- your Python version") FORUM_BLURB_TWITCHIO = _FORUM_BLURB.format(remarks="- your TwitchIO version") FORUM_BLURB_WAVELINK = _FORUM_BLURB.format(remarks="- your Wavelink version\n- your Discord.py version") -FORUM_BLURB_DPY = _FORUM_BLURB.format(remarks="- your Discord.py version") +FORUM_BLURB_DPY = _FORUM_BLURB.format(remarks="- your Discord.py version") # fmt: on @@ -88,7 +88,7 @@ def predicate(ctx: core.GuildContext) -> bool: if ctx.author == ctx.channel.owner: return True - raise NotThreadOwner() + raise NotThreadOwner return commands.check(predicate) @@ -104,7 +104,7 @@ async def forum_post_created(self, thread: discord.Thread) -> None: if thread.parent_id != Channels.HELP_FORUM: return - channel: discord.TextChannel = thread.guild.get_channel(Channels.FORUM_LOGS) # type: ignore + channel: discord.TextChannel | None = thread.guild.get_channel(Channels.FORUM_LOGS) # pyright: ignore[reportAssignmentType] # this is narrowed by the constant value if not channel: return diff --git a/modules/info.py b/modules/info.py index 8f877a3..23c7ee0 100644 --- a/modules/info.py +++ b/modules/info.py @@ -44,7 +44,7 @@ class Information(core.Cog): def __init__(self, bot: core.Bot) -> None: self.bot: core.Bot = bot - async def cog_command_error(self, ctx: core.Context, error: commands.CommandError) -> None: # type: ignore # bad lib types. + async def cog_command_error(self, ctx: core.Context, error: commands.CommandError) -> None: # pyright: ignore[reportIncompatibleMethodOverride] # generic Context is incorrect. error = getattr(error, "original", error) if isinstance(error, NoPermissions): @@ -58,20 +58,19 @@ def _embed_factory(self, entity: EntityT) -> discord.Embed: if isinstance(entity, discord.User): return self._user_info(entity, embed=embed) - elif isinstance(entity, discord.Member): - embed = self._user_info(entity, embed=embed) # type: ignore # superclass + if isinstance(entity, discord.Member): + embed = self._user_info(entity, embed=embed) return self._member_info(entity, embed=embed) - elif isinstance(entity, discord.Role): + if isinstance(entity, discord.Role): return self._role_info(entity, embed=embed) - elif isinstance(entity, discord.abc.GuildChannel): + if isinstance(entity, discord.abc.GuildChannel): return self._channel_info(entity, embed=embed) - else: - return self._guild_info(entity, embed=embed) + return self._guild_info(entity, embed=embed) def _member_info(self, member: discord.Member, /, *, embed: discord.Embed) -> discord.Embed: if member.joined_at: joined_at_fmt = ( - discord.utils.format_dt(member.joined_at, "F") + "\n" f"({discord.utils.format_dt(member.joined_at, 'R')})" + discord.utils.format_dt(member.joined_at, "F") + f"\n({discord.utils.format_dt(member.joined_at, 'R')})" ) embed.add_field(name="Member joined the guild on:", value=joined_at_fmt) @@ -82,12 +81,12 @@ def _member_info(self, member: discord.Member, /, *, embed: discord.Embed) -> di return embed - def _user_info(self, user: discord.User, /, *, embed: discord.Embed) -> discord.Embed: + def _user_info(self, user: discord.User | discord.Member, /, *, embed: discord.Embed) -> discord.Embed: embed = discord.Embed(title=f"Info on {user.display_name}!", colour=discord.Colour.random()) embed.set_author(name=user.name) embed.set_image(url=user.display_avatar.url) created_at_fmt = ( - discord.utils.format_dt(user.created_at, "F") + "\n" f"({discord.utils.format_dt(user.created_at, 'R')})" + discord.utils.format_dt(user.created_at, "F") + f"\n({discord.utils.format_dt(user.created_at, 'R')})" ) embed.add_field(name="Account was created on:", value=created_at_fmt) @@ -154,7 +153,7 @@ async def info( entity: Accepts a Person's ID, a Role ID or a Channel ID. Defaults to showing info on the Guild. """ - embed = self._embed_factory(entity) # type: ignore # we ignore because converter sadness + embed = self._embed_factory(entity) # pyright: ignore[reportArgumentType] # converter usage messes with types await ctx.reply(embed=embed, mention_author=False) diff --git a/modules/logging.py b/modules/logging.py index 79efea3..bd0fe76 100644 --- a/modules/logging.py +++ b/modules/logging.py @@ -19,7 +19,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -""" +""" # noqa: A005 # we access this via importlib machinery from __future__ import annotations diff --git a/modules/manuals.py b/modules/manuals.py index c1be44d..7a5ee2f 100644 --- a/modules/manuals.py +++ b/modules/manuals.py @@ -27,7 +27,7 @@ import yarl from discord.enums import Enum from discord.ext import commands -from discord.ext.commands.view import StringView # type: ignore # why does this need a stub +from discord.ext.commands.view import StringView # pyright: ignore[reportMissingTypeStubs] # why is this an error? import constants import core @@ -82,18 +82,18 @@ def _smart_guess_lib(self, ctx: core.Context) -> LibEnum | None: if "twitchio-help" in tags: return LibEnum.twitchio - elif "wavelink-help" in tags: + if "wavelink-help" in tags: return LibEnum.wavelink - elif "discord.py-help" in tags: + if "discord.py-help" in tags: return LibEnum.discordpy - elif "python-help" in tags: + if "python-help" in tags: return LibEnum.python return None if ctx.channel.id == constants.Channels.WAVELINK_DEV: return LibEnum.wavelink - elif ctx.channel.id == constants.Channels.TWITCHIO_DEV: + if ctx.channel.id == constants.Channels.TWITCHIO_DEV: return LibEnum.twitchio return None @@ -130,17 +130,16 @@ async def get_lib(self, ctx: core.Context, query: str) -> tuple[LibEnum, str, st await ctx.reply("Sorry, I couldn't find a library that matched. Try again with a different library?") return None - elif ( + if ( maybe_lib and isinstance(ctx.channel, discord.Thread) and ctx.channel.parent_id == constants.Channels.HELP_FORUM and lib == self._smart_guess_lib(ctx) + and 1006717008613740596 not in ctx.channel._applied_tags # pyright: ignore[reportPrivateUsage] # shortcut ): - if 1006717008613740596 not in ctx.channel._applied_tags: # type: ignore # other-help tag, that one doesnt get a smart guess - tip += ( - "\n• Tip: Forum posts with tags will automatically have the relevant libraries used, no need to" - " specify it!" - ) + tip += ( + "\n• Tip: Forum posts with tags will automatically have the relevant libraries used, no need to specify it!" + ) return lib, final_query, tip @@ -151,7 +150,7 @@ async def get_lib(self, ctx: core.Context, query: str) -> tuple[LibEnum, str, st signature="[library]? [query]", aliases=["docs", "rtfd"], ) - @commands.dynamic_cooldown(_cooldown_bucket, commands.BucketType.member) # type: ignore + @commands.dynamic_cooldown(_cooldown_bucket, commands.BucketType.member) # pyright: ignore[reportArgumentType] # this is generic narrowing nonsense async def rtfm(self, ctx: core.Context, *, query: str) -> None: """ Searches relevant documentation. @@ -197,7 +196,7 @@ async def rtfm(self, ctx: core.Context, *, query: str) -> None: "location": str(lib.value[0]), "show-labels": str(labels), "label-labels": str(clear_labels), - } + }, ) headers = { @@ -230,7 +229,7 @@ async def rtfm(self, ctx: core.Context, *, query: str) -> None: signature="[library]? [query]", aliases=["source"], ) - @commands.dynamic_cooldown(_cooldown_bucket, commands.BucketType.member) # type: ignore + @commands.dynamic_cooldown(_cooldown_bucket, commands.BucketType.member) # pyright: ignore[reportArgumentType] # this is generic narrowing nonsense async def rtfs(self, ctx: core.Context, *, query: str) -> None: """ Searches relevant library source code. @@ -269,7 +268,7 @@ async def rtfs(self, ctx: core.Context, *, query: str) -> None: "query": final_query, "library": str(lib.value[2]), "format": "links" if not source else "source", - } + }, ) headers = { @@ -306,7 +305,8 @@ async def rtfs(self, ctx: core.Context, *, query: str) -> None: else: n = next(iter(nodes.items())) await ctx.send( - f"Showing source for `{n[0]}`\nCommit: {matches['commit'][:6]}" + tip, reference=ctx.replied_message + f"Showing source for `{n[0]}`\nCommit: {matches['commit'][:6]}" + tip, + reference=ctx.replied_message, ) pages = TextPager(ctx, n[1], prefix="```py", reply_author_takes_paginator=True) diff --git a/modules/moderation.py b/modules/moderation.py index 01b9cb9..ae6a020 100644 --- a/modules/moderation.py +++ b/modules/moderation.py @@ -82,7 +82,12 @@ class ModerationRespostView(discord.ui.View): message: discord.Message | discord.WebhookMessage def __init__( - self, *, timeout: float | None = 180, event_type: core.DiscordPyModerationEvent, target_id: int, target_reason: str + self, + *, + timeout: float | None = 180, + event_type: core.DiscordPyModerationEvent, + target_id: int, + target_reason: str, ) -> None: super().__init__(timeout=timeout) self.event_type: core.DiscordPyModerationEvent = event_type @@ -155,7 +160,7 @@ async def github_request( ) -> Any: api_key = core.CONFIG["TOKENS"].get("github_bot") if not api_key: - return + return None hdrs = { "Accept": "application/vnd.github.inertia-preview+json", @@ -174,18 +179,17 @@ async def github_request( if r.status == 429 or remaining == "0": # wait before we release the lock - delta = discord.utils._parse_ratelimit_header(r) # type: ignore # shh this is okay + delta = discord.utils._parse_ratelimit_header(r) # pyright: ignore[reportPrivateUsage] # shh this is okay await asyncio.sleep(delta) self._req_lock.release() return await self.github_request(method, url, params=params, data=data, headers=headers) - elif 300 > r.status >= 200: + if 300 > r.status >= 200: return js - else: - raise GithubError(js["message"]) + raise GithubError(js["message"]) async def create_gist( self, @@ -205,7 +209,7 @@ async def create_gist( "files": { filename: { "content": content, - } + }, }, } @@ -223,7 +227,9 @@ async def find_discord_tokens(self, message: discord.Message) -> None: return url = await self.create_gist( - "\n".join(tokens), filename="tokens.txt", description="Tokens found within the Pythonista guild." + "\n".join(tokens), + filename="tokens.txt", + description="Tokens found within the Pythonista guild.", ) msg: str = ( @@ -290,7 +296,8 @@ async def on_papi_dpy_modlog(self, payload: ModLogPayload, /) -> None: moderator_id = payload["author_id"] moderator = self.dpy_mod_cache.get(moderator_id) or await self.bot.get_or_fetch_user( - moderator_id, cache=self.dpy_mod_cache + moderator_id, + cache=self.dpy_mod_cache, ) if moderator: diff --git a/modules/suggestions.py b/modules/suggestions.py index de6f737..0623d66 100644 --- a/modules/suggestions.py +++ b/modules/suggestions.py @@ -22,7 +22,6 @@ """ import logging -from datetime import datetime import discord from discord import ui @@ -53,7 +52,10 @@ def __init__( self.add_option(label="Guild", value="1", description="This suggestion applies to the guild.", emoji="\U0001f4c1") self.add_option( - label="PythonistaBot", value="2", description="This suggestion applies to PythonistaBot.", emoji="\U0001f916" + label="PythonistaBot", + value="2", + description="This suggestion applies to PythonistaBot.", + emoji="\U0001f916", ) self.add_option( label="TwitchIO", @@ -71,18 +73,24 @@ def __init__( async def callback(self, interaction: discord.Interaction) -> None: if self.original_author != interaction.user: return await interaction.response.send_message( - f"This menu is not for you. See `{core.CONFIG['prefix']}suggest` to make a suggestion!", ephemeral=True + f"This menu is not for you. See `{core.CONFIG['prefix']}suggest` to make a suggestion!", + ephemeral=True, ) author = self.original_author suggestion_type = get_suggestion_type(self.values[0]) await interaction.response.send_message("Your suggestion has been sent!") embed = discord.Embed( - title=f"Suggestion for {suggestion_type}", description=self.suggestion, timestamp=datetime.now(), color=0x7289DA + title=f"Suggestion for {suggestion_type}", + description=self.suggestion, + timestamp=discord.utils.utcnow(), + color=0x7289DA, ) embed.set_author(name=author.name, icon_url=author.display_avatar) embed.set_footer(text=f"Suggestion sent by {author} (ID: {author.id})") await self.webhook.send(embed=embed, avatar_url=author.display_avatar, username=author.name) + return None + class TypeView(ui.View): message: discord.Message | discord.WebhookMessage diff --git a/server/application.py b/server/application.py index f43374f..be32924 100644 --- a/server/application.py +++ b/server/application.py @@ -21,6 +21,7 @@ SOFTWARE. """ +from json import JSONDecodeError from typing import Any import starlette_plus @@ -50,7 +51,7 @@ async def dpy_modlog(self, request: starlette_plus.Request) -> starlette_plus.Re try: data: dict[str, Any] = await request.json() - except Exception as e: + except (JSONDecodeError, TypeError) as e: return starlette_plus.Response(f"Invalid payload: {e}", status_code=400) if not data: