diff --git a/cogs/__init__.py b/cogs/__init__.py index 1bb07f5d5..068641872 100644 --- a/cogs/__init__.py +++ b/cogs/__init__.py @@ -77,7 +77,7 @@ "StartupCog", "StatsCommandsCog", "StrikeCommandCog", - "StrikeUserCommandCog", + "StrikeContextCommandsCog", "WriteRolesCommandCog", "setup", ) diff --git a/cogs/command_error.py b/cogs/command_error.py index e18be21ef..5da025d9a 100644 --- a/cogs/command_error.py +++ b/cogs/command_error.py @@ -1,6 +1,5 @@ """Contains cog classes for any command_error interactions.""" -import contextlib import logging from typing import TYPE_CHECKING @@ -8,10 +7,7 @@ from discord import Forbidden from discord.ext.commands.errors import CheckAnyFailure -from exceptions import ( - CommitteeRoleDoesNotExistError, - GuildDoesNotExistError, -) +from exceptions import GuildDoesNotExistError from exceptions.base import BaseErrorWithErrorCode from utils import CommandChecks, TeXBotBaseCog @@ -60,11 +56,10 @@ async def on_application_command_error( ) elif CommandChecks.is_interaction_user_has_committee_role_failure(error.checks[0]): # type: ignore[arg-type] - # noinspection PyUnusedLocal - committee_role_mention: str = "@Committee" - with contextlib.suppress(CommitteeRoleDoesNotExistError): - committee_role_mention = (await self.bot.committee_role).mention - message = f"Only {committee_role_mention} members can run this command." + message = ( + f"Only {await self.bot.get_mention_string(self.bot.committee_role)} " + "members can run this command." + ) await self.command_send_error( ctx, diff --git a/cogs/delete_all.py b/cogs/delete_all.py index ed51fd649..d89572dc1 100644 --- a/cogs/delete_all.py +++ b/cogs/delete_all.py @@ -31,7 +31,6 @@ async def _delete_all( ctx: "TeXBotApplicationContext", delete_model: type["AsyncBaseModel"] ) -> None: """Perform the actual deletion process of all instances of the given model class.""" - # noinspection PyProtectedMember await delete_model._default_manager.all().adelete() delete_model_instances_name_plural: str = ( diff --git a/cogs/edit_message.py b/cogs/edit_message.py index 4746efcf4..aa6e4e1bc 100644 --- a/cogs/edit_message.py +++ b/cogs/edit_message.py @@ -25,7 +25,6 @@ class EditMessageCommandCog(TeXBotBaseCog): - # noinspection SpellCheckingInspection """Cog class that defines the "/edit-message" command and its call-back method.""" @staticmethod @@ -50,7 +49,6 @@ async def autocomplete_get_text_channels( return await TeXBotBaseCog.autocomplete_get_text_channels(ctx) - # noinspection SpellCheckingInspection @discord.slash_command( # type: ignore[no-untyped-call, misc] name="edit-message", description="Edits a message sent by TeX-Bot to the value supplied.", diff --git a/cogs/induct.py b/cogs/induct.py index 92d49fd7a..ea42fb757 100644 --- a/cogs/induct.py +++ b/cogs/induct.py @@ -15,8 +15,6 @@ GuestRoleDoesNotExistError, GuildDoesNotExistError, MemberRoleDoesNotExistError, - RolesChannelDoesNotExistError, - RulesChannelDoesNotExistError, ) from utils import ( CommandChecks, @@ -77,59 +75,60 @@ async def on_member_update(self, before: discord.Member, after: discord.Member) await IntroductionReminderOptOutMember.objects.aget(discord_id=before.id) ).adelete() - async for message in after.history(): + reminder_message: discord.Message + async for reminder_message in after.history(): MESSAGE_IS_INTRODUCTION_REMINDER: bool = bool( - ("joined the " in message.content) - and (" Discord guild but have not yet introduced" in message.content) - and message.author.bot + ("joined the " in reminder_message.content) + and (" Discord guild but have not yet introduced" in reminder_message.content) + and reminder_message.author.bot ) if MESSAGE_IS_INTRODUCTION_REMINDER: - await message.delete( + await reminder_message.delete( reason="Delete introduction reminders after member is inducted.", ) - # noinspection PyUnusedLocal - rules_channel_mention: str = "**`#welcome`**" - with contextlib.suppress(RulesChannelDoesNotExistError): - rules_channel_mention = (await self.bot.rules_channel).mention - - # noinspection PyUnusedLocal - roles_channel_mention: str = "**`#roles`**" - with contextlib.suppress(RolesChannelDoesNotExistError): - roles_channel_mention = (await self.bot.roles_channel).mention - - user_type: Literal["guest", "member"] = "guest" - with contextlib.suppress(MemberRoleDoesNotExistError): - if await self.bot.member_role in after.roles: - user_type = "member" - + user_type: Literal["guest", "member"] try: - await after.send( + user_type = "member" if await self.bot.member_role in after.roles else "guest" + except MemberRoleDoesNotExistError: + user_type = "guest" + + messages_to_send: list[str] = [ + ( f"**Congrats on joining the {self.bot.group_short_name} Discord server " f"as a {user_type}!** " "You now have access to communicate in all the public channels.\n\n" "Some things to do to get started:\n" - f"1. Check out our rules in {rules_channel_mention}\n" - f"2. Head to {roles_channel_mention} and click on the icons to get " - "optional roles like pronouns and year groups\n" + f"1. Check out our rules in { + await self.bot.get_mention_string(self.bot.rules_channel) + }\n" + f"2. Head to { + await self.bot.get_mention_string(self.bot.roles_channel) + } and click on the icons to get optional roles like pronouns and year groups\n" "3. Change your nickname to whatever you wish others to refer to you as " "(You can do this by right-clicking your name in the members-list " - 'to the right & selecting "Edit Server Profile").', + 'to the right & selecting "Edit Server Profile").' + ), + ] + + if user_type != "member": + messages_to_send.append( + f"You can also get yourself an annual membership " + f"to {self.bot.group_full_name} for only £5! " + f"Just head to {settings['PURCHASE_MEMBERSHIP_URL']}. " + "You'll get awesome perks like a free T-shirt:shirt:, " + "access to member only events:calendar_spiral: and a cool green name on " + f"the {self.bot.group_short_name} Discord server:green_square:! " + f"Checkout all the perks at {settings['MEMBERSHIP_PERKS_URL']}", ) - if user_type != "member": - await after.send( - f"You can also get yourself an annual membership " - f"to {self.bot.group_full_name} for only £5! " - f"Just head to {settings['PURCHASE_MEMBERSHIP_URL']}. " - "You'll get awesome perks like a free T-shirt:shirt:, " - "access to member only events:calendar_spiral: and a cool green name on " - f"the {self.bot.group_short_name} Discord server:green_square:! " - f"Checkout all the perks at {settings['MEMBERSHIP_PERKS_URL']}", - ) + + try: + message_to_send: str + for message_to_send in messages_to_send: + await after.send(message_to_send) except discord.Forbidden: logger.info( - "Failed to open DM channel to user %s so no welcome message was sent.", - after, + "Failed to open DM channel to user %s so no welcome message was sent.", after ) @@ -227,17 +226,11 @@ async def _perform_induction( return if not silent: - general_channel: discord.TextChannel = await self.bot.general_channel - - # noinspection PyUnusedLocal - roles_channel_mention: str = "**`#roles`**" - with contextlib.suppress(RolesChannelDoesNotExistError): - roles_channel_mention = (await self.bot.roles_channel).mention - - await general_channel.send( + await (await self.bot.general_channel).send( f"{await self.get_random_welcome_message(induction_member)} :tada:\n" - f"Remember to grab your roles in {roles_channel_mention} " - "and say hello to everyone here! :wave:", + f"Remember to grab your roles in { + await self.bot.get_mention_string(self.bot.roles_channel) + } and say hello to everyone here! :wave:", ) await induction_member.add_roles( @@ -245,16 +238,16 @@ async def _perform_induction( reason=INDUCT_AUDIT_MESSAGE, ) - # noinspection PyUnusedLocal - applicant_role: discord.Role | None = None - with contextlib.suppress(ApplicantRoleDoesNotExistError): - applicant_role = await ctx.bot.applicant_role - - if applicant_role and applicant_role in induction_member.roles: - await induction_member.remove_roles( - applicant_role, - reason=INDUCT_AUDIT_MESSAGE, - ) + try: + applicant_role: discord.Role = await ctx.bot.applicant_role + except ApplicantRoleDoesNotExistError: + pass + else: + if applicant_role in induction_member.roles: + await induction_member.remove_roles( + applicant_role, + reason=INDUCT_AUDIT_MESSAGE, + ) tex_emoji: discord.Emoji | None = self.bot.get_emoji(743218410409820213) if not tex_emoji: @@ -365,7 +358,7 @@ async def induct( # type: ignore[misc] class InductContextCommandsCog(BaseInductCog): - """Cog class that defines the context-menu induction commands & their call-back methods.""" + """Cog class to define the context-menu induction commands and their call-back methods.""" @discord.user_command(name="Induct User") # type: ignore[no-untyped-call, misc] @CommandChecks.check_interaction_user_has_committee_role @@ -434,7 +427,6 @@ async def non_silent_message_induct( # type: ignore[misc] class EnsureMembersInductedCommandCog(TeXBotBaseCog): """Cog class that defines the "/ensure-members-inducted" command and call-back method.""" - # noinspection SpellCheckingInspection @discord.slash_command( # type: ignore[no-untyped-call, misc] name="ensure-members-inducted", description="Ensures all users with the @Member role also have the @Guest role.", diff --git a/cogs/kill.py b/cogs/kill.py index 66b52d7a9..62d8db471 100644 --- a/cogs/kill.py +++ b/cogs/kill.py @@ -1,6 +1,5 @@ """Contains cog classes for any killing interactions.""" -import contextlib import logging from typing import TYPE_CHECKING @@ -64,9 +63,11 @@ async def kill(self, ctx: "TeXBotApplicationContext") -> None: # type: ignore[m The "kill" command shuts down TeX-Bot, but only after the user has confirmed that this is the action they wish to take. """ - committee_role: discord.Role | None = None - with contextlib.suppress(CommitteeRoleDoesNotExistError): + committee_role: discord.Role | None + try: committee_role = await self.bot.committee_role + except CommitteeRoleDoesNotExistError: + committee_role = None response: discord.Message | discord.Interaction = await ctx.respond( content=( diff --git a/cogs/make_member.py b/cogs/make_member.py index 1171adedd..a5265e706 100644 --- a/cogs/make_member.py +++ b/cogs/make_member.py @@ -1,6 +1,5 @@ """Contains cog classes for any make_member interactions.""" -import contextlib import logging import re from typing import TYPE_CHECKING @@ -13,11 +12,7 @@ from config import settings from db.core.models import GroupMadeMember -from exceptions import ( - ApplicantRoleDoesNotExistError, - CommitteeRoleDoesNotExistError, - GuestRoleDoesNotExistError, -) +from exceptions import ApplicantRoleDoesNotExistError, GuestRoleDoesNotExistError from utils import CommandChecks, TeXBotBaseCog if TYPE_CHECKING: @@ -78,10 +73,8 @@ class MakeMemberCommandCog(TeXBotBaseCog): - # noinspection SpellCheckingInspection """Cog class that defines the "/makemember" command and its call-back method.""" - # noinspection SpellCheckingInspection @discord.slash_command( # type: ignore[no-untyped-call, misc] name="makemember", description=( @@ -118,7 +111,7 @@ class MakeMemberCommandCog(TeXBotBaseCog): parameter_name="group_member_id", ) @CommandChecks.check_interaction_user_in_main_guild - async def make_member(self, ctx: "TeXBotApplicationContext", group_member_id: str) -> None: # type: ignore[misc] # noqa: PLR0915 + async def make_member(self, ctx: "TeXBotApplicationContext", group_member_id: str) -> None: # type: ignore[misc] """ Definition & callback response of the "make_member" command. @@ -152,24 +145,17 @@ async def make_member(self, ctx: "TeXBotApplicationContext", group_member_id: st ) return - GROUP_MEMBER_ID_IS_ALREADY_USED: Final[ - bool - ] = await GroupMadeMember.objects.filter( + if await GroupMadeMember.objects.filter( hashed_group_member_id=GroupMadeMember.hash_group_member_id( group_member_id, self.bot.group_member_id_type ) - ).aexists() - if GROUP_MEMBER_ID_IS_ALREADY_USED: - # noinspection PyUnusedLocal - committee_mention: str = "committee" - with contextlib.suppress(CommitteeRoleDoesNotExistError): - committee_mention = (await self.bot.committee_role).mention - + ).aexists(): await ctx.followup.send( content=( ":information_source: No changes made. This student ID has already " - f"been used. Please contact a {committee_mention} member if this is " - "an error. :information_source:" + f"been used. Please contact a { + await self.bot.get_mention_string(self.bot.committee_role) + } member if this is an error. :information_source:" ), ephemeral=True, ) @@ -278,11 +264,11 @@ async def make_member(self, ctx: "TeXBotApplicationContext", group_member_id: st guest_role, reason='TeX Bot slash-command: "/makemember"', ) - - # noinspection PyUnusedLocal - applicant_role: discord.Role | None = None - with contextlib.suppress(ApplicantRoleDoesNotExistError): + applicant_role: discord.Role | None + try: applicant_role = await ctx.bot.applicant_role + except ApplicantRoleDoesNotExistError: + applicant_role = None if applicant_role and applicant_role in interaction_member.roles: await interaction_member.remove_roles( diff --git a/cogs/remind_me.py b/cogs/remind_me.py index 0a7049889..d686e991a 100644 --- a/cogs/remind_me.py +++ b/cogs/remind_me.py @@ -215,7 +215,6 @@ async def remind_me( # type: ignore[misc] The "remind_me" command responds with the given message after the specified time. """ - # noinspection PyTypeChecker parsed_time: tuple[time.struct_time, int] = parsedatetime.Calendar().parseDT( delay, tzinfo=timezone.get_current_timezone(), @@ -289,7 +288,7 @@ def __init__(self, bot: "TeXBot") -> None: @override def cog_unload(self) -> None: """ - Unload hook that ends all running tasks whenever the tasks cog is unloaded. + Unload-hook that ends all running tasks whenever the tasks cog is unloaded. This may be run dynamically or when the bot closes. """ @@ -333,7 +332,6 @@ async def clear_reminders_backlog(self) -> None: await reminder.adelete() continue - # noinspection PyUnresolvedReferences channel: discord.PartialMessageable = self.bot.get_partial_messageable( reminder.channel_id, type=reminder.channel_type, diff --git a/cogs/send_get_roles_reminders.py b/cogs/send_get_roles_reminders.py index e67b61825..173276d12 100644 --- a/cogs/send_get_roles_reminders.py +++ b/cogs/send_get_roles_reminders.py @@ -1,6 +1,5 @@ """Contains cog classes for any send_get_roles_reminders interactions.""" -import contextlib import functools import logging from typing import TYPE_CHECKING, override @@ -12,7 +11,7 @@ import utils from config import settings from db.core.models import SentGetRolesReminderMember -from exceptions import GuestRoleDoesNotExistError, RolesChannelDoesNotExistError +from exceptions import GuestRoleDoesNotExistError from utils import TeXBotBaseCog from utils.error_capture_decorators import ( ErrorCaptureDecorators, @@ -46,7 +45,7 @@ def __init__(self, bot: "TeXBot") -> None: @override def cog_unload(self) -> None: """ - Unload hook that ends all running tasks whenever the tasks cog is unloaded. + Unload-hook that ends all running tasks whenever the tasks cog is unloaded. This may be run dynamically or when the bot closes. """ @@ -72,13 +71,8 @@ async def send_get_roles_reminders(self) -> None: # NOTE: Shortcut accessors are placed at the top of the function, so that the exceptions they raise are displayed before any further errors may be sent main_guild: discord.Guild = self.bot.main_guild guest_role: discord.Role = await self.bot.guest_role + roles_channel_mention: str = await self.bot.get_mention_string(self.bot.roles_channel) - # noinspection PyUnusedLocal - roles_channel_mention: str = "**`#roles`**" - with contextlib.suppress(RolesChannelDoesNotExistError): - roles_channel_mention = (await self.bot.roles_channel).mention - - # noinspection SpellCheckingInspection OPT_IN_ROLE_NAMES: Final[frozenset[str]] = frozenset( { "He / Him", @@ -131,10 +125,8 @@ async def send_get_roles_reminders(self) -> None: if sent_get_roles_reminder_member_exists: continue - # noinspection PyUnusedLocal - guest_role_received_time: datetime.datetime | None = None - with contextlib.suppress(StopIteration, StopAsyncIteration): - # noinspection PyTypeChecker + guest_role_received_time: datetime.datetime | None + try: guest_role_received_time = await anext( log.created_at async for log in main_guild.audit_logs( @@ -146,6 +138,8 @@ async def send_get_roles_reminders(self) -> None: and guest_role in log.after.roles ) ) + except (StopIteration, StopAsyncIteration): + guest_role_received_time = None if guest_role_received_time is not None: time_since_role_received: datetime.timedelta = ( diff --git a/cogs/send_introduction_reminders.py b/cogs/send_introduction_reminders.py index 79f48c41b..bd4bbe71c 100644 --- a/cogs/send_introduction_reminders.py +++ b/cogs/send_introduction_reminders.py @@ -132,7 +132,6 @@ async def send_introduction_reminders(self) -> None: continue async for message in member.history(): - # noinspection PyUnresolvedReferences MESSAGE_CONTAINS_OPT_IN_OUT_BUTTON: bool = bool( bool(message.components) and isinstance(message.components[0], discord.ActionRow) diff --git a/cogs/stats.py b/cogs/stats.py index 879f9e9d7..d6d5ddbb3 100644 --- a/cogs/stats.py +++ b/cogs/stats.py @@ -6,7 +6,7 @@ from typing import TYPE_CHECKING import discord -import matplotlib.pyplot as plt +import matplotlib.pyplot import mplcyberpunk from config import settings @@ -52,7 +52,7 @@ def plot_bar_chart( extra_text: str = "", ) -> discord.File: """Generate an image of a plot bar chart from the given data & format variables.""" - plt.style.use("cyberpunk") + matplotlib.pyplot.style.use("cyberpunk") max_data_value: int = max(data.values()) + 1 @@ -68,15 +68,15 @@ def plot_bar_chart( if value > 0 or index <= 4 } - bars = plt.bar(*zip(*data.items(), strict=True)) + bars = matplotlib.pyplot.bar(*zip(*data.items(), strict=True)) if extra_values: - extra_bars = plt.bar(*zip(*extra_values.items(), strict=True)) + extra_bars = matplotlib.pyplot.bar(*zip(*extra_values.items(), strict=True)) mplcyberpunk.add_bar_gradient(extra_bars) mplcyberpunk.add_bar_gradient(bars) - x_tick_labels: Collection[Plot_Text] = plt.gca().get_xticklabels() + x_tick_labels: Collection[Plot_Text] = matplotlib.pyplot.gca().get_xticklabels() count_x_tick_labels: int = len(x_tick_labels) index: int @@ -89,9 +89,9 @@ def plot_bar_chart( if index % 2 == 1 and count_x_tick_labels > 4: tick_label.set_y(tick_label.get_position()[1] - 0.044) - plt.yticks(range(0, max_data_value, math.ceil(max_data_value / 15))) + matplotlib.pyplot.yticks(range(0, max_data_value, math.ceil(max_data_value / 15))) - x_label_obj: Plot_Text = plt.xlabel( + x_label_obj: Plot_Text = matplotlib.pyplot.xlabel( x_label, fontweight="bold", fontsize="large", @@ -99,7 +99,7 @@ def plot_bar_chart( ) x_label_obj._get_wrap_line_width = lambda: 475 # type: ignore[attr-defined] - y_label_obj: Plot_Text = plt.ylabel( + y_label_obj: Plot_Text = matplotlib.pyplot.ylabel( y_label, fontweight="bold", fontsize="large", @@ -107,26 +107,26 @@ def plot_bar_chart( ) y_label_obj._get_wrap_line_width = lambda: 375 # type: ignore[attr-defined] - title_obj: Plot_Text = plt.title(title, fontsize="x-large", wrap=True) + title_obj: Plot_Text = matplotlib.pyplot.title(title, fontsize="x-large", wrap=True) title_obj._get_wrap_line_width = lambda: 500 # type: ignore[attr-defined] if extra_text: - extra_text_obj: Plot_Text = plt.text( + extra_text_obj: Plot_Text = matplotlib.pyplot.text( 0.5, -0.27, extra_text, ha="center", - transform=plt.gca().transAxes, + transform=matplotlib.pyplot.gca().transAxes, wrap=True, fontstyle="italic", fontsize="small", ) extra_text_obj._get_wrap_line_width = lambda: 400 # type: ignore[attr-defined] - plt.subplots_adjust(bottom=0.2) + matplotlib.pyplot.subplots_adjust(bottom=0.2) plot_file = io.BytesIO() - plt.savefig(plot_file, format="png") - plt.close() + matplotlib.pyplot.savefig(plot_file, format="png") + matplotlib.pyplot.close() plot_file.seek(0) discord_plot_file: discord.File = discord.File( @@ -178,7 +178,6 @@ class StatsCommandsCog(TeXBotBaseCog): description=f"Various statistics about {_DISCORD_SERVER_NAME} Discord server", ) - # noinspection SpellCheckingInspection @stats.command( name="channel", description="Displays the stats for the current/a given channel.", @@ -511,7 +510,6 @@ async def user_stats(self, ctx: "TeXBotApplicationContext") -> None: ), ) - # noinspection SpellCheckingInspection @stats.command( name="left-members", description=f"Displays the stats about members that have left {_DISCORD_SERVER_NAME}", diff --git a/cogs/strike.py b/cogs/strike.py index a3db5714a..94faa470e 100644 --- a/cogs/strike.py +++ b/cogs/strike.py @@ -8,8 +8,6 @@ from typing import TYPE_CHECKING import discord - -# noinspection SpellCheckingInspection from asyncstdlib.builtins import any as asyncany from discord.ui import View @@ -18,7 +16,6 @@ from exceptions import ( GuildDoesNotExistError, NoAuditLogsStrikeTrackingError, - RulesChannelDoesNotExistError, StrikeTrackingError, ) from utils import ( @@ -257,37 +254,29 @@ class BaseStrikeCog(TeXBotBaseCog): async def _send_strike_user_message( self, strike_user: discord.User | discord.Member, member_strikes: DiscordMemberStrikes ) -> None: - # noinspection PyUnusedLocal - rules_channel_mention: str = "**`#welcome`**" - with contextlib.suppress(RulesChannelDoesNotExistError): - rules_channel_mention = (await self.bot.rules_channel).mention - - includes_ban_message: str = ( - ( - "\nBecause you now have been given 3 strikes, you have been banned from " - f"the {self.bot.group_short_name} Discord server " - f"and we have contacted {self.bot.group_moderation_contact} for " - "further action & advice." - ) - if member_strikes.strikes >= 3 - else "" - ) - - actual_strike_amount: int = min(3, member_strikes.strikes) - await strike_user.send( "Hi, a recent incident occurred in which you may have broken one or more of " f"the {self.bot.group_short_name} Discord server's rules.\n" "We have increased the number of strikes associated with your account " - f"to {actual_strike_amount} and " + f"to {min(3, member_strikes.strikes)} and " "the corresponding moderation action will soon be applied to you. " "To find what moderation action corresponds to which strike level, " "you can view " f"the {self.bot.group_short_name} Discord server moderation document " f"[here](<{settings.MODERATION_DOCUMENT_URL}>)\nPlease ensure you have read " - f"the rules in {rules_channel_mention} so that your future behaviour adheres " - f"to them.{includes_ban_message}\n\nA committee member will be in contact " - "with you shortly, to discuss this further.", + f"the rules in {await self.bot.get_mention_string(self.bot.rules_channel)} so " + "that your future behaviour adheres to them." + f"{ + ( + '\nBecause you now have been given 3 strikes, you have been banned from ' + f'the {self.bot.group_short_name} Discord server ' + f'and we have contacted {self.bot.group_moderation_contact} for ' + 'further action & advice.' + ) + if member_strikes.strikes >= 3 + else '' + }\n\n" + "A committee member will be in contact with you shortly, to discuss this further." ) async def _confirm_perform_moderation_action( @@ -392,7 +381,6 @@ async def _confirm_increase_strike( ) if not perform_action: - # noinspection SpellCheckingInspection await message_sender_component.send( content=( f"{confirm_strike_message}\n" @@ -510,7 +498,6 @@ async def get_confirmation_message_channel( return guild_confirmation_message_channel - # noinspection PyTypeHints @capture_strike_tracking_error async def _confirm_manual_add_strike( self, strike_user: discord.User | discord.Member, action: discord.AuditLogAction @@ -520,7 +507,6 @@ async def _confirm_manual_add_strike( committee_role: discord.Role = await self.bot.committee_role try: - # noinspection PyTypeChecker audit_log_entry: discord.AuditLogEntry = await anext( _audit_log_entry async for _audit_log_entry in main_guild.audit_logs( @@ -648,7 +634,6 @@ async def _confirm_manual_add_strike( "with number of strikes**" ), ) - # noinspection SpellCheckingInspection await out_of_sync_ban_confirmation_message.edit( content=( f"Successfully banned {strike_user.mention}.\n" @@ -710,7 +695,6 @@ async def _confirm_manual_add_strike( ) if button_interaction.data["custom_id"] == "no_manual_moderation_action": # type: ignore[index, typeddict-item] - # noinspection SpellCheckingInspection await confirmation_message.edit( content=( f"Aborted increasing {strike_user.mention}'s strikes " @@ -750,7 +734,7 @@ async def _confirm_manual_add_strike( @TeXBotBaseCog.listener() @capture_guild_does_not_exist_error async def on_member_update(self, before: discord.Member, after: discord.Member) -> None: - """Flag manually applied timeout & track strikes accordingly.""" + """Flag manually applied timeout and track strikes accordingly.""" # NOTE: Shortcut accessors are placed at the top of the function, so that the exceptions they raise are displayed before any further errors may be sent main_guild: discord.Guild = self.bot.main_guild @@ -762,10 +746,9 @@ async def on_member_update(self, before: discord.Member, after: discord.Member) audit_log_entry: discord.AuditLogEntry async for audit_log_entry in main_guild.audit_logs(limit=5): - FOUND_CORRECT_AUDIT_LOG_ENTRY: bool = ( - audit_log_entry.target.id == after.id - and audit_log_entry.action - == (discord.AuditLogAction.auto_moderation_user_communication_disabled) + FOUND_CORRECT_AUDIT_LOG_ENTRY: bool = audit_log_entry.target.id == after.id and ( + audit_log_entry.action + == discord.AuditLogAction.auto_moderation_user_communication_disabled ) if FOUND_CORRECT_AUDIT_LOG_ENTRY: await self._confirm_manual_add_strike( @@ -782,7 +765,7 @@ async def on_member_update(self, before: discord.Member, after: discord.Member) @TeXBotBaseCog.listener() @capture_guild_does_not_exist_error async def on_member_remove(self, member: discord.Member) -> None: - """Flag manually applied kick & track strikes accordingly.""" + """Flag manually applied kick and track strikes accordingly.""" # NOTE: Shortcut accessors are placed at the top of the function, so that the exceptions they raise are displayed before any further errors may be sent main_guild: discord.Guild = self.bot.main_guild @@ -805,7 +788,7 @@ async def on_member_remove(self, member: discord.Member) -> None: async def on_member_ban( self, guild: discord.Guild, user: discord.User | discord.Member ) -> None: - """Flag manually applied ban & track strikes accordingly.""" + """Flag manually applied ban and track strikes accordingly.""" if guild != self.bot.main_guild or user.bot: return @@ -884,7 +867,7 @@ async def strike(self, ctx: "TeXBotApplicationContext", str_strike_member_id: st class StrikeContextCommandsCog(BaseStrikeCog): - """Cog class that defines the context menu strike command & its call-back method.""" + """Cog class that defines the context menu strike command and its call-back method.""" async def _send_message_to_committee( self, ctx: "TeXBotApplicationContext", message: discord.Message diff --git a/cogs/write_roles.py b/cogs/write_roles.py index c24278455..cd76ee94c 100644 --- a/cogs/write_roles.py +++ b/cogs/write_roles.py @@ -16,10 +16,8 @@ class WriteRolesCommandCog(TeXBotBaseCog): - # noinspection SpellCheckingInspection """Cog class that defines the "/write-roles" command and its call-back method.""" - # noinspection SpellCheckingInspection @discord.slash_command( # type: ignore[no-untyped-call, misc] name="write-roles", description="Populates #roles with the correct messages.", diff --git a/config.py b/config.py index 76d7179ec..a401be2e6 100644 --- a/config.py +++ b/config.py @@ -1,5 +1,5 @@ """ -Contains settings values and import & setup functions. +Contains settings values and import and setup functions. Settings values are imported from the .env file or the current environment variables. These values are used to configure the functionality of the bot at run-time. @@ -149,7 +149,6 @@ def _setup_logging() -> None: logger.setLevel(getattr(logging, raw_console_log_level)) console_logging_handler: logging.Handler = logging.StreamHandler() - # noinspection SpellCheckingInspection console_logging_handler.setFormatter( logging.Formatter("{asctime} | {name} | {levelname:^8} - {message}", style="{"), ) @@ -243,6 +242,7 @@ def _setup_group_full_name(cls) -> None: "GROUP_NAME must not contain any invalid characters." ) raise ImproperlyConfiguredError(INVALID_GROUP_FULL_NAME) + cls._settings["_GROUP_FULL_NAME"] = raw_group_full_name @classmethod @@ -258,6 +258,7 @@ def _setup_group_short_name(cls) -> None: "GROUP_SHORT_NAME must not contain any invalid characters." ) raise ImproperlyConfiguredError(INVALID_GROUP_SHORT_NAME) + cls._settings["_GROUP_SHORT_NAME"] = raw_group_short_name @classmethod @@ -386,6 +387,7 @@ def _setup_roles_messages(cls) -> None: dict_key="roles_messages", invalid_value=messages_dict["roles_messages"], ) + cls._settings["ROLES_MESSAGES"] = set(messages_dict["roles_messages"]) # type: ignore[call-overload] @classmethod @@ -773,8 +775,7 @@ class RuntimeSettings(Settings): def run_setup() -> None: - """Execute the setup functions required, before other modules can be run.""" - # noinspection PyProtectedMember + """Execute the required setup functions.""" settings._setup_env_variables() # noqa: SLF001 logger.debug("Begin database setup") diff --git a/db/core/models/__init__.py b/db/core/models/__init__.py index e368ac74b..c2bfdea88 100644 --- a/db/core/models/__init__.py +++ b/db/core/models/__init__.py @@ -2,7 +2,7 @@ import hashlib import re -from typing import TYPE_CHECKING, Final, override +from typing import TYPE_CHECKING, override import discord from django.core.exceptions import ValidationError @@ -14,8 +14,10 @@ if TYPE_CHECKING: from collections.abc import Sequence + from typing import Final __all__: "Sequence[str]" = ( + "AssignedCommitteeAction", "DiscordMember", "DiscordMemberStrikes", "DiscordReminder", @@ -31,6 +33,8 @@ class AssignedCommitteeAction(BaseDiscordMemberWrapper): """Model to represent an action that has been assigned to a Discord committee-member.""" class Status(models.TextChoices): + """The named status and shortcode associated with the progress of each action.""" + BLOCKED = "BLK", _("Blocked") CANCELLED = "CND", _("Cancelled") COMPLETE = "CMP", _("Complete") @@ -62,7 +66,7 @@ class Status(models.TextChoices): blank=False, ) - class Meta: + class Meta: # noqa: D106 verbose_name = "Assigned Committee Action" constraints = [ # noqa: RUF012 models.UniqueConstraint( @@ -312,7 +316,6 @@ def channel_type(self) -> discord.ChannelType: def channel_type(self, channel_type: discord.ChannelType | int) -> None: if isinstance(channel_type, discord.ChannelType): try: - # noinspection PyUnresolvedReferences channel_type = int(channel_type.value) except ValueError: INVALID_CHANNEL_TYPE_MESSAGE: Final[str] = ( @@ -407,7 +410,6 @@ class Meta: # noqa: D106 @override def __str__(self) -> str: - # noinspection PyUnresolvedReferences return f"{self.id}: {', '.join(self.roles)}" @override diff --git a/db/core/models/managers.py b/db/core/models/managers.py index 9b1b0eb2a..fcc0f3524 100644 --- a/db/core/models/managers.py +++ b/db/core/models/managers.py @@ -32,7 +32,6 @@ class BaseHashedIDManager[T_model: AsyncBaseModel](Manager[T_model], abc.ABC): use_in_migrations: bool = True - # noinspection SpellCheckingInspection @abc.abstractmethod def _remove_unhashed_id_from_kwargs(self, kwargs: dict[str, object]) -> dict[str, object]: """Remove any unhashed ID values from the kwargs dict before executing a query.""" @@ -50,7 +49,6 @@ def _perform_remove_unhashed_id_from_kwargs( return self._remove_unhashed_id_from_kwargs(kwargs=kwargs) - # noinspection SpellCheckingInspection @abc.abstractmethod async def _aremove_unhashed_id_from_kwargs( self, kwargs: dict[str, object] @@ -88,7 +86,6 @@ def exclude(self, *args: object, **kwargs: object) -> "QuerySet[T_model]": **self._perform_remove_unhashed_id_from_kwargs(kwargs), ) - # noinspection SpellCheckingInspection async def aexclude(self, *args: object, **kwargs: object) -> "QuerySet[T_model]": return super().exclude( *args, @@ -99,7 +96,6 @@ async def aexclude(self, *args: object, **kwargs: object) -> "QuerySet[T_model]" def create(self, **kwargs: object) -> T_model: return super().create(**self._perform_remove_unhashed_id_from_kwargs(kwargs)) - # noinspection SpellCheckingInspection @override async def acreate(self, **kwargs: object) -> T_model: return await super().acreate(**(await self._aremove_unhashed_id_from_kwargs(kwargs))) @@ -139,7 +135,6 @@ def update_or_create( # type: ignore[override] **self._perform_remove_unhashed_id_from_kwargs(kwargs), ) - # noinspection SpellCheckingInspection @override async def aupdate_or_create( # type: ignore[override] self, @@ -157,7 +152,6 @@ async def aupdate_or_create( # type: ignore[override] def update(self, **kwargs: object) -> int: return super().update(**self._perform_remove_unhashed_id_from_kwargs(kwargs)) - # noinspection SpellCheckingInspection @override async def aupdate(self, **kwargs: object) -> int: return await super().aupdate(**(await self._aremove_unhashed_id_from_kwargs(kwargs))) @@ -165,13 +159,12 @@ async def aupdate(self, **kwargs: object) -> int: class HashedDiscordMemberManager(BaseHashedIDManager["DiscordMember"]): """ - Manager class to create & retrieve DiscordMember model instances. + Manager class to create and retrieve DiscordMember model instances. This manager implements extra functionality to filter/create instances using a given discord_id that with be automatically hashed before saved to the database. """ - # noinspection SpellCheckingInspection @override def _remove_unhashed_id_from_kwargs(self, kwargs: dict[str, object]) -> dict[str, object]: raw_discord_id: object | None = None @@ -193,7 +186,6 @@ def _remove_unhashed_id_from_kwargs(self, kwargs: dict[str, object]) -> dict[str return kwargs - # noinspection SpellCheckingInspection @override async def _aremove_unhashed_id_from_kwargs( self, kwargs: dict[str, object] @@ -222,13 +214,12 @@ class RelatedDiscordMemberManager[T_BaseDiscordMemberWrapper: "BaseDiscordMember BaseHashedIDManager[T_BaseDiscordMemberWrapper] ): """ - Manager class to create & retrieve instances of any concrete `BaseDiscordMemberWrapper`. + Manager class to create and retrieve instances of any concrete `BaseDiscordMemberWrapper`. This manager implements extra functionality to filter/create instances using a given discord_id that with be automatically hashed before saved to the database. """ - # noinspection SpellCheckingInspection @override def _remove_unhashed_id_from_kwargs(self, kwargs: dict[str, object]) -> dict[str, object]: raw_discord_id: object | None = None @@ -260,7 +251,6 @@ def _remove_unhashed_id_from_kwargs(self, kwargs: dict[str, object]) -> dict[str return kwargs - # noinspection SpellCheckingInspection @override async def _aremove_unhashed_id_from_kwargs( self, kwargs: dict[str, object] diff --git a/db/core/models/utils.py b/db/core/models/utils.py index c9b19d045..e5a0ccf14 100644 --- a/db/core/models/utils.py +++ b/db/core/models/utils.py @@ -1,4 +1,4 @@ -"""Utility classes & functions.""" +"""Utility classes and functions.""" import hashlib import re @@ -20,7 +20,7 @@ class AsyncBaseModel(models.Model): """ - Asynchronous base model, defining extra synchronous & asynchronous utility methods. + Asynchronous base model, defining extra synchronous and asynchronous utility methods. This class is abstract so should not be instantiated or have a table made for it in the database (see https://docs.djangoproject.com/en/stable/topics/db/models/#abstract-base-classes). @@ -84,7 +84,6 @@ def update( field_name: str for field_name in set(kwargs.keys()) - self.get_proxy_field_names(): try: - # noinspection PyUnresolvedReferences self._meta.get_field(field_name) except FieldDoesNotExist: unexpected_kwargs.add(field_name) @@ -112,7 +111,6 @@ def update( update.alters_data: bool = True # type: ignore[attr-defined, misc] - # noinspection SpellCheckingInspection async def aupdate( self, *, diff --git a/exceptions/base.py b/exceptions/base.py index 128a579e2..ce0cb8e73 100644 --- a/exceptions/base.py +++ b/exceptions/base.py @@ -19,7 +19,6 @@ class BaseTeXBotError(BaseException, abc.ABC): """Base exception parent class.""" - # noinspection PyMethodParameters,PyPep8Naming @classproperty @abc.abstractmethod def DEFAULT_MESSAGE(cls) -> str: # noqa: N802 @@ -55,7 +54,6 @@ def __repr__(self) -> str: class BaseErrorWithErrorCode(BaseTeXBotError, abc.ABC): # noqa: N818 """Base class for exception errors that have an error code.""" - # noinspection PyMethodParameters,PyPep8Naming @classproperty @abc.abstractmethod def ERROR_CODE(cls) -> str: # noqa: N802 @@ -65,7 +63,6 @@ def ERROR_CODE(cls) -> str: # noqa: N802 class BaseDoesNotExistError(BaseErrorWithErrorCode, ValueError, abc.ABC): """Exception class to raise when a required Discord entity is missing.""" - # noinspection PyMethodParameters,PyPep8Naming @classproperty def DEPENDENT_COMMANDS(cls) -> frozenset[str]: # noqa: N802 """ @@ -76,7 +73,6 @@ def DEPENDENT_COMMANDS(cls) -> frozenset[str]: # noqa: N802 """ return frozenset() - # noinspection PyMethodParameters,PyPep8Naming @classproperty def DEPENDENT_TASKS(cls) -> frozenset[str]: # noqa: N802 """ @@ -87,7 +83,6 @@ def DEPENDENT_TASKS(cls) -> frozenset[str]: # noqa: N802 """ return frozenset() - # noinspection PyMethodParameters,PyPep8Naming @classproperty def DEPENDENT_EVENTS(cls) -> frozenset[str]: # noqa: N802 """ @@ -98,7 +93,6 @@ def DEPENDENT_EVENTS(cls) -> frozenset[str]: # noqa: N802 """ return frozenset() - # noinspection PyMethodParameters,PyPep8Naming @classproperty @abc.abstractmethod def DOES_NOT_EXIST_TYPE(cls) -> str: # noqa: N802 diff --git a/exceptions/committee_actions.py b/exceptions/committee_actions.py index c85125090..d35328d4f 100644 --- a/exceptions/committee_actions.py +++ b/exceptions/committee_actions.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from collections.abc import Sequence -__all__: "Sequence[str]" = () +__all__: "Sequence[str]" = ("InvalidActionDescriptionError", "InvalidActionTargetError") class InvalidActionTargetError(BaseTeXBotError, RuntimeError): diff --git a/exceptions/config_changes.py b/exceptions/config_changes.py index c2c04a580..e119979c4 100644 --- a/exceptions/config_changes.py +++ b/exceptions/config_changes.py @@ -19,7 +19,6 @@ class ImproperlyConfiguredError(BaseTeXBotError, Exception): """Exception class to raise when environment variables are not correctly provided.""" - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def DEFAULT_MESSAGE(cls) -> str: @@ -29,7 +28,6 @@ def DEFAULT_MESSAGE(cls) -> str: class RestartRequiredDueToConfigChange(BaseTeXBotError, Exception): # noqa: N818 """Exception class to raise when a restart is required to apply config changes.""" - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def DEFAULT_MESSAGE(cls) -> str: diff --git a/exceptions/does_not_exist.py b/exceptions/does_not_exist.py index 79af1670a..f33a33ffc 100644 --- a/exceptions/does_not_exist.py +++ b/exceptions/does_not_exist.py @@ -30,7 +30,6 @@ class RulesChannelDoesNotExistError(BaseTeXBotError, ValueError): """Exception class to raise when the channel, marked as the rules channel, is missing.""" - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def DEFAULT_MESSAGE(cls) -> str: @@ -40,19 +39,16 @@ def DEFAULT_MESSAGE(cls) -> str: class GuildDoesNotExistError(BaseDoesNotExistError): """Exception class to raise when a required Discord guild is missing.""" - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def DEFAULT_MESSAGE(cls) -> str: return "Server with given ID does not exist or is not accessible to the bot." - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def ERROR_CODE(cls) -> str: return "E1011" - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def DOES_NOT_EXIST_TYPE(cls) -> str: @@ -72,19 +68,16 @@ def __init__(self, message: str | None = None, guild_id: int | None = None) -> N class RoleDoesNotExistError(BaseDoesNotExistError, abc.ABC): """Exception class to raise when a required Discord role is missing.""" - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def DEFAULT_MESSAGE(cls) -> str: return f'Role with name "{cls.ROLE_NAME}" does not exist.' - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def DOES_NOT_EXIST_TYPE(cls) -> str: return "role" - # noinspection PyMethodParameters,PyPep8Naming @classproperty @abc.abstractmethod def ROLE_NAME(cls) -> str: # noqa: N802 @@ -106,17 +99,14 @@ def __init__(self, message: str | None = None) -> None: class CommitteeRoleDoesNotExistError(RoleDoesNotExistError): """Exception class to raise when the "Committee" Discord role is missing.""" - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def ERROR_CODE(cls) -> str: return "E1021" - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def DEPENDENT_COMMANDS(cls) -> frozenset[str]: - # noinspection SpellCheckingInspection return frozenset( { "writeroles", @@ -131,7 +121,6 @@ def DEPENDENT_COMMANDS(cls) -> frozenset[str]: }, ) - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def ROLE_NAME(cls) -> str: @@ -141,19 +130,16 @@ def ROLE_NAME(cls) -> str: class CommitteeElectRoleDoesNotExistError(RoleDoesNotExistError): """Exception class to raise when the "Committee-Elect" Discord role is missing.""" - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def ERROR_CODE(cls) -> str: return "E1026" - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def DEPENDENT_COMMANDS(cls) -> frozenset[str]: return frozenset({"handover"}) - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def ROLE_NAME(cls) -> str: @@ -163,17 +149,14 @@ def ROLE_NAME(cls) -> str: class GuestRoleDoesNotExistError(RoleDoesNotExistError): """Exception class to raise when the "Guest" Discord role is missing.""" - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def ERROR_CODE(cls) -> str: return "E1022" - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def DEPENDENT_COMMANDS(cls) -> frozenset[str]: - # noinspection SpellCheckingInspection return frozenset( { "induct", @@ -184,14 +167,11 @@ def DEPENDENT_COMMANDS(cls) -> frozenset[str]: }, ) - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def DEPENDENT_TASKS(cls) -> frozenset[str]: - # noinspection SpellCheckingInspection return frozenset({"send_get_roles_reminders"}) - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def ROLE_NAME(cls) -> str: @@ -201,20 +181,16 @@ def ROLE_NAME(cls) -> str: class MemberRoleDoesNotExistError(RoleDoesNotExistError): """Exception class to raise when the "Member" Discord role is missing.""" - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def ERROR_CODE(cls) -> str: return "E1023" - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def DEPENDENT_COMMANDS(cls) -> frozenset[str]: - # noinspection SpellCheckingInspection return frozenset({"makemember", "ensure-members-inducted", "annual-roles-reset"}) - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def ROLE_NAME(cls) -> str: @@ -224,20 +200,16 @@ def ROLE_NAME(cls) -> str: class ArchivistRoleDoesNotExistError(RoleDoesNotExistError): """Exception class to raise when the "Archivist" Discord role is missing.""" - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def ERROR_CODE(cls) -> str: return "E1024" - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def DEPENDENT_COMMANDS(cls) -> frozenset[str]: - # noinspection SpellCheckingInspection return frozenset({"archive", "increment-year-channels"}) - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def ROLE_NAME(cls) -> str: @@ -247,19 +219,16 @@ def ROLE_NAME(cls) -> str: class ApplicantRoleDoesNotExistError(RoleDoesNotExistError): """Exception class to raise when the "Applicant" Discord role is missing.""" - # noinspection PyMethodParameters @classproperty @override def ERROR_CODE(cls) -> str: return "E1025" - # noinspection PyMethodParameters @classproperty @override def DEPENDENT_COMMANDS(cls) -> frozenset[str]: return frozenset({"make_applicant"}) - # noinspection PyMethodParameters @classproperty @override def ROLE_NAME(cls) -> str: @@ -269,19 +238,16 @@ def ROLE_NAME(cls) -> str: class ChannelDoesNotExistError(BaseDoesNotExistError): """Exception class to raise when a required Discord channel is missing.""" - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def DEFAULT_MESSAGE(cls) -> str: return f'Channel with name "{cls.CHANNEL_NAME}" does not exist.' - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def DOES_NOT_EXIST_TYPE(cls) -> str: return "channel" - # noinspection PyMethodParameters,PyPep8Naming @classproperty @abc.abstractmethod def CHANNEL_NAME(cls) -> str: # noqa: N802 @@ -305,20 +271,16 @@ def __init__(self, message: str | None = None) -> None: class RolesChannelDoesNotExistError(ChannelDoesNotExistError): """Exception class to raise when the "Roles" Discord channel is missing.""" - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def ERROR_CODE(cls) -> str: return "E1031" - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def DEPENDENT_COMMANDS(cls) -> frozenset[str]: - # noinspection SpellCheckingInspection return frozenset({"writeroles"}) - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def CHANNEL_NAME(cls) -> str: @@ -328,20 +290,16 @@ def CHANNEL_NAME(cls) -> str: class GeneralChannelDoesNotExistError(ChannelDoesNotExistError): """Exception class to raise when the "General" Discord channel is missing.""" - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def ERROR_CODE(cls) -> str: return "E1032" - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def DEPENDENT_COMMANDS(cls) -> frozenset[str]: - # noinspection SpellCheckingInspection return frozenset({"induct"}) - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def CHANNEL_NAME(cls) -> str: diff --git a/exceptions/guild.py b/exceptions/guild.py index cf851bb6f..51c3c4ee3 100644 --- a/exceptions/guild.py +++ b/exceptions/guild.py @@ -18,7 +18,6 @@ class DiscordMemberNotInMainGuildError(BaseTeXBotError, ValueError): """Exception class for when no members of your Discord guild have the given user ID.""" - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def DEFAULT_MESSAGE(cls) -> str: @@ -35,13 +34,11 @@ def __init__(self, message: str | None = None, user_id: int | None = None) -> No class EveryoneRoleCouldNotBeRetrievedError(BaseErrorWithErrorCode, ValueError): """Exception class for when the "@everyone" role could not be retrieved.""" - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def DEFAULT_MESSAGE(cls) -> str: return 'The reference to the "@everyone" role could not be correctly retrieved.' - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def ERROR_CODE(cls) -> str: diff --git a/exceptions/messages.py b/exceptions/messages.py index 1505186f8..9a2911fe3 100644 --- a/exceptions/messages.py +++ b/exceptions/messages.py @@ -19,7 +19,6 @@ class InvalidMessagesJSONFileError(ImproperlyConfiguredError): """Exception class to raise when the messages.json file has an invalid structure.""" - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def DEFAULT_MESSAGE(cls) -> str: @@ -36,7 +35,6 @@ def __init__(self, message: str | None = None, dict_key: str | None = None) -> N class MessagesJSONFileMissingKeyError(InvalidMessagesJSONFileError): """Exception class to raise when a key in the messages.json file is missing.""" - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def DEFAULT_MESSAGE(cls) -> str: @@ -60,7 +58,6 @@ def missing_key(self, value: str | None) -> None: class MessagesJSONFileValueError(InvalidMessagesJSONFileError): """Exception class to raise when a key in the messages.json file has an invalid value.""" - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def DEFAULT_MESSAGE(cls) -> str: diff --git a/exceptions/strike.py b/exceptions/strike.py index 8cfe9e599..a499ea8e7 100644 --- a/exceptions/strike.py +++ b/exceptions/strike.py @@ -23,7 +23,6 @@ class StrikeTrackingError(BaseTeXBotError, RuntimeError): and not tracked correctly. """ - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def DEFAULT_MESSAGE(cls) -> str: @@ -38,7 +37,6 @@ class NoAuditLogsStrikeTrackingError(BaseTeXBotError, RuntimeError): and not tracked correctly. """ - # noinspection PyMethodParameters,PyPep8Naming @classproperty @override def DEFAULT_MESSAGE(cls) -> str: diff --git a/main.py b/main.py index 165fe96d0..ec9f69be9 100755 --- a/main.py +++ b/main.py @@ -25,7 +25,6 @@ config.run_setup() intents: discord.Intents = discord.Intents.default() - # noinspection PyDunderSlots,PyUnresolvedReferences intents.members = True bot: TeXBot = TeXBot( diff --git a/pyproject.toml b/pyproject.toml index f9dfe6a3f..4facdcfae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -129,13 +129,16 @@ ignore = [ "CPY", "D206", "D212", + "D300", + "E111", + "E114", + "E117", "FA", - "FAST", + "ICN001", "INP001", "ISC001", "ISC002", "N806", - "PD", "PIE808", "Q000", "Q001", @@ -151,6 +154,16 @@ select = ["ALL", "D204", "D213", "D401"] task-tags = ["BUG", "DONE", "FIXME", "HACK", "IDEA", "ISSUE", "NOBUG", "NOTE", "REQ", "TODO"] [tool.ruff.lint.flake8-import-conventions] +aliases = {} +banned-aliases = { "regex" = [ + "re", +], "numpy" = [ + "np", +], "matplotlib" = [ + "mpl", +], "matplotlib.pyplot" = [ + "plt", +] } banned-from = ["abc", "re", "regex"] [tool.ruff.lint.per-file-ignores] @@ -160,6 +173,7 @@ banned-from = ["abc", "re", "regex"] extend-ignore-names = ["_base_manager", "_default_manager", "_get_wrap_line_width", "_meta"] [tool.ruff.lint.flake8-type-checking] +exempt-modules = [] quote-annotations = true strict = true @@ -212,7 +226,6 @@ plugins.ul-style.style = "asterisk" [tool.uv] default-groups = ["dev", "main"] -no-binary-package = ["TeX-Bot-Py-V2"] no-build = true package = false -required-version = ">=0.5.8" +required-version = ">=0.6.10" diff --git a/utils/command_checks.py b/utils/command_checks.py index 5534ced18..620c99c17 100644 --- a/utils/command_checks.py +++ b/utils/command_checks.py @@ -67,12 +67,10 @@ async def _check(ctx: "TeXBotApplicationContext") -> bool: @classmethod def is_interaction_user_in_main_guild_failure(cls, check: "CheckFailure") -> bool: - # noinspection GrazieInspection - """Whether check failed due to the interaction user not being in your Discord guild.""" + """Whether the check failed due to the user not being in your Discord guild.""" return bool(check.__name__ == cls._check_interaction_user_in_main_guild.__name__) # type: ignore[attr-defined] @classmethod def is_interaction_user_has_committee_role_failure(cls, check: "CheckFailure") -> bool: - # noinspection GrazieInspection - """Whether check failed due to the interaction user not having the committee role.""" + """Whether the check failed due to the user not having the committee role.""" return bool(check.__name__ == cls._check_interaction_user_has_committee_role.__name__) # type: ignore[attr-defined] diff --git a/utils/message_sender_components.py b/utils/message_sender_components.py index 5674cd44f..7f5493f91 100644 --- a/utils/message_sender_components.py +++ b/utils/message_sender_components.py @@ -19,12 +19,29 @@ "ResponseMessageSender", ) +if TYPE_CHECKING: + + class _BaseChannelSendKwargs(TypedDict): + """Type-hint for the required kwargs to the channel-send-function.""" + + content: str + + class _ChannelSendKwargs(_BaseChannelSendKwargs, total=False): + """ + Type-hint-definition for all kwargs to the channel-send-function. + + Includes both required & optional kwargs. + """ + + view: "View" + class MessageSavingSenderComponent(abc.ABC): """ Abstract protocol definition of a sending component that saves the sent-message. - Defines the way to send a provided message content & optional view to the defined endpoint. + Defines the way to send a provided message content + and optional view to the defined endpoint. """ @override @@ -38,7 +55,7 @@ async def _send( """ Subclass implementation of `send()` method. - Implementations should send the provided message content & optional view + Implementations should send the provided message content and optional view to the defined endpoint. """ @@ -85,23 +102,7 @@ def __init__(self, channel: discord.DMChannel | discord.TextChannel) -> None: async def _send( self, content: str, *, view: "View | None" = None ) -> discord.Message | discord.Interaction: - if TYPE_CHECKING: - - class _BaseChannelSendKwargs(TypedDict): - """Type-hint for the required kwargs to the channel-send-function.""" - - content: str - - class ChannelSendKwargs(_BaseChannelSendKwargs, total=False): - """ - Type-hint-definition for all kwargs to the channel-send-function. - - Includes both required & optional kwargs. - """ - - view: "View" - - send_kwargs: ChannelSendKwargs = {"content": content} + send_kwargs: _ChannelSendKwargs = {"content": content} if view: send_kwargs["view"] = view diff --git a/utils/suppress_traceback.py b/utils/suppress_traceback.py index 325e3aa9c..eb679a45c 100644 --- a/utils/suppress_traceback.py +++ b/utils/suppress_traceback.py @@ -23,19 +23,16 @@ class SuppressTraceback: @override def __init__(self) -> None: - # noinspection SpellCheckingInspection """ Initialise a new SuppressTraceback context manager instance. - The current value of `sys.tracebacklimit` is stored for future reference - to revert back to upon exiting the context manager. + The current value of `sys.tracebacklimit` is stored for future reference to revert to + upon exiting the context manager. """ - # noinspection SpellCheckingInspection self.previous_traceback_limit: int | None = getattr(sys, "tracebacklimit", None) def __enter__(self) -> None: """Enter the context manager, suppressing the traceback output.""" - # noinspection SpellCheckingInspection sys.tracebacklimit = 0 def __exit__( @@ -48,10 +45,8 @@ def __exit__( if exc_type is not None or exc_val is not None or exc_tb is not None: return - # noinspection SpellCheckingInspection if hasattr(sys, "tracebacklimit"): if self.previous_traceback_limit is None: del sys.tracebacklimit else: - # noinspection SpellCheckingInspection sys.tracebacklimit = self.previous_traceback_limit diff --git a/utils/tex_bot.py b/utils/tex_bot.py index dcf0c0efd..175765cdf 100644 --- a/utils/tex_bot.py +++ b/utils/tex_bot.py @@ -12,6 +12,7 @@ from exceptions import ( ApplicantRoleDoesNotExistError, ArchivistRoleDoesNotExistError, + ChannelDoesNotExistError, CommitteeElectRoleDoesNotExistError, CommitteeRoleDoesNotExistError, DiscordMemberNotInMainGuildError, @@ -20,12 +21,13 @@ GuestRoleDoesNotExistError, GuildDoesNotExistError, MemberRoleDoesNotExistError, + RoleDoesNotExistError, RolesChannelDoesNotExistError, RulesChannelDoesNotExistError, ) if TYPE_CHECKING: - from collections.abc import Sequence + from collections.abc import Awaitable, Sequence from logging import Logger from typing import Final, NoReturn @@ -70,7 +72,6 @@ async def close(self) -> "NoReturn": # type: ignore[misc] logger.info("TeX-Bot manually terminated.") - # noinspection PyPep8Naming @property def EXIT_WAS_DUE_TO_KILL_COMMAND(self) -> bool: # noqa: N802 """Return whether the TeX-Bot exited due to the kill command being used.""" @@ -533,3 +534,19 @@ async def fetch_log_channel(self) -> discord.TextChannel: raise RuntimeError(LOG_CHANNEL_NOT_FOUND_MESSAGE) return full_webhook.channel + + @classmethod + async def get_mention_string( + cls, + channel_coroutine: "Awaitable[discord.TextChannel | discord.Role]", + default: str | None = None, + ) -> str: + """Return the mention string for a given role/channel, even if it does not exist.""" + try: + return (await channel_coroutine).mention + except RoleDoesNotExistError as e: + return f"@{e.ROLE_NAME}" if default is None else default + except ChannelDoesNotExistError as e: + return f"**`#{e.CHANNEL_NAME}`**" if default is None else default + except RulesChannelDoesNotExistError: + return "**`#welcome`**" if default is None else default diff --git a/utils/tex_bot_base_cog.py b/utils/tex_bot_base_cog.py index 22b5cd963..5b3ee7566 100644 --- a/utils/tex_bot_base_cog.py +++ b/utils/tex_bot_base_cog.py @@ -8,7 +8,7 @@ import discord from discord import Cog -from exceptions import CommitteeRoleDoesNotExistError, DiscordMemberNotInMainGuildError +from exceptions import DiscordMemberNotInMainGuildError from exceptions.base import ( BaseDoesNotExistError, ) @@ -119,14 +119,10 @@ async def send_error( construct_error_message: str = ":warning:There was an error" if error_code: - # noinspection PyUnusedLocal - committee_mention: str = "committee" - - with contextlib.suppress(CommitteeRoleDoesNotExistError): - committee_mention = (await bot.committee_role).mention - construct_error_message = ( - f"**Contact a {committee_mention} member, referencing error code: " + f"**Contact a { + await bot.get_mention_string(bot.committee_role, default='committee') + } member, referencing error code: " f"{error_code}**\n" ) + construct_error_message @@ -180,7 +176,6 @@ async def autocomplete_get_text_channels( try: main_guild: discord.Guild = ctx.bot.main_guild - # noinspection PyUnusedLocal channel_permissions_limiter: MentionableMember = await ctx.bot.guest_role except BaseDoesNotExistError: return set()