From 321f8eaa0b42bcc4b36e93c5335a3700c5da54ea Mon Sep 17 00:00:00 2001 From: MysticMia <65396931+MysticMia@users.noreply.github.com> Date: Fri, 16 May 2025 04:35:09 +0200 Subject: [PATCH 01/26] Expand docs, move async Reminder error handling, and replace Tags cooldown variable from epoch int to datetime object. Expand the documentation for try_store_vc_rename. Move error handling from parse_and_create_reminder in _parse_reminder_time to remindme command function. --- .../customvcs/channel_rename_tracker.py | 16 +++- extensions/reminders/cogs/reminders.py | 75 ++++++++++++++- .../reminders/objects/reminderobject.py | 96 ++++--------------- extensions/tags/cogs/tags.py | 16 ++-- 4 files changed, 116 insertions(+), 87 deletions(-) diff --git a/extensions/customvcs/channel_rename_tracker.py b/extensions/customvcs/channel_rename_tracker.py index 3b23fc5..bc84d6f 100644 --- a/extensions/customvcs/channel_rename_tracker.py +++ b/extensions/customvcs/channel_rename_tracker.py @@ -21,11 +21,21 @@ def try_store_vc_rename(channel_id: int, max_rename_limit: int = 2) -> None | in """ Store a new voice channel rename in the storage dictionary. + .. note:: + + Returns a unix timestamp, so it can be used in discord + timestamps like . This timestamp is for the + ratelimit, which typically resets 10 minutes after the first + channel edit. + :param channel_id: The voice channel that will be renamed. - :param max_rename_limit: (optional) The amount of times to allow the channel to be renamed in 10 minutes. + :param max_rename_limit: (optional) The amount of times to allow the + channel to be renamed in 10 minutes. - :return: ``None`` if the voice channel's rename timestamp was successfully stored; or the channel's first - rename timestamp if it was edited more than *max_rename_limit* times in the past 10 minutes. + :return: ``None`` if the voice channel's rename timestamp was + successfully stored; or the channel's first rename timestamp if + it was edited more than *max_rename_limit* times in the past + 10 minutes. """ if channel_id in recently_renamed_vcs: # if there have been 2 or more renames for this channel diff --git a/extensions/reminders/cogs/reminders.py b/extensions/reminders/cogs/reminders.py index 52566e9..fe966f5 100644 --- a/extensions/reminders/cogs/reminders.py +++ b/extensions/reminders/cogs/reminders.py @@ -27,7 +27,80 @@ async def remindme(self, itx: discord.Interaction, reminder_datetime: str, remin "Please keep reminder text below 1500 characters... Otherwise I can't send you a message about it!", ephemeral=True) return - await parse_and_create_reminder(itx, reminder_datetime, reminder) + + cmd_mention_help = itx.client.get_command_mention("help") + try: + await parse_and_create_reminder(itx, reminder_datetime, reminder) + except OverflowError as ex: + await itx.response.send_message(str(ex), ephemeral=True) + except UnixTimestampInPastException as ex: + timestamp_unix = int(ex.distance.timestamp()) + await itx.response.send_message( + "Couldn't make new reminder: \n" + "> Your message was interpreted as a unix timestamp, but this " + "timestamp would be before the current time!\n" + f"Interpreted timestamp: {timestamp_unix} " + f"(, ).\n" + # round up (+ 0.5) to ensure t=0.99 > t=0.80 + f"Current time: {int(ex.creation_time.timestamp() + 0.5)} " + f"(, " + f").\n" + f"For more info, use {cmd_mention_help} `page:113`.", + ephemeral=True + ) + return + except MalformedISODateTimeException as ex: + await itx.response.send_message( + f"Couldn't make new reminder:\n> {str(ex.inner_exception)}\n" + "You can only use a format like [number][letter], or yyyy-mm-ddThh:mm:ss+0000. Some examples:\n" + ' "3mo 0.5d", "in 2 hours, 3.5 mins", "1 year and 3 seconds", "3day4hour", "4d1m", ' + '"2023-12-31T23:59+0100", "\n' + "Words like \"in\" and \"and\" are ignored, so you can use those for readability if you'd like.\n" + ' year = y, year, years\n' + ' month = mo, month, months\n' + ' week = w, week, weeks\n' + ' day = d, day, days\n' + ' hour = h, hour, hours\n' + ' minute = m, min, mins, minute, minutes\n' + ' second = s, sec, secs, second, seconds\n' + f'For more info, use {cmd_mention_help} `page:113`.', + ephemeral=True) + return + except TimestampParseException as ex: + await itx.response.send_message( + f"Couldn't make new reminder:\n> {str(ex.inner_exception)}\n\n" + "You can make a reminder for days in advance, like so: \"4d12h\", \"4day 12hours\" or " + "\"in 3 minutes and 2 seconds\"\n" + "You can also use ISO8601 format, like '2023-12-31T23:59+0100', or just '2023-12-31'\n" + "Or you can use Unix Epoch format: the amount of seconds since January 1970: '1735155750" + "\n" + "If you give a time but not a timezone, I don't want you to get reminded at the wrong time, " + "so I'll say something went wrong.\n" + f"For more info, use {cmd_mention_help} `page:113`.", + ephemeral=True + ) + return + except MissingQuantityException as ex: + await itx.response.send_message( + f"Couldn't make new reminder:\n> {str(ex)}\n\n" + f"Be sure you start the reminder time with a number like \"4 days\".\n" + f"For more info, use {cmd_mention_help} `page:113`.", + ephemeral=True + ) + return + except MissingUnitException as ex: + await itx.response.send_message( + f"Couldn't make new reminder:\n> {str(ex)}\n\n" + f"Be sure you end the reminder time with a unit like \"4 days\".\n" + f"If you intended to use a unix timestamp instead, make sure your timestamp is correct. Any number" + f"below 1000000 is parsed in the \"1 day 2 hours\" format, which means not providing a unit will" + f"give this error. Note: a unix timestamp of 1000000 is 20 Jan 1970 ( = )\n" + f"For more info, use {cmd_mention_help} `page:113`.", + ephemeral=True + ) + return + except ReminderTimeSelectionMenuTimeOut: + return @app_commands.command(name="reminders", description="Check your list of reminders!") @app_commands.describe(item="Which reminder would you like to know more about? (use reminder-ID)") diff --git a/extensions/reminders/objects/reminderobject.py b/extensions/reminders/objects/reminderobject.py index 7003c3e..b2dd1b4 100644 --- a/extensions/reminders/objects/reminderobject.py +++ b/extensions/reminders/objects/reminderobject.py @@ -134,7 +134,7 @@ async def send_reminder(self): async def _handle_reminder_timestamp_parsing( itx: discord.Interaction, reminder_datetime: str -) -> (datetime, discord.Interaction): +) -> tuple[datetime, discord.Interaction]: # todo: docstring # validate format # note: "t" here is lowercase because the reminder_datetime string # gets lowercased... @@ -184,6 +184,8 @@ async def _handle_reminder_timestamp_parsing( raise ValueError( f"Incorrect format given! I could not convert {reminder_datetime} to format {timestamp_format}") + # todo: move the above code to a new function + distance = timestamp # clarify datetime timezone if necessary if mode == TimestampFormats.DateNoTime: @@ -319,79 +321,21 @@ async def parse_and_create_reminder( if len(user_reminders) > 50: cmd_mention = itx.client.get_command_mention("reminder reminders") cmd_mention1 = itx.client.get_command_mention("reminder remove") - await itx.response.send_message(f"Please don't make more than 50 reminders. Use {cmd_mention} to see " - f"your reminders, and use {cmd_mention1} `item: ` to remove a reminder", - ephemeral=True) - return - - cmd_mention_help = itx.client.get_command_mention("help") - try: - distance, creation_time = await _parse_reminder_time(itx, reminder_datetime) - except UnixTimestampInPastException as ex: - timestamp_unix = int(ex.distance.timestamp()) - await itx.response.send_message( - "Couldn't make new reminder: \n" - "> Your message was interpreted as a unix timestamp, but this timestamp would be before " - "the current time!\n" - f"Interpreted timestamp: {timestamp_unix} " - f"(, ).\n" - f"Current time: {int(ex.creation_time.timestamp() + 0.5)} " # round up to ensure t=0.99 > t=0.80 - f"(, ).\n" - f"For more info, use {cmd_mention_help} `page:113`.", - ephemeral=True - ) - return - except MalformedISODateTimeException as ex: - await itx.response.send_message( - f"Couldn't make new reminder:\n> {str(ex.inner_exception)}\n" - "You can only use a format like [number][letter], or yyyy-mm-ddThh:mm:ss+0000. Some examples:\n" - ' "3mo 0.5d", "in 2 hours, 3.5 mins", "1 year and 3 seconds", "3day4hour", "4d1m", ' - '"2023-12-31T23:59+0100", "\n' - "Words like \"in\" and \"and\" are ignored, so you can use those for readability if you'd like.\n" - ' year = y, year, years\n' - ' month = mo, month, months\n' - ' week = w, week, weeks\n' - ' day = d, day, days\n' - ' hour = h, hour, hours\n' - ' minute = m, min, mins, minute, minutes\n' - ' second = s, sec, secs, second, seconds\n' - f'For more info, use {cmd_mention_help} `page:113`.', - ephemeral=True) - return - except TimestampParseException as ex: - await itx.response.send_message( - f"Couldn't make new reminder:\n> {str(ex.inner_exception)}\n\n" - "You can make a reminder for days in advance, like so: \"4d12h\", \"4day 12hours\" or " - "\"in 3 minutes and 2 seconds\"\n" - "You can also use ISO8601 format, like '2023-12-31T23:59+0100', or just '2023-12-31'\n" - "Or you can use Unix Epoch format: the amount of seconds since January 1970: '1735155750" - "\n" - "If you give a time but not a timezone, I don't want you to get reminded at the wrong time, " - "so I'll say something went wrong.\n" - f"For more info, use {cmd_mention_help} `page:113`.", - ephemeral=True + message = ( + f"Please don't make more than 50 reminders. Use {cmd_mention} to " + f"see your reminders, and use {cmd_mention1} `item: ` to remove " + f"a reminder." ) - return - except MissingQuantityException as ex: - await itx.response.send_message( - f"Couldn't make new reminder:\n> {str(ex)}\n\n" - f"Be sure you start the reminder time with a number like \"4 days\".\n" - f"For more info, use {cmd_mention_help} `page:113`.", - ephemeral=True - ) - return - except MissingUnitException as ex: - await itx.response.send_message( - f"Couldn't make new reminder:\n> {str(ex)}\n\n" - f"Be sure you end the reminder time with a unit like \"4 days\".\n" - f"If you intended to use a unix timestamp instead, make sure your timestamp is correct. Any number" - f"below 1000000 is parsed in the \"1 day 2 hours\" format, which means not providing a unit will" - f"give this error. Note: a unix timestamp of 1000000 is 20 Jan 1970 ( = )\n" - f"For more info, use {cmd_mention_help} `page:113`.", - ephemeral=True - ) - return - except ReminderTimeSelectionMenuTimeOut: - return - - await _create_reminder(itx, distance, creation_time, reminder, user_reminders, False) + raise OverflowError(message) + + distance, creation_time = await _parse_reminder_time( + itx, reminder_datetime) + + await _create_reminder( + itx, + distance, + creation_time, + reminder, + user_reminders, + False + ) diff --git a/extensions/tags/cogs/tags.py b/extensions/tags/cogs/tags.py index 41e9aa5..3c62bb0 100644 --- a/extensions/tags/cogs/tags.py +++ b/extensions/tags/cogs/tags.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import datetime, timedelta # ^ to make report tag auto-trigger at most once every 15 minutes import discord @@ -24,7 +24,7 @@ # to prevent excessive spamming when multiple people mention staff. A sorta cooldown -report_message_reminder_unix = 0 # int(datetime.now().timestamp()) +report_message_reminder = datetime.min def _get_enabled_tag_ids(itx) -> set[str]: @@ -99,8 +99,10 @@ def __init__(self, client: Bot): @commands.Cog.listener() async def on_message(self, message: discord.Message): - global report_message_reminder_unix - if message.guild is None or message.author.bot: + global report_message_reminder + if not self.client.is_module_enabled(message.guild, ModuleKeys.tags): + return + if message.author.bot: return staff_role_list: list[discord.Role] @@ -117,11 +119,11 @@ async def on_message(self, message: discord.Message): if any(staff_role_mentions in message.content for staff_role_mentions in staff_role_mentions): - time_now = int(datetime.now().timestamp()) # get time in unix - if time_now - report_message_reminder_unix > 900: # 15 minutes + time_now = datetime.now() + if time_now - report_message_reminder > timedelta(minutes=15): tag = create_report_info_tag(ticket_channel) await tag.send_to_channel(message.channel) - report_message_reminder_unix = time_now + report_message_reminder = time_now @app_commands.command(name="tag", description="Look up something through a tag") From 5b3b9e21d061fe020b0e79a2b65de4a660c5a8ea Mon Sep 17 00:00:00 2001 From: MysticMia <65396931+MysticMia@users.noreply.github.com> Date: Fri, 16 May 2025 05:01:50 +0200 Subject: [PATCH 02/26] Replace all Interaction type function annotations Interaction[Bot] --- extensions/addons/cogs/funaddons.py | 2 +- extensions/addons/cogs/otheraddons.py | 10 +++++----- extensions/addons/cogs/searchaddons.py | 6 ++++-- extensions/changechannel/cogs/changechannel.py | 6 ++++-- extensions/compliments/cogs/compliments.py | 2 +- extensions/compliments/views/confirmpronouns.py | 12 +++++++----- extensions/crashhandling/cogs/crashhandling.py | 2 +- extensions/customvcs/cogs/vctables.py | 8 ++++---- extensions/emojistats/cogs/emojistats.py | 4 ++-- extensions/emojistats/cogs/stickerstats.py | 4 ++-- extensions/getmemberdata/cogs/memberdata.py | 2 +- extensions/help/cogs/helpcommand.py | 9 +++++---- extensions/help/views/helppage.py | 11 +++++++---- extensions/nameusage/cogs/nameusage.py | 5 +++-- extensions/nameusage/views/pageview.py | 7 ++++--- extensions/reminders/cogs/reminders.py | 7 ++++--- extensions/reminders/objects/reminderobject.py | 8 ++++---- extensions/reminders/views/timeofdayselection.py | 3 ++- extensions/settings/cogs/settings.py | 12 ++++++------ extensions/staffaddons/cogs/staffaddons.py | 2 +- extensions/tags/cogs/tags.py | 4 ++-- extensions/tags/tags.py | 6 +++--- extensions/termdictionary/cogs/termdictionary.py | 13 +++++++------ .../termdictionary/views/dictionaryapi_pageview.py | 9 +++++---- .../views/urbandictionary_pageview.py | 6 ++++-- extensions/testing_commands/cogs/testingcommands.py | 8 ++++---- extensions/todolist/cogs/todolist.py | 4 +++- extensions/toneindicator/cogs/toneindicator.py | 3 ++- extensions/watchlist/cogs/watchlist.py | 6 +++--- resources/views/generics.py | 4 +++- 30 files changed, 104 insertions(+), 81 deletions(-) diff --git a/extensions/addons/cogs/funaddons.py b/extensions/addons/cogs/funaddons.py index 636af37..b6fe50f 100644 --- a/extensions/addons/cogs/funaddons.py +++ b/extensions/addons/cogs/funaddons.py @@ -235,7 +235,7 @@ async def on_message(self, message: discord.Message): advanced="Roll more advanced! example: 1d20+3*2d4. Overwrites dice/faces given; " "'help' for more") async def roll( - self, itx: discord.Interaction, + self, itx: discord.Interaction[Bot], dice: app_commands.Range[int, 1, 999999], faces: app_commands.Range[int, 1, 999999], public: bool = False, mod: int | None = None, advanced: str | None = None diff --git a/extensions/addons/cogs/otheraddons.py b/extensions/addons/cogs/otheraddons.py index 2202c57..22e3d9b 100644 --- a/extensions/addons/cogs/otheraddons.py +++ b/extensions/addons/cogs/otheraddons.py @@ -157,7 +157,7 @@ def _get_emoji_from_str( return emoji -async def _unit_autocomplete(itx: discord.Interaction, current: str): +async def _unit_autocomplete(itx: discord.Interaction[Bot], current: str): options = conversion_rates.copy() if itx.namespace.mode not in options: return [] # user hasn't selected a mode yet. @@ -172,7 +172,7 @@ async def _unit_autocomplete(itx: discord.Interaction, current: str): ][:25] -async def _role_autocomplete(itx: discord.Interaction, current: str): +async def _role_autocomplete(itx: discord.Interaction[Bot], current: str): """Autocomplete for /remove-role command.""" role_options = { 1126160553145020460: ("Hide Politics channel role", "NPA"), # NPA @@ -227,7 +227,7 @@ async def on_message(self, message: discord.Message): @app_commands.rename(from_unit='to') @app_commands.autocomplete(to_unit=_unit_autocomplete) async def convert_unit( - self, itx: discord.Interaction, mode: str, from_unit: str, value: float, to_unit: str, public: bool = False + self, itx: discord.Interaction[Bot], mode: str, from_unit: str, value: float, to_unit: str, public: bool = False ): rates = conversion_rates.copy() if mode not in rates: @@ -353,7 +353,7 @@ async def add_poll_reactions( @app_commands.command(name="get_rina_command_mention", description="Sends a hidden command mention for your command") @app_commands.describe(command="Command to get a mention for (with/out slash)") - async def find_command_mention_itx(self, itx: discord.Interaction, command: str): + async def find_command_mention_itx(self, itx: discord.Interaction[Bot], command: str): command = command.removeprefix("/").lower() try: app_commands.commands.validate_name(command) @@ -379,7 +379,7 @@ async def find_command_mention_itx(self, itx: discord.Interaction, command: str) @app_commands.describe(role_name="The name of the role to remove") @app_commands.autocomplete(role_name=_role_autocomplete) @module_enabled_check(ModuleKeys.remove_role_command) - async def remove_role(self, itx: discord.Interaction, role_name: str): + async def remove_role(self, itx: discord.Interaction[Bot], role_name: str): itx.user: discord.Member # noqa # It shouldn't be a discord.User cause the app_command check # prevents DMs. diff --git a/extensions/addons/cogs/searchaddons.py b/extensions/addons/cogs/searchaddons.py index 6ee88c6..bf1dfdd 100644 --- a/extensions/addons/cogs/searchaddons.py +++ b/extensions/addons/cogs/searchaddons.py @@ -5,6 +5,8 @@ import discord.app_commands as app_commands import discord.ext.commands as commands +from resources.customs import Bot + from extensions.addons.equaldexregion import EqualDexRegion from extensions.addons.views.equaldex_additionalinfo import EqualDexAdditionalInfo from extensions.addons.views.math_sendpublicbutton import SendPublicButtonMath @@ -20,7 +22,7 @@ def __init__(self): @app_commands.command(name="equaldex", description="Find info about LGBTQ+ laws in different countries!") @app_commands.describe(country_id="What country do you want to know more about? (GB, US, AU, etc.)") - async def equaldex(self, itx: discord.Interaction, country_id: str): + async def equaldex(self, itx: discord.Interaction[Bot], country_id: str): illegal_characters = "" for char in country_id.lower(): if char not in "abcdefghijklmnopqrstuvwxyz": @@ -111,7 +113,7 @@ async def equaldex(self, itx: discord.Interaction, country_id: str): await itx.response.send_message(embed=embed, view=view, ephemeral=True) @app_commands.command(name="math", description="Ask Wolfram Alpha a question") - async def math(self, itx: discord.Interaction, query: str): + async def math(self, itx: discord.Interaction[Bot], query: str): # todo: shorten function / split, and re-investigate the API docs to see if I can parse stuff better await itx.response.defer(ephemeral=True) if query.lower() in ["help", "what is this", "what is this?"]: diff --git a/extensions/changechannel/cogs/changechannel.py b/extensions/changechannel/cogs/changechannel.py index ee9c6bb..041cf5f 100644 --- a/extensions/changechannel/cogs/changechannel.py +++ b/extensions/changechannel/cogs/changechannel.py @@ -2,8 +2,10 @@ import discord.app_commands as app_commands import discord.ext.commands as commands -from extensions.settings.objects import ModuleKeys from resources.checks import module_enabled_check +from resources.customs import Bot + +from extensions.settings.objects import ModuleKeys class ChangeChannel(commands.Cog): @@ -14,7 +16,7 @@ class ChangeChannel(commands.Cog): @module_enabled_check(ModuleKeys.change_channel) async def changechannel( self, - itx: discord.Interaction, + itx: discord.Interaction[Bot], destination: discord.TextChannel ): itx.response: discord.InteractionResponse # noqa diff --git a/extensions/compliments/cogs/compliments.py b/extensions/compliments/cogs/compliments.py index 9b8d7b8..afbc8f9 100644 --- a/extensions/compliments/cogs/compliments.py +++ b/extensions/compliments/cogs/compliments.py @@ -145,7 +145,7 @@ async def _choose_and_send_compliment( roles=False, replied_user=False)) -async def _send_confirm_gender_modal(client: Bot, itx: discord.Interaction, user: discord.User | discord.Member): +async def _send_confirm_gender_modal(client: Bot, itx: discord.Interaction[Bot], user: discord.User | discord.Member): # Define a simple View that gives us a confirmation menu view = ConfirmPronounsView(timeout=60) await itx.response.send_message( diff --git a/extensions/compliments/views/confirmpronouns.py b/extensions/compliments/views/confirmpronouns.py index 94b6e50..9b8f0ce 100644 --- a/extensions/compliments/views/confirmpronouns.py +++ b/extensions/compliments/views/confirmpronouns.py @@ -1,5 +1,7 @@ import discord +from resources.customs import Bot + class ConfirmPronounsView(discord.ui.View): def __init__(self, timeout=None): @@ -11,31 +13,31 @@ def __init__(self, timeout=None): # stop the View from listening to more input. # We also send the user an ephemeral message that we're confirming their choice. @discord.ui.button(label='She/Her', style=discord.ButtonStyle.green) - async def feminine(self, itx: discord.Interaction, _button: discord.ui.Button): + async def feminine(self, itx: discord.Interaction[Bot], _button: discord.ui.Button): self.value = "she/her" await itx.response.edit_message(content='Selected She/Her pronouns for compliment', view=None) self.stop() @discord.ui.button(label='He/Him', style=discord.ButtonStyle.green) - async def masculine(self, itx: discord.Interaction, _button: discord.ui.Button): + async def masculine(self, itx: discord.Interaction[Bot], _button: discord.ui.Button): self.value = "he/him" await itx.response.edit_message(content='Selected He/Him pronouns for the compliment', view=None) self.stop() @discord.ui.button(label='They/Them', style=discord.ButtonStyle.green) - async def enby_them(self, itx: discord.Interaction, _button: discord.ui.Button): + async def enby_them(self, itx: discord.Interaction[Bot], _button: discord.ui.Button): self.value = "they/them" await itx.response.edit_message(content='Selected They/Them pronouns for the compliment', view=None) self.stop() @discord.ui.button(label='It/Its', style=discord.ButtonStyle.green) - async def enby_its(self, itx: discord.Interaction, _button: discord.ui.Button): + async def enby_its(self, itx: discord.Interaction[Bot], _button: discord.ui.Button): self.value = "it/its" await itx.response.edit_message(content='Selected It/Its pronouns for the compliment', view=None) self.stop() @discord.ui.button(label='Unisex/Unknown', style=discord.ButtonStyle.grey) - async def unisex(self, itx: discord.Interaction, _button: discord.ui.Button): + async def unisex(self, itx: discord.Interaction[Bot], _button: discord.ui.Button): self.value = "unisex" await itx.response.edit_message(content='Selected Unisex/Unknown gender for the compliment', view=None) self.stop() diff --git a/extensions/crashhandling/cogs/crashhandling.py b/extensions/crashhandling/cogs/crashhandling.py index 67ccea6..08b912c 100644 --- a/extensions/crashhandling/cogs/crashhandling.py +++ b/extensions/crashhandling/cogs/crashhandling.py @@ -101,7 +101,7 @@ async def _send_crash_message( ) -async def _reply(itx: discord.Interaction, message: str) -> None: +async def _reply(itx: discord.Interaction[Bot], message: str) -> None: """ A helper function to handle replying to an interaction by either using :py:func:`~discord.Webhook.send` or :py:func:`~discord.InteractionResponse.send_message`, depending on if a response has been responded to already. diff --git a/extensions/customvcs/cogs/vctables.py b/extensions/customvcs/cogs/vctables.py index a7e2326..5fec402 100644 --- a/extensions/customvcs/cogs/vctables.py +++ b/extensions/customvcs/cogs/vctables.py @@ -372,7 +372,7 @@ async def vctable_help(self, itx: discord.Interaction): discord.app_commands.Choice(name='Check owners', value=3) ]) @module_enabled_check(ModuleKeys.vc_tables) - async def edit_vctable_owners(self, itx: discord.Interaction, mode: int, user: discord.Member | None = None): + async def edit_vctable_owners(self, itx: discord.Interaction[Bot], mode: int, user: discord.Member | None = None): if itx.user == user and mode != 3: if mode == 1: await itx.response.send_message("You can't set yourself as owner!", ephemeral=True) @@ -449,7 +449,7 @@ async def edit_vctable_owners(self, itx: discord.Interaction, mode: int, user: d discord.app_commands.Choice(name='Check speakers', value=3) ]) @module_enabled_check(ModuleKeys.vc_tables) - async def edit_vctable_speakers(self, itx: discord.Interaction, mode: int, user: discord.Member | None = None): + async def edit_vctable_speakers(self, itx: discord.Interaction[Bot], mode: int, user: discord.Member | None = None): if itx.user == user and mode != 3: await itx.response.send_message("You can't edit your own speaking permissions!", ephemeral=True) return @@ -533,7 +533,7 @@ async def edit_vctable_speakers(self, itx: discord.Interaction, mode: int, user: discord.app_commands.Choice(name='Check participants', value=3) ]) @module_enabled_check(ModuleKeys.vc_tables) - async def edit_vctable_participants(self, itx: discord.Interaction, mode: int, user: discord.Member | None = None): + async def edit_vctable_participants(self, itx: discord.Interaction[Bot], mode: int, user: discord.Member | None = None): if itx.user == user and mode != 3: await itx.response.send_message("You can't edit your own participation permissions!", ephemeral=True) return @@ -627,7 +627,7 @@ async def edit_vctable_participants(self, itx: discord.Interaction, mode: int, u ]) @module_enabled_check(ModuleKeys.vc_tables) async def edit_vctable_muted_participants( - self, itx: discord.Interaction, mode: int, user: discord.Member | None = None + self, itx: discord.Interaction[Bot], mode: int, user: discord.Member | None = None ): if itx.user == user and mode != 3: await itx.response.send_message("You can't " + "un" * (mode == 2) + "mute yourself!", ephemeral=True) diff --git a/extensions/emojistats/cogs/emojistats.py b/extensions/emojistats/cogs/emojistats.py index 545f9af..fcb8f67 100644 --- a/extensions/emojistats/cogs/emojistats.py +++ b/extensions/emojistats/cogs/emojistats.py @@ -145,7 +145,7 @@ async def on_raw_reaction_add( @app_commands.rename(emoji_name="emoji") @app_commands.describe(emoji_name="Emoji you want to get data of") @app_commands.check(not_in_dms_check) - async def get_emoji_data(self, itx: discord.Interaction, emoji_name: str): + async def get_emoji_data(self, itx: discord.Interaction[Bot], emoji_name: str): if ":" in emoji_name: emoji_name = emoji_name.strip().split(":")[2][:-1] emoji_id = emoji_name @@ -218,7 +218,7 @@ async def get_emoji_data(self, itx: discord.Interaction, emoji_name: str): @app_commands.check(not_in_dms_check) async def get_unused_emojis( self, - itx: discord.Interaction, + itx: discord.Interaction[Bot], public: bool = False, max_results: int = 10, used_max: int = sys.maxsize, diff --git a/extensions/emojistats/cogs/stickerstats.py b/extensions/emojistats/cogs/stickerstats.py index b8a452f..2e04ec6 100644 --- a/extensions/emojistats/cogs/stickerstats.py +++ b/extensions/emojistats/cogs/stickerstats.py @@ -54,7 +54,7 @@ async def on_message(self, message: discord.Message): @app_commands.rename(sticker_name="sticker") @app_commands.describe(sticker_name="Sticker you want to get data of") @app_commands.check(not_in_dms_check) - async def get_sticker_data(self, itx: discord.Interaction, sticker_name: str): + async def get_sticker_data(self, itx: discord.Interaction[Bot], sticker_name: str): if ":" in sticker_name: # idk why people would, but idk the format for stickers so ill just assume or something idk sticker_name = sticker_name.strip().split(":")[1][:-1] @@ -96,7 +96,7 @@ async def get_sticker_data(self, itx: discord.Interaction, sticker_name: str): used_max="Up to how many times may the sticker have been used? (default: 10)") @app_commands.check(not_in_dms_check) async def get_unused_stickers( - self, itx: discord.Interaction, public: bool = False, + self, itx: discord.Interaction[Bot], public: bool = False, max_results: int = 10, used_max: int = sys.maxsize ): await itx.response.defer(ephemeral=not public) diff --git a/extensions/getmemberdata/cogs/memberdata.py b/extensions/getmemberdata/cogs/memberdata.py index 3f46224..f65d0ba 100644 --- a/extensions/getmemberdata/cogs/memberdata.py +++ b/extensions/getmemberdata/cogs/memberdata.py @@ -63,7 +63,7 @@ async def on_member_update(self, before, after): public="Send the output to everyone in the channel") @app_commands.check(not_in_dms_check) async def get_member_data( - self, itx: discord.Interaction, lower_bound: str, upper_bound: str = None, doubles: bool = False, + self, itx: discord.Interaction[Bot], lower_bound: str, upper_bound: str = None, doubles: bool = False, public: bool = False ): # todo: split function into multiple subfunctions. diff --git a/extensions/help/cogs/helpcommand.py b/extensions/help/cogs/helpcommand.py index ceadb5e..f80190f 100644 --- a/extensions/help/cogs/helpcommand.py +++ b/extensions/help/cogs/helpcommand.py @@ -2,6 +2,7 @@ import discord.app_commands as app_commands import discord.ext.commands as commands +from resources.customs import Bot from resources.utils import is_admin from resources.utils.stringhelper import replace_string_command_mentions @@ -10,7 +11,7 @@ from extensions.help.views.helppage import HelpPageView -async def send_help_menu(itx: discord.Interaction, requested_page: int = FIRST_PAGE): +async def send_help_menu(itx: discord.Interaction[Bot], requested_page: int = FIRST_PAGE): if requested_page not in help_pages: min_index, max_index = get_nearest_help_pages_from_page(requested_page, list(help_pages)) relative_page_location_details = f"(nearest pages are `{min_index}` and `{max_index}`)." @@ -41,7 +42,7 @@ async def send_help_menu(itx: discord.Interaction, requested_page: int = FIRST_P ephemeral=True) -async def _help_page_autocomplete(itx: discord.Interaction, current: str) -> list[app_commands.Choice[int]]: +async def _help_page_autocomplete(itx: discord.Interaction[Bot], current: str) -> list[app_commands.Choice[int]]: results = [] added_pages = [] @@ -98,11 +99,11 @@ def __init__(self): @app_commands.command(name="help", description="A help command to learn more about me!") @app_commands.describe(page="What page do you want to jump to? (useful if sharing commands)") @app_commands.autocomplete(page=_help_page_autocomplete) - async def help(self, itx: discord.Interaction, page: int = FIRST_PAGE): + async def help(self, itx: discord.Interaction[Bot], page: int = FIRST_PAGE): await send_help_menu(itx, page) @app_commands.command(name="commands", description="A help command to learn more about me!") @app_commands.describe(page="What page do you want to jump to? (useful if sharing commands)") @app_commands.autocomplete(page=_help_page_autocomplete) - async def commands(self, itx: discord.Interaction, page: int = FIRST_PAGE): + async def commands(self, itx: discord.Interaction[Bot], page: int = FIRST_PAGE): await send_help_menu(itx, page) diff --git a/extensions/help/views/helppage.py b/extensions/help/views/helppage.py index c68a37d..1896a60 100644 --- a/extensions/help/views/helppage.py +++ b/extensions/help/views/helppage.py @@ -1,6 +1,7 @@ import discord import random # for random help page jump page number placeholder +from resources.customs import Bot from resources.modals.generics import SingleLineModal from resources.utils.stringhelper import replace_string_command_mentions from resources.views.generics import PageView @@ -9,8 +10,10 @@ from extensions.help.helppage import HelpPage -class HelpPageView(PageView): - async def update_page(self, itx: discord.Interaction, view: PageView) -> None: +class HelpPageView(PageView): # todo: add "override" to all overriding funcs + async def update_page( + self, itx: discord.Interaction[Bot], view: PageView + ) -> None: page_key = list(self.pages)[self.page] embed = generate_help_page_embed(self.pages[page_key], page_key, itx.client) await itx.response.edit_message( @@ -20,13 +23,13 @@ async def update_page(self, itx: discord.Interaction, view: PageView) -> None: # region buttons @discord.ui.button(emoji="📋", style=discord.ButtonStyle.gray) - async def go_to_index(self, itx: discord.Interaction, _: discord.ui.Button): + async def go_to_index(self, itx: discord.Interaction[Bot], _: discord.ui.Button): self.page = 2 self.update_button_colors() await self.update_page(itx, self) @discord.ui.button(emoji="🔢", style=discord.ButtonStyle.gray) - async def jump_to_page(self, itx: discord.Interaction, _: discord.ui.Button): + async def jump_to_page(self, itx: discord.Interaction[Bot], _: discord.ui.Button): help_page_indexes = list(self.pages) jump_page_modal = SingleLineModal( "Jump to a help page", diff --git a/extensions/nameusage/cogs/nameusage.py b/extensions/nameusage/cogs/nameusage.py index 8a84201..3a122ea 100644 --- a/extensions/nameusage/cogs/nameusage.py +++ b/extensions/nameusage/cogs/nameusage.py @@ -6,6 +6,7 @@ from extensions.nameusage.views.pageview import GetTopPageView from resources.checks import not_in_dms_check +from resources.customs import Bot class NameUsage(commands.GroupCog, name="nameusage", description="Get data about which names are used in which server"): @@ -19,7 +20,7 @@ def __init__(self): discord.app_commands.Choice(name='Search nicks and usernames', value=3), ]) @app_commands.check(not_in_dms_check) - async def nameusage_gettop(self, itx: discord.Interaction, mode: int): + async def nameusage_gettop(self, itx: discord.Interaction[Bot], mode: int): await itx.response.defer(ephemeral=True) sections = {} for member in itx.guild.members: @@ -130,7 +131,7 @@ def add(member_name_part): discord.app_commands.Choice(name='Search both nicknames and usernames', value=3), ]) @app_commands.check(not_in_dms_check) - async def nameusage_name(self, itx: discord.Interaction, name: str, search_type: int, public: bool = False): + async def nameusage_name(self, itx: discord.Interaction[Bot], name: str, search_type: int, public: bool = False): await itx.response.defer(ephemeral=not public) count = 0 type_string = "" diff --git a/extensions/nameusage/views/pageview.py b/extensions/nameusage/views/pageview.py index 37cc598..4d11e29 100644 --- a/extensions/nameusage/views/pageview.py +++ b/extensions/nameusage/views/pageview.py @@ -1,6 +1,7 @@ import discord from extensions.nameusage.modals.getnamemodal import GetNameModal +from resources.customs import Bot # todo: use Generics.PageView @@ -27,7 +28,7 @@ async def _make_page(self): # stop the View from listening to more input. # We also send the user an ephemeral message that we're confirming their choice. @discord.ui.button(label='Previous', style=discord.ButtonStyle.blurple) - async def previous(self, itx: discord.Interaction, _button: discord.ui.Button): + async def previous(self, itx: discord.Interaction[Bot], _button: discord.ui.Button): # self.value = "previous" self.page -= 1 if self.page < 0: @@ -36,7 +37,7 @@ async def previous(self, itx: discord.Interaction, _button: discord.ui.Button): await itx.response.edit_message(embed=embed) @discord.ui.button(label='Next', style=discord.ButtonStyle.blurple) - async def next(self, itx: discord.Interaction, _button: discord.ui.Button): + async def next(self, itx: discord.Interaction[Bot], _button: discord.ui.Button): self.page += 1 if self.page >= int(len(self.pages) / 2): self.page = 0 @@ -44,7 +45,7 @@ async def next(self, itx: discord.Interaction, _button: discord.ui.Button): await itx.response.edit_message(embed=embed) @discord.ui.button(label='Find my name', style=discord.ButtonStyle.blurple) - async def find_name(self, itx: discord.Interaction, _button: discord.ui.Button): + async def find_name(self, itx: discord.Interaction[Bot], _button: discord.ui.Button): send_one = GetNameModal(self.pages, self.embed_title) await itx.response.send_modal(send_one) await send_one.wait() diff --git a/extensions/reminders/cogs/reminders.py b/extensions/reminders/cogs/reminders.py index fe966f5..f8e4e91 100644 --- a/extensions/reminders/cogs/reminders.py +++ b/extensions/reminders/cogs/reminders.py @@ -3,6 +3,7 @@ import discord.ext.commands as commands from extensions.reminders.objects import parse_and_create_reminder +from resources.customs import Bot class RemindersCog(commands.GroupCog, name="reminder"): @@ -13,7 +14,7 @@ def __init__(self): @app_commands.describe(reminder_datetime="When would you like me to remind you? (1d2h, 5 weeks, 1mo10d)", reminder="What would you like me to remind you of?") @app_commands.rename(reminder_datetime='time') - async def remindme(self, itx: discord.Interaction, reminder_datetime: str, reminder: str): + async def remindme(self, itx: discord.Interaction[Bot], reminder_datetime: str, reminder: str): # Supported formats: # - "next thursday at 3pm" # - "tomorrow" @@ -104,7 +105,7 @@ async def remindme(self, itx: discord.Interaction, reminder_datetime: str, remin @app_commands.command(name="reminders", description="Check your list of reminders!") @app_commands.describe(item="Which reminder would you like to know more about? (use reminder-ID)") - async def reminders(self, itx: discord.Interaction, item: int = None): + async def reminders(self, itx: discord.Interaction[Bot], item: int = None): collection = itx.client.rina_db["reminders"] query = {"userID": itx.user.id} db_data = collection.find_one(query) @@ -160,7 +161,7 @@ async def reminders(self, itx: discord.Interaction, item: int = None): @app_commands.command(name="remove", description="Remove of your reminders") @app_commands.describe(item="Which reminder would you like to know more about? (use reminder-ID)") - async def remove(self, itx: discord.Interaction, item: int): + async def remove(self, itx: discord.Interaction[Bot], item: int): collection = itx.client.rina_db["reminders"] query = {"userID": itx.user.id} db_data = collection.find_one(query) diff --git a/extensions/reminders/objects/reminderobject.py b/extensions/reminders/objects/reminderobject.py index b2dd1b4..6bea088 100644 --- a/extensions/reminders/objects/reminderobject.py +++ b/extensions/reminders/objects/reminderobject.py @@ -132,7 +132,7 @@ async def send_reminder(self): async def _handle_reminder_timestamp_parsing( - itx: discord.Interaction, + itx: discord.Interaction[Bot], reminder_datetime: str ) -> tuple[datetime, discord.Interaction]: # todo: docstring # validate format @@ -217,7 +217,7 @@ async def _handle_reminder_timestamp_parsing( return distance, itx -async def _parse_reminder_time(itx: discord.Interaction, reminder_datetime: str) -> tuple[datetime, datetime]: +async def _parse_reminder_time(itx: discord.Interaction[Bot], reminder_datetime: str) -> tuple[datetime, datetime]: """ Parse a datetime string: relative to today (2d 11h); a ISO8601 timestamp; or a unix timestamp. It outputs the reminder's datetime and interaction's creation time with timezone awareness. If the ISO timestamp gives @@ -267,7 +267,7 @@ async def _parse_reminder_time(itx: discord.Interaction, reminder_datetime: str) async def _create_reminder( - itx: discord.Interaction, + itx: discord.Interaction[Bot], distance: datetime, creation_time: datetime, reminder: str, db_data: list[ReminderDict], @@ -307,7 +307,7 @@ async def _create_reminder( async def parse_and_create_reminder( - itx: discord.Interaction, + itx: discord.Interaction[Bot], reminder_datetime: str, reminder: str ): diff --git a/extensions/reminders/views/timeofdayselection.py b/extensions/reminders/views/timeofdayselection.py index b9c5d0b..0e22cf6 100644 --- a/extensions/reminders/views/timeofdayselection.py +++ b/extensions/reminders/views/timeofdayselection.py @@ -1,6 +1,7 @@ import discord from resources.views.generics import create_simple_button +from resources.customs import Bot class TimeOfDaySelection(discord.ui.View): @@ -16,7 +17,7 @@ def callback(itx): # pass the button label to the callback self.add_item(create_simple_button(option, discord.ButtonStyle.green, callback)) - async def callback(self, interaction: discord.Interaction, label: str): + async def callback(self, interaction: discord.Interaction[Bot], label: str): self.value = label self.return_interaction = interaction self.stop() diff --git a/extensions/settings/cogs/settings.py b/extensions/settings/cogs/settings.py index 38c2228..7fc4d00 100644 --- a/extensions/settings/cogs/settings.py +++ b/extensions/settings/cogs/settings.py @@ -5,14 +5,14 @@ import discord.ext.commands as commands import discord.app_commands as app_commands -from extensions.settings.objects.server_settings import ParseError -from extensions.watchlist.local_watchlist import refetch_watchlist_threads from resources.checks import ( is_admin_check, not_in_dms_check, module_enabled_check, MissingAttributesCheckFailure, ) from extensions.help.cogs import send_help_menu +from extensions.settings.objects.server_settings import ParseError +from extensions.watchlist.local_watchlist import refetch_watchlist_threads from extensions.settings.objects import ( ServerSettings, ServerAttributes, ServerAttributeIds, EnabledModules, TypeAutocomplete, ModeAutocomplete, @@ -45,7 +45,7 @@ def get_attribute_autocomplete_mode( @app_commands.check(is_admin_check) async def _setting_autocomplete( - itx: discord.Interaction, current: str + itx: discord.Interaction[Bot], current: str ) -> list[app_commands.Choice[str]]: itx.namespace.type = typing.cast(str | None, itx.namespace.type) @@ -76,7 +76,7 @@ async def _setting_autocomplete( @app_commands.check(is_admin_check) async def _mode_autocomplete( - itx: discord.Interaction, current: str + itx: discord.Interaction[Bot], current: str ) -> list[app_commands.Choice[str]]: itx.namespace.type = typing.cast(str | None, itx.namespace.type) itx.namespace.setting = typing.cast(str | None, itx.namespace.setting) @@ -116,7 +116,7 @@ async def _mode_autocomplete( @app_commands.check(is_admin_check) async def _value_autocomplete( - itx: discord.Interaction, current: str + itx: discord.Interaction[Bot], current: str ) -> list[app_commands.Choice[str]]: itx.namespace.type = typing.cast(str | None, itx.namespace.type) itx.namespace.mode = typing.cast(str | None, itx.namespace.mode) @@ -723,7 +723,7 @@ async def migrate_watchlist(self, itx: discord.Interaction[Bot]): @app_commands.check(not_in_dms_check) async def settings( self, - itx: discord.Interaction, + itx: discord.Interaction[Bot], setting_type: str, setting: str | None = None, mode: str | None = None, diff --git a/extensions/staffaddons/cogs/staffaddons.py b/extensions/staffaddons/cogs/staffaddons.py index 78b5b4d..c5aaa08 100644 --- a/extensions/staffaddons/cogs/staffaddons.py +++ b/extensions/staffaddons/cogs/staffaddons.py @@ -21,7 +21,7 @@ def __init__(self): @app_commands.command(name="say", description="Force Rina to repeat your wise words") @app_commands.describe(text="What will you make Rina repeat?", reply_to_interaction="Show who sent the message?") - async def say(self, itx: discord.Interaction, text: str, reply_to_interaction: bool = False): + async def say(self, itx: discord.Interaction[Bot], text: str, reply_to_interaction: bool = False): if reply_to_interaction: await itx.response.send_message(text, ephemeral=False, allowed_mentions=discord.AllowedMentions.none()) return diff --git a/extensions/tags/cogs/tags.py b/extensions/tags/cogs/tags.py index 3c62bb0..08b89dc 100644 --- a/extensions/tags/cogs/tags.py +++ b/extensions/tags/cogs/tags.py @@ -34,7 +34,7 @@ def _get_enabled_tag_ids(itx) -> set[str]: return set(default_tags + custom_tags) -async def _tag_autocomplete(itx: discord.Interaction, current: str): +async def _tag_autocomplete(itx: discord.Interaction[Bot], current: str): """Autocomplete for /tag command.""" if current == "": return [app_commands.Choice(name="Show list of tags", value="help")] @@ -135,7 +135,7 @@ async def on_message(self, message: discord.Message): @module_enabled_check(ModuleKeys.tags) async def tag( self, - itx: discord.Interaction, + itx: discord.Interaction[Bot], tag: str, public: bool = True, anonymous: bool = True diff --git a/extensions/tags/tags.py b/extensions/tags/tags.py index 7bd1b92..1d1ff7b 100644 --- a/extensions/tags/tags.py +++ b/extensions/tags/tags.py @@ -97,7 +97,7 @@ async def send( async def _handle_send_publicly( self, - itx: discord.Interaction, + itx: discord.Interaction[Bot], anonymous: bool, report_to_staff: bool ) -> None: @@ -139,7 +139,7 @@ async def _handle_send_publicly( async def _handle_send_privately( self, - itx: discord.Interaction, + itx: discord.Interaction[Bot], anonymous: bool, report_to_staff: bool ) -> None: @@ -188,7 +188,7 @@ def create_report_info_tag( async def send_report_info( - itx: discord.Interaction, public: bool, anonymous: bool, + itx: discord.Interaction[Bot], public: bool, anonymous: bool, ) -> None: """Helper to send report tag.""" ticket_channel: discord.abc.Messageable | None = get_mod_ticket_channel( diff --git a/extensions/termdictionary/cogs/termdictionary.py b/extensions/termdictionary/cogs/termdictionary.py index 6e5b921..615113f 100644 --- a/extensions/termdictionary/cogs/termdictionary.py +++ b/extensions/termdictionary/cogs/termdictionary.py @@ -7,6 +7,7 @@ import discord.app_commands as app_commands import discord.ext.commands as commands +from resources.customs import Bot from resources.checks import is_staff_check # for staff dictionary commands # for logging custom dictionary changes, or when a search query returns nothing or >2000 characters from resources.utils.utils import log_to_guild @@ -17,7 +18,7 @@ del_separators_table = str.maketrans({" ": "", "-": "", "_": ""}) -async def dictionary_autocomplete(itx: discord.Interaction, current: str): +async def dictionary_autocomplete(itx: discord.Interaction[Bot], current: str): def simplify(q): if type(q) is str: return q.lower().translate(del_separators_table) @@ -93,7 +94,7 @@ def __init__(self): discord.app_commands.Choice(name='Search from urbandictionary.com', value=8), ]) @app_commands.autocomplete(term=dictionary_autocomplete) - async def dictionary(self, itx: discord.Interaction, term: str, public: bool = False, source: int = 1): + async def dictionary(self, itx: discord.Interaction[Bot], term: str, public: bool = False, source: int = 1): # todo: rewrite this whole command. # - Make the sources an enum. # - Allow going to the next source if you're not satisfied with a dictionary's result. @@ -422,7 +423,7 @@ def simplify(q): term="This is the main word for the dictionary entry: Egg, Hormone Replacement Therapy (HRT), (case sens.)", definition="Give this term a definition", synonyms="Add synonyms (SEPARATE WITH \", \")") - async def define(self, itx: discord.Interaction, term: str, definition: str, synonyms: str = ""): + async def define(self, itx: discord.Interaction[Bot], term: str, definition: str, synonyms: str = ""): def simplify(q): if type(q) is str: return q.lower().translate(del_separators_table) @@ -472,7 +473,7 @@ def simplify(q): term="This is the main word for the dictionary entry (case sens.) Example: Egg, Hormone " "Replacement Therapy (HRT), etc.", definition="Redefine this definition") - async def redefine(self, itx: discord.Interaction, term: str, definition: str): + async def redefine(self, itx: discord.Interaction[Bot], term: str, definition: str): collection = itx.client.rina_db["termDictionary"] query = {"term": term} search = collection.find_one(query) @@ -493,7 +494,7 @@ async def redefine(self, itx: discord.Interaction, term: str, definition: str): @admin.command(name="undefine", description="Remove a dictionary entry for a word!") @app_commands.describe( term="What word do you need to undefine (case sensitive). Example: Egg, Hormone Replacement Therapy (HRT), etc") - async def undefine(self, itx: discord.Interaction, term: str): + async def undefine(self, itx: discord.Interaction[Bot], term: str): collection = itx.client.rina_db["termDictionary"] query = {"term": term} search = collection.find_one(query) @@ -518,7 +519,7 @@ async def undefine(self, itx: discord.Interaction, term: str): discord.app_commands.Choice(name='Add a synonym', value=1), discord.app_commands.Choice(name='Remove a synonym', value=2), ]) - async def edit_synonym(self, itx: discord.Interaction, term: str, mode: int, synonym: str): + async def edit_synonym(self, itx: discord.Interaction[Bot], term: str, mode: int, synonym: str): collection = itx.client.rina_db["termDictionary"] query = {"term": term} search = collection.find_one(query) diff --git a/extensions/termdictionary/views/dictionaryapi_pageview.py b/extensions/termdictionary/views/dictionaryapi_pageview.py index 7ed0c78..e0433ff 100644 --- a/extensions/termdictionary/views/dictionaryapi_pageview.py +++ b/extensions/termdictionary/views/dictionaryapi_pageview.py @@ -1,6 +1,7 @@ import discord from extensions.termdictionary.modals import DictionaryAPISendPageModal +from resources.customs import Bot class DictionaryApi_PageView(discord.ui.View): @@ -13,7 +14,7 @@ def __init__(self, pages, pages_detailed, timeout=None): self.pages_detailed = pages_detailed @discord.ui.button(label='Previous', style=discord.ButtonStyle.blurple) - async def previous(self, itx: discord.Interaction, _button: discord.ui.Button): + async def previous(self, itx: discord.Interaction[Bot], _button: discord.ui.Button): self.page -= 1 if self.page < 0: self.page = len(self.pages) - 1 @@ -22,7 +23,7 @@ async def previous(self, itx: discord.Interaction, _button: discord.ui.Button): await itx.response.edit_message(embed=embed) @discord.ui.button(label='Next', style=discord.ButtonStyle.blurple) - async def next(self, itx: discord.Interaction, _button: discord.ui.Button): + async def next(self, itx: discord.Interaction[Bot], _button: discord.ui.Button): self.page += 1 if self.page >= (len(self.pages)): self.page = 0 @@ -36,7 +37,7 @@ async def next(self, itx: discord.Interaction, _button: discord.ui.Button): ephemeral=True) @discord.ui.button(label='Send publicly', style=discord.ButtonStyle.gray) - async def send_publicly(self, itx: discord.Interaction, _button: discord.ui.Button): + async def send_publicly(self, itx: discord.Interaction[Bot], _button: discord.ui.Button): self.value = 1 embed = self.pages[self.page] await itx.response.edit_message(content="Sent successfully!", embed=None) @@ -45,7 +46,7 @@ async def send_publicly(self, itx: discord.Interaction, _button: discord.ui.Butt self.stop() @discord.ui.button(label='Send one entry', style=discord.ButtonStyle.gray) - async def send_single_entry(self, itx: discord.Interaction, _button: discord.ui.Button): + async def send_single_entry(self, itx: discord.Interaction[Bot], _button: discord.ui.Button): self.value = 2 send_one = DictionaryAPISendPageModal(self.pages_detailed[self.page]) diff --git a/extensions/termdictionary/views/urbandictionary_pageview.py b/extensions/termdictionary/views/urbandictionary_pageview.py index c31f6bf..f60df01 100644 --- a/extensions/termdictionary/views/urbandictionary_pageview.py +++ b/extensions/termdictionary/views/urbandictionary_pageview.py @@ -1,5 +1,7 @@ import discord +from resources.customs import Bot + class UrbanDictionary_PageView(discord.ui.View): def __init__(self, pages, timeout=None): @@ -11,7 +13,7 @@ def __init__(self, pages, timeout=None): self.pages = pages @discord.ui.button(label='Previous', style=discord.ButtonStyle.blurple) - async def previous(self, itx: discord.Interaction, _button: discord.ui.Button): + async def previous(self, itx: discord.Interaction[Bot], _button: discord.ui.Button): self.page -= 1 if self.page < 0: self.page = len(self.pages) - 1 @@ -20,7 +22,7 @@ async def previous(self, itx: discord.Interaction, _button: discord.ui.Button): await itx.response.edit_message(embed=embed) @discord.ui.button(label='Next', style=discord.ButtonStyle.blurple) - async def next(self, itx: discord.Interaction, _button: discord.ui.Button): + async def next(self, itx: discord.Interaction[Bot], _button: discord.ui.Button): self.page += 1 if self.page >= (len(self.pages)): self.page = 0 diff --git a/extensions/testing_commands/cogs/testingcommands.py b/extensions/testing_commands/cogs/testingcommands.py index 53b297f..390cbe6 100644 --- a/extensions/testing_commands/cogs/testingcommands.py +++ b/extensions/testing_commands/cogs/testingcommands.py @@ -93,7 +93,7 @@ def __init__(self): ) @app_commands.check(is_staff_check) async def send_fake_watchlist_mod_log( - self, itx: discord.Interaction, target: discord.User, reason: str = "", + self, itx: discord.Interaction[Bot], target: discord.User, reason: str = "", rule: str = None, private_notes: str = "", role_changes: str = "" ): staff_logs_category = itx.client.get_guild_attribute( @@ -121,13 +121,13 @@ async def send_fake_watchlist_mod_log( @app_commands.describe(page_count="The amount of pages to send/test") @app_commands.check(is_staff_check) async def send_pageview_test_embed( - self, itx: discord.Interaction, page_count: app_commands.Range[int, 1, 10000] = 40 + self, itx: discord.Interaction[Bot], page_count: app_commands.Range[int, 1, 10000] = 40 ): def get_chars(length: int): letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/ " return (''.join(random.choice(letters) for _ in range(length))).strip() - async def update_test_page(itx1: discord.Interaction, view1: PageView): + async def update_test_page(itx1: discord.Interaction[Bot], view1: PageView): embed = view1.pages[view1.page] await itx1.response.edit_message( content="updated a" + str(view1.page), @@ -163,7 +163,7 @@ async def go_to_page_button_callback(itx1: discord.Interaction): @app_commands.command(name="send_srmod_appeal_test", description="Send a test embed of a ban appeal") @app_commands.describe(username="The username you want to fill in") @app_commands.check(is_staff_check) - async def send_srmod_appeal_test(self, itx: discord.Interaction, username: str): + async def send_srmod_appeal_test(self, itx: discord.Interaction[Bot], username: str): embed: discord.Embed = discord.Embed(title="New Ban Appeal") embed.add_field(name="Which of the following are you appealing?", value="Discord Ban") diff --git a/extensions/todolist/cogs/todolist.py b/extensions/todolist/cogs/todolist.py index dd4054a..6e8e133 100644 --- a/extensions/todolist/cogs/todolist.py +++ b/extensions/todolist/cogs/todolist.py @@ -2,6 +2,8 @@ import discord.app_commands as app_commands import discord.ext.commands as commands +from resources.customs import Bot + class TodoList(commands.Cog): def __init__(self): @@ -15,7 +17,7 @@ def __init__(self): discord.app_commands.Choice(name='Remove to-do', value=2), discord.app_commands.Choice(name='Check', value=3) ]) - async def todo(self, itx: discord.Interaction, mode: int, todo: str = None): + async def todo(self, itx: discord.Interaction[Bot], mode: int, todo: str = None): if mode == 1: # Add item to to-do list if todo is None: cmd_mention = itx.client.get_command_mention("todo") diff --git a/extensions/toneindicator/cogs/toneindicator.py b/extensions/toneindicator/cogs/toneindicator.py index a10fde3..d2144b0 100644 --- a/extensions/toneindicator/cogs/toneindicator.py +++ b/extensions/toneindicator/cogs/toneindicator.py @@ -5,6 +5,7 @@ from extensions.toneindicator.searchmode import SearchMode from resources.customs import Bot + tone_indicators: dict[str, list[str]] = { "excited": ["/!", "/exc"], "alterous": ["/a", "/ars"], @@ -324,7 +325,7 @@ class ToneIndicator(commands.Cog): @app_commands.allowed_contexts(guilds=True, private_channels=True, dms=True) async def toneindicator( self, - itx: discord.Interaction, + itx: discord.Interaction[Bot], mode: int, string: str, public: bool = False diff --git a/extensions/watchlist/cogs/watchlist.py b/extensions/watchlist/cogs/watchlist.py index 6b5c877..01f17e8 100644 --- a/extensions/watchlist/cogs/watchlist.py +++ b/extensions/watchlist/cogs/watchlist.py @@ -300,7 +300,7 @@ async def _add_to_watchlist( @app_commands.check(is_staff_check) @module_enabled_check(ModuleKeys.watchlist) @app_commands.context_menu(name="Add user to watchlist") -async def watchlist_ctx_user(itx: discord.Interaction, user: discord.User): +async def watchlist_ctx_user(itx: discord.Interaction[Bot], user: discord.User): watchlist_reason_modal = WatchlistReasonModal( _add_to_watchlist, "Add user to watchlist", user, None, 300 @@ -312,7 +312,7 @@ async def watchlist_ctx_user(itx: discord.Interaction, user: discord.User): @module_enabled_check(ModuleKeys.watchlist) @app_commands.context_menu(name="Add msg to watchlist") async def watchlist_ctx_message( - itx: discord.Interaction, + itx: discord.Interaction[Bot], message: discord.Message ): watchlist_reason_modal = WatchlistReasonModal( @@ -337,7 +337,7 @@ def __init__(self, client: Bot): @module_enabled_check(ModuleKeys.watchlist) async def watchlist( self, - itx: discord.Interaction, + itx: discord.Interaction[Bot], user: discord.User, reason: str = "", message_id: str = None diff --git a/resources/views/generics.py b/resources/views/generics.py index b9fdf04..0fc6c8e 100644 --- a/resources/views/generics.py +++ b/resources/views/generics.py @@ -2,6 +2,8 @@ import discord import typing +from resources.customs import Bot + def create_simple_button( label: str, @@ -93,7 +95,7 @@ def page_up_style(self) -> tuple[discord.ButtonStyle, bool]: self.page == self.max_page_index and not self.loop_around_pages ) - async def update_page(self, itx: discord.Interaction, view: PageView): + async def update_page(self, itx: discord.Interaction[Bot], view: PageView): """ Update the page message. This typically involves calculating the message content for the message and updating the original message. From 06654e0fca5533c155c1391f8cb763c64c87429e Mon Sep 17 00:00:00 2001 From: MysticMia <65396931+MysticMia@users.noreply.github.com> Date: Fri, 16 May 2025 05:06:20 +0200 Subject: [PATCH 03/26] Fix flake8 errors (line too long, missing imports after moving reminder exception handling) --- extensions/addons/cogs/otheraddons.py | 8 ++++++- extensions/customvcs/cogs/vctables.py | 24 +++++++++++++------ extensions/reminders/cogs/reminders.py | 4 ++++ .../reminders/objects/reminderobject.py | 9 ++++--- 4 files changed, 32 insertions(+), 13 deletions(-) diff --git a/extensions/addons/cogs/otheraddons.py b/extensions/addons/cogs/otheraddons.py index 22e3d9b..05b0dc8 100644 --- a/extensions/addons/cogs/otheraddons.py +++ b/extensions/addons/cogs/otheraddons.py @@ -227,7 +227,13 @@ async def on_message(self, message: discord.Message): @app_commands.rename(from_unit='to') @app_commands.autocomplete(to_unit=_unit_autocomplete) async def convert_unit( - self, itx: discord.Interaction[Bot], mode: str, from_unit: str, value: float, to_unit: str, public: bool = False + self, + itx: discord.Interaction[Bot], + mode: str, + from_unit: str, + value: float, + to_unit: str, + public: bool = False ): rates = conversion_rates.copy() if mode not in rates: diff --git a/extensions/customvcs/cogs/vctables.py b/extensions/customvcs/cogs/vctables.py index 5fec402..5b31d12 100644 --- a/extensions/customvcs/cogs/vctables.py +++ b/extensions/customvcs/cogs/vctables.py @@ -52,16 +52,18 @@ def _is_vctable_authorized(channel, guild: discord.Guild) -> bool: def _is_vctable_locked(channel, guild: discord.Guild) -> bool: - if guild.guild.default_role not in channel.overwrites: + if guild.default_role not in channel.overwrites: return False - return channel.overwrites[guild.guild.default_role].view_channel is False + return channel.overwrites[guild.default_role].view_channel is False # endregion Permission checks def _get_vctable_members_with_predicate( channel: discord.VoiceChannel, - predicate: Callable[[discord.VoiceChannel, discord.Member | discord.Role], bool] + predicate: Callable[ + [discord.VoiceChannel, discord.Member | discord.Role + | discord.Object], bool] ): outputs = [] for target in channel.overwrites: @@ -83,8 +85,8 @@ async def _get_current_voice_channel(itx: discord.Interaction[Bot], action: str, ``None`` if the user is not in a custom voice channel. :raise MissingAttributesCheckFailure: If any guild attributes are missing. """ - vc_hub: discord.VoiceChannel - vc_category: discord.CategoryChannel + vc_hub: discord.VoiceChannel | None + vc_category: discord.CategoryChannel | None blacklisted_channels: list[discord.VoiceChannel] vc_hub, vc_category, blacklisted_channels, vc_blacklist_prefix = itx.client.get_guild_attribute( itx.guild, @@ -160,7 +162,10 @@ def __init__(self): name="Give the channel a different name (api efficiency)") @module_enabled_check(ModuleKeys.vc_tables) async def vctable_create( - self, itx: discord.Interaction[Bot], owners: str = "", name: app_commands.Range[str, 3, 35] | None = None + self, + itx: discord.Interaction[Bot], + owners: str = "", + name: app_commands.Range[str, 3, 35] | None = None ): vctable_prefix: str | None = itx.client.get_guild_attribute(itx.guild, AttributeKeys.vctable_prefix) @@ -372,7 +377,12 @@ async def vctable_help(self, itx: discord.Interaction): discord.app_commands.Choice(name='Check owners', value=3) ]) @module_enabled_check(ModuleKeys.vc_tables) - async def edit_vctable_owners(self, itx: discord.Interaction[Bot], mode: int, user: discord.Member | None = None): + async def edit_vctable_owners( + self, + itx: discord.Interaction[Bot], + mode: int, + user: discord.Member | None = None + ): if itx.user == user and mode != 3: if mode == 1: await itx.response.send_message("You can't set yourself as owner!", ephemeral=True) diff --git a/extensions/reminders/cogs/reminders.py b/extensions/reminders/cogs/reminders.py index f8e4e91..5fe8365 100644 --- a/extensions/reminders/cogs/reminders.py +++ b/extensions/reminders/cogs/reminders.py @@ -2,8 +2,12 @@ import discord.app_commands as app_commands import discord.ext.commands as commands +from extensions.reminders.exceptions import UnixTimestampInPastException, \ + MalformedISODateTimeException, TimestampParseException, \ + ReminderTimeSelectionMenuTimeOut from extensions.reminders.objects import parse_and_create_reminder from resources.customs import Bot +from resources.utils import MissingQuantityException, MissingUnitException class RemindersCog(commands.GroupCog, name="reminder"): diff --git a/extensions/reminders/objects/reminderobject.py b/extensions/reminders/objects/reminderobject.py index 6bea088..9184177 100644 --- a/extensions/reminders/objects/reminderobject.py +++ b/extensions/reminders/objects/reminderobject.py @@ -4,13 +4,12 @@ import discord from resources.customs import Bot -from resources.utils import ( - TimeParser, MissingQuantityException, MissingUnitException -) +from resources.utils import TimeParser from extensions.reminders.exceptions import ( - UnixTimestampInPastException, TimestampParseException, - MalformedISODateTimeException, ReminderTimeSelectionMenuTimeOut + UnixTimestampInPastException, + TimestampParseException, + ReminderTimeSelectionMenuTimeOut, ) from extensions.reminders.objects import ( ReminderDict, TimestampFormats, DatabaseData From 2155c01345d1de024c8d48d2612599fae9cdeb63 Mon Sep 17 00:00:00 2001 From: MysticMia <65396931+MysticMia@users.noreply.github.com> Date: Tue, 20 May 2025 03:35:04 +0200 Subject: [PATCH 04/26] Wrap every file I could find at 79 characters. Took a few hours D: Some files aren't wrapped, like the /roll command in funaddons and most dictionary pages. That's because those are being edited in the remove-complexity branch and will get formatted eventually or have already been formatted there. --- dir | 0 extensions/addons/cogs/funaddons.py | 70 +- extensions/addons/cogs/otheraddons.py | 253 ++-- extensions/addons/cogs/searchaddons.py | 332 ++++-- .../addons/views/math_sendpublicbutton.py | 14 +- extensions/compliments/cogs/compliments.py | 371 ++++-- .../compliments/views/confirmpronouns.py | 60 +- .../crashhandling/cogs/crashhandling.py | 160 ++- .../customvcs/channel_rename_tracker.py | 16 +- extensions/customvcs/cogs/customvcs.py | 299 +++-- extensions/customvcs/cogs/vctables.py | 1030 ++++++++++++----- extensions/customvcs/modals/staffeditor.py | 185 ++- extensions/emojistats/cogs/emojistats.py | 8 +- extensions/emojistats/cogs/stickerstats.py | 132 ++- extensions/getmemberdata/cogs/memberdata.py | 195 +++- extensions/help/cogs/helpcommand.py | 94 +- extensions/help/helppage.py | 6 +- extensions/help/helppages.py | 480 +++++--- extensions/help/utils.py | 62 +- extensions/help/views/helppage.py | 83 +- .../cogs/banappealreaction.py | 18 +- extensions/nameusage/cogs/nameusage.py | 80 +- extensions/nameusage/modals/getnamemodal.py | 38 +- extensions/nameusage/views/pageview.py | 16 +- extensions/qotw/cogs/devrequest.py | 130 ++- extensions/qotw/cogs/qotw.py | 54 +- extensions/reminders/cogs/reminders.py | 160 ++- .../reminders/objects/bumpreminderobject.py | 16 +- .../reminders/objects/emoji_animate_type.py | 3 +- .../reminders/objects/reminderobject.py | 214 +++- extensions/reminders/views/copyreminder.py | 29 +- extensions/reminders/views/sharereminder.py | 6 +- .../reminders/views/timeofdayselection.py | 9 +- extensions/settings/cogs/settings.py | 12 +- .../settings/objects/server_settings.py | 123 +- extensions/staffaddons/cogs/staffaddons.py | 151 ++- extensions/starboard/cogs/starboard.py | 189 ++- extensions/starboard/local_starboard.py | 6 +- extensions/tags/cogs/tags.py | 3 +- extensions/tags/tags.py | 6 +- .../testing_commands/cogs/testingcommands.py | 104 +- extensions/todolist/cogs/todolist.py | 109 +- .../toneindicator/cogs/toneindicator.py | 32 +- extensions/vclogreader/cogs/vclogreader.py | 177 ++- extensions/vclogreader/customvoicechannel.py | 13 +- extensions/watchlist/cogs/watchlist.py | 139 ++- .../watchlist/modals/watchlistreasonmodal.py | 28 +- main.py | 86 +- resources/checks/command_checks.py | 55 +- resources/checks/permission_checks.py | 17 +- resources/checks/permissions.py | 16 +- resources/customs/api_token_dict.py | 11 +- resources/customs/bot.py | 44 +- resources/customs/enabledservers.py | 12 +- resources/customs/progressbar.py | 47 +- resources/modals/generics.py | 9 +- resources/utils/__init__.py | 3 +- resources/utils/stringhelper.py | 12 +- resources/utils/timeparser.py | 137 ++- resources/utils/utils.py | 117 +- resources/views/generics.py | 71 +- unit_tests/extensions/help/test_helppages.py | 12 +- .../reminders/objects/test_reminderobject.py | 99 +- .../extensions/settings/test_attributes.py | 23 +- .../resources/customs/test_progressbar.py | 61 +- .../timeparser/test_parse_time_string.py | 25 +- .../timeparser/test_shrink_time_terms.py | 8 +- unit_tests/test_main.py | 12 +- unit_tests/utils/embeds.py | 46 +- 69 files changed, 4542 insertions(+), 2096 deletions(-) create mode 100644 dir diff --git a/dir b/dir new file mode 100644 index 0000000..e69de29 diff --git a/extensions/addons/cogs/funaddons.py b/extensions/addons/cogs/funaddons.py index b6fe50f..8149743 100644 --- a/extensions/addons/cogs/funaddons.py +++ b/extensions/addons/cogs/funaddons.py @@ -1,4 +1,6 @@ -import random # for dice rolls (/roll) and selecting a random staff interaction wait time +import random +# ^ for dice rolls (/roll) and selecting a random staff +# interaction wait time from typing import TypeVar import discord @@ -167,39 +169,53 @@ def handle_random_pat_reaction( self.headpat_wait += 1 if self.headpat_wait >= 1000: # todo: do we want server-specific headpat times? - # todo attribute: add headpat channel blacklist. Should also look if messages are in thread of this channel. - # Should also be able to be categories. + # todo attribute: add headpat channel blacklist. Should also + # look if messages are in thread of this channel. Should + # also be able to be categories. if ( - (type(message.channel) is discord.Thread and - message.channel.parent == 987358841245151262) or # <#welcome-verify> - message.channel.name.startswith('ticket-') or - message.channel.name.startswith('closed-') or - message.channel.category.id in [ - 959584962443632700, 959590295777968128, - 959928799309484032, 1041487583475138692, - 995330645901455380, 995330667665707108] or + (type(message.channel) is discord.Thread + and message.channel.parent == 987358841245151262) + # ^ <#welcome-verify> + or message.channel.name.startswith('ticket-') + or message.channel.name.startswith('closed-') # <#Bulletin Board>, <#Moderation Logs>, # <#Verifier Archive>, <#Events>, # <#Open Tickets>, <#Closed Tickets> - message.guild.id in [981730502987898960] # don't send in Mod server + or message.channel.category.id in [ + 959584962443632700, 959590295777968128, + 959928799309484032, 1041487583475138692, + 995330645901455380, 995330667665707108 + ] + or message.guild.id in [981730502987898960] + # ^ don't send in Mod server ): return False self.headpat_wait = 0 - # # TODO: re-enable code someday - # # people asked for no random headpats anymore; or make it opt-in. See GitHub #23 + # TODO: re-enable code someday + # people asked for no random headpats anymore; or make it + # opt-in. See GitHub #23 + # try: # added_pat = True # await message.add_reaction(headpat_emoji) #headpatWait # except discord.errors.Forbidden: - # await log_to_guild(self.client, message.guild, f"**:warning: Warning: **Couldn\'t add pat " - # f"reaction to {message.jump_url} (Forbidden): " - # f"They might have blocked Rina...') + # await log_to_guild( + # self.client, + # message.guild, + # f"**:warning: Warning: **Couldn\'t add pat " + # f"reaction to {message.jump_url} (Forbidden): " + # f"They might have blocked Rina..." + # ) # except discord.errors.HTTPException as ex: - # await log_to_guild(self.client, message.guild, f"**:warning: Warning: **Couldn\'t add pat " - # f"reaction to {message.jump_url}. " - # f"(HTTP/{ex.code}) " - # f"They might have blocked Rina...') + # await log_to_guild( + # self.client, + # message.guild, + # f"**:warning: Warning: **Couldn\'t add pat " + # f"reaction to {message.jump_url}. " + # f"(HTTP/{ex.code}) " + # f"They might have blocked Rina..." + # ) # return True return False @@ -209,8 +225,10 @@ async def on_message(self, message: discord.Message): if message.author.bot: return - if self.client.is_module_enabled(message.guild, ModuleKeys.headpat_reactions): - headpat_emoji: discord.Emoji | None = self.client.get_guild_attribute( + if self.client.is_module_enabled( + message.guild, ModuleKeys.headpat_reactions): + headpat_emoji: discord.Emoji | None + headpat_emoji = self.client.get_guild_attribute( message.guild, AttributeKeys.headpat_emoji) if headpat_emoji is None: raise MissingAttributesCheckFailure( @@ -219,8 +237,10 @@ async def on_message(self, message: discord.Message): self.handle_random_pat_reaction(message, headpat_emoji) return - if self.client.is_module_enabled(message.guild, ModuleKeys.awawawa_reactions): - awawawa_emoji: discord.Emoji | None = self.client.get_guild_attribute( + if self.client.is_module_enabled( + message.guild, ModuleKeys.awawawa_reactions): + awawawa_emoji: discord.Emoji | None + awawawa_emoji = self.client.get_guild_attribute( message.guild, AttributeKeys.awawawa_emoji) if awawawa_emoji is None: raise MissingAttributesCheckFailure( diff --git a/extensions/addons/cogs/otheraddons.py b/extensions/addons/cogs/otheraddons.py index 05b0dc8..6d6b2f3 100644 --- a/extensions/addons/cogs/otheraddons.py +++ b/extensions/addons/cogs/otheraddons.py @@ -20,13 +20,19 @@ currency_options = { code: 0 for code in ( - "AED,AFN,ALL,AMD,ANG,AOA,ARS,AUD,AWG,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BTC,BTN,BWP,BYN,BZD,CAD," - "CDF,CHF,CLF,CLP,CNH,CNY,COP,CRC,CUC,CUP,CVE,CZK,DJF,DKK,DOP,DZD,EGP,ERN,ETB,EUR,FJD,FKP,GBP,GEL,GGP,GHS,GIP," - "GMD,GNF,GTQ,GYD,HKD,HNL,HRK,HTG,HUF,IDR,ILS,IMP,INR,IQD,IRR,ISK,JEP,JMD,JOD,JPY,KES,KGS,KHR,KMF,KPW,KRW,KWD," - "KYD,KZT,LAK,LBP,LKR,LRD,LSL,LYD,MAD,MDL,MGA,MKD,MMK,MNT,MOP,MRU,MUR,MVR,MWK,MXN,MYR,MZN,NAD,NGN,NIO,NOK,NPR," - "NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SDG,SEK,SGD,SHP,SLL,SOS,SRD,SSP,STD,STN," - "SVC,SYP,SZL,THB,TJS,TMT,TND,TOP,TRY,TTD,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XAG,XAU,XCD,XDR,XOF," - "XPD,XPF,XPT,YER,ZAR,ZMW,ZWL".split(","))} + "AED,AFN,ALL,AMD,ANG,AOA,ARS,AUD,AWG,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD," + "BND,BOB,BRL,BSD,BTC,BTN,BWP,BYN,BZD,CAD,CDF,CHF,CLF,CLP,CNH,CNY,COP," + "CRC,CUC,CUP,CVE,CZK,DJF,DKK,DOP,DZD,EGP,ERN,ETB,EUR,FJD,FKP,GBP,GEL," + "GGP,GHS,GIP,GMD,GNF,GTQ,GYD,HKD,HNL,HRK,HTG,HUF,IDR,ILS,IMP,INR,IQD," + "IRR,ISK,JEP,JMD,JOD,JPY,KES,KGS,KHR,KMF,KPW,KRW,KWD,KYD,KZT,LAK,LBP," + "LKR,LRD,LSL,LYD,MAD,MDL,MGA,MKD,MMK,MNT,MOP,MRU,MUR,MVR,MWK,MXN,MYR," + "MZN,NAD,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON," + "RSD,RUB,RWF,SAR,SBD,SCR,SDG,SEK,SGD,SHP,SLL,SOS,SRD,SSP,STD,STN,SVC," + "SYP,SZL,THB,TJS,TMT,TND,TOP,TRY,TTD,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES," + "VND,VUV,WST,XAF,XAG,XAU,XCD,XDR,XOF,XPD,XPF,XPT,YER,ZAR,ZMW,ZWL" + .split(",") + ) +} conversion_rates = { # [default 0, incrementation] "temperature": { "Celsius": [273.15, 1, "°C"], @@ -96,12 +102,14 @@ }, "currency": currency_options, "time": { - # 365.2421896698-6.15359\cdot10^{-6}a-7.29\cdot10^{-10}a^{2}+2.64\cdot10^{-10}a^{3} + # 365.2421896698-6.15359\cdot10^{-6}a-7.29 + # \cdot10^{-10}a^{2}+2.64\cdot10^{-10}a^{3} # where `a` is centuries of 36525 SI days - # 31556925.1 <- this will hold up for 3 years (until 2025-7-13T21:48:21.351744), + # 31556925.1 <- this will hold up for 3 years (until + # 2025-7-13T21:48:21.351744), # after which it will be 31556925.0 - # 31556925.0 will hold up for another 18 years (until 2044); after which it will be - # 31556924.9 for 19 years (2063) + # 31556925.0 will hold up for another 18 years (until 2044); + # after which it will be 31556924.9 for 19 years (2063) "decennium": [0, 1 / 315569251.0, "dec"], "year": [0, 1 / 31556925.1, "yr"], "month": [0, 12 / 31556925.1, "mo"], @@ -124,21 +132,24 @@ def _get_emoji_from_str( client: Bot, emoji_str: str | None ) -> discord.Emoji | discord.PartialEmoji | None: """ - Get a matching (partial) emoji object from an emoji string or emoji ID. + Get a matching (partial) emoji object from an emoji string or + emoji ID. :param client: The client/bot whose servers to check for the emoji. - :param emoji_str: The emoji ( -> Emoji) or id (0123456789 -> PartialEmoji) to look for. + :param emoji_str: The emoji ( -> Emoji) + or id (0123456789 -> PartialEmoji) to look for. - Returns: - - ``None``: If no emoji found, or it can't be used by the bot (not in the server). - - ``discord.PartialEmoji``: If emoji is Unicode. - - ``discord.Emoji``: If emoji is valid and can be used but the bot. + :return: PartialEmoji if emoji is Unicode; Emoji if emoji is custom; + or None if emoji wasn't found or can't be used by the bot (not in + the server?). .. note:: - :py:func:`discord.PartialEmoji.from_str` turns "e" into ```` - this means :py:func:`discord.PartialEmoji.is_unicode_emoji` will return ``True`` because ``id == None`` - (and ``name != None`` is implied?) so it might still raise a NotFound error. + :py:func:`discord.PartialEmoji.from_str` turns "e" into + ```` this means + :py:func:`discord.PartialEmoji.is_unicode_emoji` will return + ``True`` because ``id == None`` (and ``name != None`` is + implied?) so it might still raise a NotFound error. """ if emoji_str is None: return None @@ -164,11 +175,13 @@ async def _unit_autocomplete(itx: discord.Interaction[Bot], current: str): options = options[itx.namespace.mode] if itx.namespace.mode == "currency": return [app_commands.Choice(name=option, value=option) - for option in options if option.lower().startswith(current.lower()) + for option in options + if option.lower().startswith(current.lower()) ][:10] else: return [app_commands.Choice(name=option, value=option) - for option in options if current.lower() in option.lower() + for option in options + if current.lower() in option.lower() ][:25] @@ -185,11 +198,15 @@ async def _role_autocomplete(itx: discord.Interaction[Bot], current: str): current.lower() in role_options[role.id][1].lower()): options.append(role.id) if options: - return [app_commands.Choice(name=role_options[role_id][0], value=role_options[role_id][1]) + return [app_commands.Choice(name=role_options[role_id][0], + value=role_options[role_id][1]) for role_id in options ][:15] else: - return [app_commands.Choice(name="You don't have any roles to remove!", value="none")] + return [ + app_commands.Choice(name="You don't have any roles to remove!", + value="none") + ] class OtherAddons(commands.Cog): @@ -202,26 +219,40 @@ async def on_message(self, message: discord.Message): return if "celcius" in message.content.lower(): + # noinspection LongLine await message.reply( "Fun fact: Celsius was named after a guy named " "['Anders Celsius'](https://en.wikipedia.org/wiki/Anders_Celsius). " - "'Celcius' is therefore an incorrect spelling. :)") + "'Celcius' is therefore an incorrect spelling. :)" + ) @app_commands.command(name="convert_unit", - description="Convert temperature or distance from imperial to metric etc.") + description="Convert temperature or distance from " + "imperial to metric etc." + ) @app_commands.choices(mode=[ - discord.app_commands.Choice(name='Currency (USD, EUR, CAD, etc.)', value="currency"), - discord.app_commands.Choice(name='Length (miles,km,inch)', value="length"), - discord.app_commands.Choice(name='Speed (mph, km/h, m/s, etc.)', value="speed"), - discord.app_commands.Choice(name='Surface area (sq.ft., m^2)', value="surface area"), - discord.app_commands.Choice(name='Temperature (Fahrenheit, C, K, etc.)', value="temperature"), - discord.app_commands.Choice(name='Time (year, minute, sec, etc.)', value="time"), - discord.app_commands.Choice(name='Volume (m^3)', value="volume"), - discord.app_commands.Choice(name='Weight (pounds, ounces, kg, gram, etc.)', value="weight"), + discord.app_commands.Choice( + name='Currency (USD, EUR, CAD, etc.)', value="currency"), + discord.app_commands.Choice( + name='Length (miles,km,inch)', value="length"), + discord.app_commands.Choice( + name='Speed (mph, km/h, m/s, etc.)', value="speed"), + discord.app_commands.Choice( + name='Surface area (sq.ft., m^2)', value="surface area"), + discord.app_commands.Choice( + name='Temperature (Fahrenheit, C, K, etc.)', value="temperature"), + discord.app_commands.Choice( + name='Time (year, minute, sec, etc.)', value="time"), + discord.app_commands.Choice( + name='Volume (m^3)', value="volume"), + discord.app_commands.Choice( + name='Weight (pounds, ounces, kg, gram, etc.)', value="weight"), ]) - @app_commands.describe(mode="What category of unit do you want to convert", - from_unit="Which unit do you want to convert from?", - public="Do you want to share the result with everyone in chat?") + @app_commands.describe( + mode="What category of unit do you want to convert", + from_unit="Which unit do you want to convert from?", + public="Do you want to share the result with everyone in chat?" + ) @app_commands.rename(from_unit='from') @app_commands.autocomplete(from_unit=_unit_autocomplete) @app_commands.rename(from_unit='to') @@ -237,18 +268,24 @@ async def convert_unit( ): rates = conversion_rates.copy() if mode not in rates: - await itx.response.send_message("You didn't give a valid conversion method/mode!", ephemeral=True) + await itx.response.send_message( + "You didn't give a valid conversion method/mode!", + ephemeral=True + ) return if mode == "currency": - # more info: https://docs.openexchangerates.org/reference/latest-json + # more info: https://docs.openexchangerates.org/reference/latest-json # noqa api_key = itx.client.api_tokens['Open Exchange Rates'] + # todo: use requests `params` response_api = requests.get( f"https://openexchangerates.org/api/latest.json?app_id={api_key}&show_alternative=true").text data = json.loads(response_api) if data.get("error", 0): - await itx.response.send_message("I'm sorry, something went wrong while trying to get " - "the latest currency exchange rates", - ephemeral=True) + await itx.response.send_message( + "I'm sorry, something went wrong while trying to get " + "the latest currency exchange rates", + ephemeral=True + ) return options = {x: [0, data['rates'][x], x] for x in data['rates']} from_unit = from_unit.upper() @@ -256,9 +293,11 @@ async def convert_unit( else: options = rates[mode] if from_unit not in options or to_unit not in options: - await itx.response.send_message("Your unit conversion things need to be options that are in the " - "list/database (as given by the autocomplete)!", - ephemeral=True) + await itx.response.send_message( + "Your unit conversion things need to be options that are " + "in the list/database (as given by the autocomplete)!", + ephemeral=True + ) return base_value = (value + options[from_unit][0]) / options[from_unit][1] # base_value is essentially the "x" in the conversion rates @@ -270,20 +309,34 @@ async def convert_unit( result = round(result, 12) if mode == "currency": await itx.response.send_message( - f"Converting {mode} from {value} {from_unit} to {result} {options[to_unit][2]}", ephemeral=not public) + f"Converting {mode} from {value} {from_unit} to " + f"{result} {options[to_unit][2]}", + ephemeral=not public + ) else: await itx.response.send_message( - f"Converting {mode} from {value} {from_unit} to {to_unit}: {result} {options[to_unit][2]}", - ephemeral=not public) - - @app_commands.command(name="add_poll_reactions", description="Make rina add an upvote/downvote emoji to a message") - @app_commands.describe(message_id="What message do you want to add the votes to?", - upvote_emoji="What emoji do you want to react first?", - downvote_emoji="What emoji do you want to react second?", - neutral_emoji="Neutral emoji option (placed between the up/downvote)") + f"Converting {mode} from {value} {from_unit} to " + f"{to_unit}: {result} {options[to_unit][2]}", + ephemeral=not public + ) + + @app_commands.command( + name="add_poll_reactions", + description="Make rina add an upvote/downvote emoji to a message" + ) + @app_commands.describe( + message_id="What message do you want to add the votes to?", + upvote_emoji="What emoji do you want to react first?", + downvote_emoji="What emoji do you want to react second?", + neutral_emoji="Neutral emoji option (placed between the up/downvote)" + ) async def add_poll_reactions( - self, itx: discord.Interaction[Bot], message_id: str, - upvote_emoji: str, downvote_emoji: str, neutral_emoji: str | None = None + self, + itx: discord.Interaction[Bot], + message_id: str, + upvote_emoji: str, + downvote_emoji: str, + neutral_emoji: str | None = None ): if not is_in_dms(itx.guild) and not itx.client.is_module_enabled( itx.guild, ModuleKeys.poll_reactions): @@ -299,35 +352,46 @@ async def add_poll_reactions( except discord.errors.NotFound: errors.append("- I couldn't find a message with this ID!") except discord.errors.Forbidden: - errors.append("- I'm not allowed to find a message in this channel!") + errors.append( + "- I'm not allowed to find a message in this channel!") except discord.errors.HTTPException: errors.append("- Fetching the message failed.") else: errors.append("- The message ID needs to be a number!") - upvote_emoji: MaybeEmoji = _get_emoji_from_str(itx.client, upvote_emoji) + upvote_emoji: MaybeEmoji = _get_emoji_from_str( + itx.client, upvote_emoji) if upvote_emoji is None: - errors.append("- I can't use this upvote emoji! (perhaps it's a nitro emoji)") + errors.append("- I can't use this upvote emoji! " + "(perhaps it's a nitro emoji)") - downvote_emoji: MaybeEmoji = _get_emoji_from_str(itx.client, downvote_emoji) + downvote_emoji: MaybeEmoji = _get_emoji_from_str( + itx.client, downvote_emoji) if downvote_emoji is None: - errors.append("- I can't use this downvote emoji! (perhaps it's a nitro emoji)") + errors.append("- I can't use this downvote emoji! " + "(perhaps it's a nitro emoji)") if neutral_emoji is None: neutral_emoji: MaybeEmoji = _get_emoji_from_str( itx.client, neutral_emoji) if neutral_emoji is None: - errors.append("- I can't use this neutral emoji! (perhaps it's a nitro emoji)") + errors.append("- I can't use this neutral emoji! " + "(perhaps it's a nitro emoji)") blacklisted_channels = itx.client.get_guild_attribute( itx.guild, AttributeKeys.poll_reaction_blacklisted_channels) if (blacklisted_channels is not None and itx.channel.id in blacklisted_channels): - errors.append("- :no_entry: You are not allowed to use this command in this channel!") + errors.append("- :no_entry: You are not allowed to use this " + "command in this channel!") if errors or not message: - await itx.response.send_message("Couldn't add poll reactions:\n" + '\n'.join(errors), ephemeral=True) + await itx.response.send_message( + "Couldn't add poll reactions:\n" + + '\n'.join(errors), + ephemeral=True + ) return try: @@ -336,39 +400,64 @@ async def add_poll_reactions( if neutral_emoji: await message.add_reaction(neutral_emoji) await message.add_reaction(downvote_emoji) - await itx.edit_original_response(content=":white_check_mark: Successfully added emojis") + await itx.edit_original_response( + content=":white_check_mark: Successfully added emojis") except discord.errors.Forbidden: - await itx.edit_original_response(content=":no_entry: Couldn't add all poll reactions: Forbidden " - "(maybe the user you're trying to add reactions to has " - "blocked Rina)") + await itx.edit_original_response( + content=":no_entry: Couldn't add all poll reactions: " + "Forbidden (maybe the user you're trying to add " + "reactions to has blocked Rina)" + ) + return except discord.errors.NotFound: - await itx.edit_original_response(content=":no_entry: Couldn't add all poll reactions: (at least) " - "one of the emojis was not a real emoji!") + await itx.edit_original_response( + content=":no_entry: Couldn't add all poll reactions: " + "(at least) one of the emojis was not a real emoji!" + ) except discord.errors.HTTPException as ex: if ex.status == 400: - await itx.edit_original_response(content=":no_entry: Couldn't add all poll reactions: " - "(at least) one of the emojis was not a real emoji!") + await itx.edit_original_response( + content=":no_entry: Couldn't add all poll reactions: " + "(at least) one of the emojis was not a real " + "emoji!" + ) else: - await itx.edit_original_response(content=":warning: Adding emojis failed!") + await itx.edit_original_response( + content=":warning: Adding emojis failed!" + ) cmd_mention = itx.client.get_command_mention("add_poll_reactions") if not is_in_dms(itx.guild): - await log_to_guild(itx.client, itx.guild, - f"{itx.user.name} ({itx.user.id}) used {cmd_mention} on message {message.jump_url}") + await log_to_guild( + itx.client, + itx.guild, + f"{itx.user.name} ({itx.user.id}) used {cmd_mention} " + f"on message {message.jump_url}" + ) @app_commands.command(name="get_rina_command_mention", - description="Sends a hidden command mention for your command") - @app_commands.describe(command="Command to get a mention for (with/out slash)") - async def find_command_mention_itx(self, itx: discord.Interaction[Bot], command: str): + description="Sends a hidden command mention for " + "your command" + ) + @app_commands.describe( + command="Command to get a mention for (with/out slash)" + ) + async def find_command_mention_itx( + self, + itx: discord.Interaction[Bot], + command: str + ): command = command.removeprefix("/").lower() try: app_commands.commands.validate_name(command) except ValueError: await itx.response.send_message( - "Heads up: your command contains unavailable characters. Discord only allows " - "names to be between 1-32 characters and contain only lower-case letters, " - "numbers, hyphens, underscores, or spaces (for command groups).", - ephemeral=True) + "Heads up: your command contains unavailable characters. " + "Discord only allows names to be between 1-32 characters " + "and contain only lower-case letters, numbers, hyphens, " + "underscores, or spaces (for command groups).", + ephemeral=True + ) return cmd_mention = itx.client.get_command_mention(command) diff --git a/extensions/addons/cogs/searchaddons.py b/extensions/addons/cogs/searchaddons.py index bf1dfdd..728b75b 100644 --- a/extensions/addons/cogs/searchaddons.py +++ b/extensions/addons/cogs/searchaddons.py @@ -8,8 +8,12 @@ from resources.customs import Bot from extensions.addons.equaldexregion import EqualDexRegion -from extensions.addons.views.equaldex_additionalinfo import EqualDexAdditionalInfo -from extensions.addons.views.math_sendpublicbutton import SendPublicButtonMath +from extensions.addons.views.equaldex_additionalinfo import ( + EqualDexAdditionalInfo +) +from extensions.addons.views.math_sendpublicbutton import ( + SendPublicButtonMath +) STAFF_CONTACT_CHECK_WAIT_MIN = 5000 @@ -20,8 +24,14 @@ class SearchAddons(commands.Cog): def __init__(self): pass - @app_commands.command(name="equaldex", description="Find info about LGBTQ+ laws in different countries!") - @app_commands.describe(country_id="What country do you want to know more about? (GB, US, AU, etc.)") + @app_commands.command( + name="equaldex", + description="Find info about LGBTQ+ laws in different countries!" + ) + @app_commands.describe( + country_id="What country do you want to know more about? " + "(GB, US, AU, etc.)" + ) async def equaldex(self, itx: discord.Interaction[Bot], country_id: str): illegal_characters = "" for char in country_id.lower(): @@ -30,15 +40,25 @@ async def equaldex(self, itx: discord.Interaction[Bot], country_id: str): illegal_characters += char if len(illegal_characters) > 1: await itx.response.send_message( - f"You can't use the following characters for country_id!\n> {illegal_characters}", ephemeral=True) + f"You can't use the following characters for country_id!\n" + f"> {illegal_characters}", + ephemeral=True + ) return equaldex_key = itx.client.api_tokens["Equaldex"] - querystring = {"regionid": country_id, "apiKey": equaldex_key} + querystring = { + "regionid": country_id, + "apiKey": equaldex_key, + # "formatted": True, + } response = requests.get( - "https://www.equaldex.com/api/region", params=querystring) # &formatted=true + "https://www.equaldex.com/api/region", + params=querystring + ) response_api = response.text - # returns ->
{"regions":{...}}
<- so you need to remove the
 and 
parts - # it also has some
\r\n strings in there for some reason...? so uh + # returns ->
{"regions":{...}}
<- so you need to + # remove the
 and 
parts. It also has some + #
\r\n strings in there for some reason...? so uh jsonizing_table = { r"
\r\n": r"\n", "
": "",
@@ -53,20 +73,31 @@ async def equaldex(self, itx: discord.Interaction[Bot], country_id: str):
         data = json.loads(response_api)
         if "error" in data and response.status_code == 404:
             if country_id.lower() == "uk":
-                await itx.response.send_message(f"I'm sorry, I couldn't find '{country_id}'...\nTry 'GB' instead!",
-                                                ephemeral=True)
+                await itx.response.send_message(
+                    f"I'm sorry, I couldn't find '{country_id}'...\n"
+                    f"Try 'GB' instead!",
+                    ephemeral=True)
             else:
-                await itx.response.send_message(f"I'm sorry, I couldn't find '{country_id}'...", ephemeral=True)
+                await itx.response.send_message(
+                    f"I'm sorry, I couldn't find '{country_id}'...",
+                    ephemeral=True
+                )
             return
         elif "error" in data:
             if country_id != country_id.upper():
-                await itx.response.send_message(f"Error code: {response.status_code}\n"
-                                                f"I'm sorry, I couldn't find '{country_id}'.\n"
-                                                f"Have you tried using uppercase? Try with "
-                                                f"`country_id:{country_id.upper()}`", ephemeral=True)
+                await itx.response.send_message(
+                    f"Error code: {response.status_code}\n"
+                    f"I'm sorry, I couldn't find '{country_id}'.\n"
+                    f"Have you tried using uppercase? Try with "
+                    f"`country_id:{country_id.upper()}`",
+                    ephemeral=True
+                )
             else:
-                await itx.response.send_message(f"Error code: {response.status_code}\n"
-                                                f"I'm sorry, I couldn't find '{country_id}'.", ephemeral=True)
+                await itx.response.send_message(
+                    f"Error code: {response.status_code}\n"
+                    f"I'm sorry, I couldn't find '{country_id}'.",
+                    ephemeral=True
+                )
             return
 
         region = EqualDexRegion(data['regions']['region'])
@@ -76,72 +107,93 @@ async def equaldex(self, itx: discord.Interaction[Bot], country_id: str):
             if type(region.issues[issue]['current_status']) is list:
                 value = "No data"
             else:
-                value = region.issues[issue]['current_status']['value_formatted']
-                # if region.issues[issue]['current_status']['value'] in ['Legal',
-                #                                                        'Equal',
-                #                                                        'No censorship',
-                #                                                        'Legal, '
-                #                                                        'surgery not required',
-                #                                                        "Sexual orientation and gender identity",
-                #                                                        "Recognized"]:
+                # value = region.issues[issue]['current_status']['value_formatted']
+                # if region.issues[issue]['current_status']['value'] in [
+                #     'Legal', 'Equal','No censorship',
+                #     'surgery not required',
+                #     "Sexual orientation and gender identity", "Recognized"
+                # ]:
                 #     value = "❤️ " + value
                 # elif region.issues[issue]['current_status']['value'] in ["Illegal"]:
                 #     value = "🚫 " + value
-                # elif region.issues[issue]['current_status']['value'] in ["Not legally recognized",
-                #                                                          "Not banned",
-                #                                                          "Varies by Region"]:
+                # elif region.issues[issue]['current_status']['value'] in [
+                #     "Not legally recognized", "Not banned", "Varies by Region"
+                # ]:
                 #     value = "🟨 " + value
                 # else:
                 #     value = "➖ " + value
-                if len(region.issues[issue]['current_status']['description']) > 0:
-                    if len(region.issues[issue]['current_status']['description']) > 200:
-                        value += f" ({region.issues[issue]['current_status']['description'][:200]}..."
+                status_description = \
+                    region.issues[issue]['current_status']['description']
+                description = region.issues[issue]['description']
+                if len(status_description) > 0:
+                    if len(status_description) > 200:
+                        value += f" ({status_description[:200]}..."
                     else:
-                        value += f" ({region.issues[issue]['current_status']['description']})"
-                elif len(region.issues[issue]['description']) > 0:
-                    if len(region.issues[issue]['description']) > 200:
-                        value += f" ({region.issues[issue]['description'][:200]}..."
+                        value += f" ({status_description})"
+                elif len(description) > 0:
+                    if len(description) > 200:
+                        value += f" ({description[:200]}..."
                     else:
-                        value += f" ({region.issues[issue]['description']})"
+                        value += f" ({description})"
                 if len(value) > 1024:
                     value = value[:1020] + "..."
-            embed.add_field(name=region.issues[issue]['label'],
-                            value=value,
-                            inline=False)
+            embed.add_field(
+                name=region.issues[issue]['label'],
+                value=value,
+                inline=False,
+            )
         embed.set_footer(text="For more info, click the button below,")
         view = EqualDexAdditionalInfo(region.url)
-        await itx.response.send_message(embed=embed, view=view, ephemeral=True)
+        await itx.response.send_message(
+            embed=embed,
+            view=view,
+            ephemeral=True
+        )
 
-    @app_commands.command(name="math", description="Ask Wolfram Alpha a question")
+    @app_commands.command(name="math",
+                          description="Ask Wolfram Alpha a question")
     async def math(self, itx: discord.Interaction[Bot], query: str):
-        # todo: shorten function / split, and re-investigate the API docs to see if I can parse stuff better
+        # todo: shorten function / split, and re-investigate the API
+        #  docs to see if I can parse stuff better
         await itx.response.defer(ephemeral=True)
         if query.lower() in ["help", "what is this", "what is this?"]:
             await itx.followup.send(
-                "This is a math command that connects to the Wolfram Alpha website! "
-                "You can ask it math or science questions, and it will answer them for you! Kinda like an AI. "
-                "It uses scientific data. Here are some example queries:\n"
+                "This is a math command that connects to the Wolfram "
+                "Alpha website! You can ask it math or science "
+                "questions, and it will answer them for you! Kinda "
+                "like an AI. It uses scientific data. Here are some "
+                "example queries:\n"
                 "- What is 9+10\n"
                 "- What is the derivative of 4x^2+3x+5\n"
-                "- What color is the sky?", ephemeral=True)
+                "- What color is the sky?",
+                ephemeral=True
+            )
             return
         if "&" in query:
             await itx.followup.send(
-                "Your query cannot contain an ampersand (&/and symbol)! (it can mess with the URL)\n"
-                "For the bitwise 'and' operator, try replacing '&' with ' bitwise and '. "
+                "Your query cannot contain an ampersand (&/and symbol)! "
+                "(it can mess with the URL)\n"
+                "For the bitwise 'and' operator, try replacing '&' with "
+                "' bitwise and '. "
                 "Example '4 & 6' -> '4 bitwise and 6'\n"
-                "For other uses, try replacing the ampersand with 'and' or the word(s) it symbolizes.", ephemeral=True)
+                "For other uses, try replacing the ampersand with 'and' "
+                "or the word(s) it symbolizes.",
+                ephemeral=True
+            )
             return
-        query = query.replace("+",
-                              " plus ")
-        # pluses are interpreted as a space (`%20`) in urls. In LaTeX, that can mean multiply
+        query = query.replace("+", " plus ")
+        # pluses are interpreted as a space (`%20`) in urls. In LaTeX,
+        #  that can mean multiply.
         api_key = itx.client.api_tokens['Wolfram Alpha']
         try:
             data = requests.get(
                 f"https://api.wolframalpha.com/v2/query?appid={api_key}&input={query}&output=json").json()
         except requests.exceptions.JSONDecodeError:
-            await itx.followup.send("Your input gave a malformed result! Perhaps it took too long to calculate...",
-                                    ephemeral=True)
+            await itx.followup.send(
+                "Your input gave a malformed result! Perhaps it took "
+                "too long to calculate...",
+                ephemeral=True
+            )
             return
 
         data = data["queryresult"]
@@ -154,74 +206,103 @@ async def math(self, itx: discord.Interaction[Bot], query: str):
                 subpods = []
                 if pod["id"] == "Input":
                     for subpod in pod["subpods"]:
-                        subpods.append(subpod["plaintext"].replace("\n", "\n>     "))
+                        subpods.append(
+                            subpod["plaintext"].replace("\n", "\n>     ")
+                        )
                     interpreted_input = '\n> '.join(subpods)
                 if pod["id"] == "Result":
                     for subpod in pod["subpods"]:
-                        if subpod.get("nodata") or subpod.get("error"):  # error or nodata == True
+                        if subpod.get("nodata") or subpod.get("error"):
+                            # error or nodata == True
                             error_or_nodata += 1
-                        subpods.append(subpod["plaintext"].replace("\n", "\n>     "))
+                        subpods.append(
+                            subpod["plaintext"].replace("\n", "\n>     ")
+                        )
                     output = '\n> '.join(subpods)
                 elif pod.get("primary", False):
                     for subpod in pod["subpods"]:
                         if len(subpod["plaintext"]) == 0:
                             continue
-                        if subpod.get("nodata") or subpod.get("error"):  # error or nodata == True
+                        if subpod.get("nodata") or subpod.get("error"):
+                            # error or nodata == True
                             error_or_nodata += 1
-                        other_primary_outputs.append(subpod["plaintext"].replace("\n", "\n>     "))
+                        other_primary_outputs.append(
+                            subpod["plaintext"].replace("\n", "\n>     ")
+                        )
             if len(output) == 0 and len(other_primary_outputs) == 0:
                 error_or_nodata = 0
-                # if there is no result and all other pods are 'primary: False'
+                # if there is no result and all other pods are
+                #  'primary: False'
                 for pod in data["pods"]:
                     if pod["id"] not in ["Input", "Result"]:
                         for subpod in pod["subpods"]:
                             if len(subpod["plaintext"]) == 0:
                                 continue
-                            if subpod.get("nodata") or subpod.get("error"):  # error or nodata == True
+                            if subpod.get("nodata") or subpod.get("error"):
+                                # error or nodata == True
                                 error_or_nodata += 1
-                            other_primary_outputs.append(subpod["plaintext"].replace("\n", "\n>     "))
+                            other_primary_outputs.append(
+                                subpod["plaintext"].replace("\n", "\n>     ")
+                            )
             if len(other_primary_outputs) > 0:
                 other_results = '\n> '.join(other_primary_outputs)
                 other_results = "\nOther results:\n> " + other_results
             else:
                 other_results = ""
-            if len(other_primary_outputs) + bool(len(output)) <= error_or_nodata:
-                # if there are more or an equal amount of errors as there are text entries
-                await itx.followup.send("There was no data for your answer!\n"
-                                        "It seems all your answers had an error or were 'nodata entries', meaning "
-                                        "you might need to try a different query to get an answer to your question!",
-                                        ephemeral=True)
+            data_count = len(other_primary_outputs) + bool(len(output))
+            if data_count <= error_or_nodata:
+                # if there are more or an equal amount of errors as
+                #  there are text entries
+                await itx.followup.send(
+                    "There was no data for your answer!\n"
+                    "It seems all your answers had an error or were "
+                    "'nodata entries', meaning you might need to try a "
+                    "different query to get an answer to your question!",
+                    ephemeral=True)
                 return
             assumptions = []
             if "assumptions" in data:
                 if type(data["assumptions"]) is dict:
-                    # only 1 assumption, instead of a list. So just make a list of 1 assumption instead.
+                    # only 1 assumption, instead of a list. So just make
+                    #  a list of 1 assumption instead.
                     data["assumptions"] = [data["assumptions"]]
                 for assumption in data.get("assumptions", []):
-                    assumption_data = {}  # because Wolfram|Alpha is being annoyingly inconsistent.
+                    assumption_data = {}
+                    # because Wolfram|Alpha is being annoyingly
+                    #  inconsistent.
                     if "word" in assumption:
                         assumption_data["${word}"] = assumption["word"]
                     if type(assumption["values"]) is dict:
-                        # only 1 value, instead of a list. So just make a list of 1 value instead.
+                        # only 1 value, instead of a list. So just make
+                        #  a list of 1 value instead.
                         assumption["values"] = [assumption["values"]]
                     for value_index in range(len(assumption["values"])):
-                        assumption_data["${desc" + str(value_index + 1) + "}"] = \
-                            assumption["values"][value_index]["desc"]
+                        assumption_data["${desc" + str(value_index + 1) + "}"] \
+                            = assumption["values"][value_index]["desc"]
                         try:
-                            assumption_data["${word" + str(value_index + 1) + "}"] = \
-                                assumption["values"][value_index]["word"]
+                            assumption_data["${word" + str(value_index + 1) + "}"] \
+                                = assumption["values"][value_index]["word"]
                         except KeyError:
-                            pass  # the "word" variable is only there sometimes. for some stupid reason.
+                            # the "word" variable is only there
+                            #  sometimes. for some stupid reason.
+                            pass
 
                     if "template" in assumption:
                         template: str = assumption["template"]
                         for replacer in assumption_data:
-                            template = template.replace(replacer, assumption_data[replacer])
+                            template = template.replace(
+                                replacer, assumption_data[replacer]
+                            )
                         if template.endswith("."):
                             template = template[:-1]
                         assumptions.append(template + "?")
                     else:
-                        template: str = assumption["type"] + " - " + assumption["desc"] + " (todo)"
+                        template: str = (
+                            assumption["type"]
+                            + " - "
+                            + assumption["desc"]
+                            + " (todo)"
+                        )
                         assumptions.append(template)
             if len(assumptions) > 0:
                 alternatives = "\nAssumptions:\n> " + '\n> '.join(assumptions)
@@ -229,7 +310,8 @@ async def math(self, itx: discord.Interaction[Bot], query: str):
                 alternatives = ""
             warnings = []
             if "warnings" in data:
-                # not sure if multiple warnings will be stored into a list instead
+                # not sure if multiple warnings will be stored into a
+                #  list instead.
                 # Edit: Turns out they do.
                 if type(data["warnings"]) is list:
                     for warning in data["warnings"]:
@@ -237,11 +319,18 @@ async def math(self, itx: discord.Interaction[Bot], query: str):
                 else:
                     warnings.append(data["warnings"]["text"])
             if len(data.get("timedout", "")) > 0:
-                warnings.append("Timed out: " + data["timedout"].replace(",", ", "))
+                warnings.append(
+                    "Timed out: "
+                    + data["timedout"].replace(",", ", ")
+                )
             if len(data.get("timedoutpods", "")) > 0:
-                warnings.append("Timed out pods: " + data["timedout"].replace(",", ", "))
+                warnings.append(
+                    "Timed out pods: "
+                    + data["timedout"].replace(",", ", ")
+                )
             if len(warnings) > 0:
-                warnings = "\nWarnings:\n> " + '\n> '.join(warnings)
+                warnings = ("\nWarnings:\n> "
+                            + '\n> '.join(warnings))
             else:
                 warnings = ""
             view = SendPublicButtonMath()
@@ -259,9 +348,13 @@ async def math(self, itx: discord.Interaction[Bot], query: str):
             if data["error"]:
                 code = data["error"]["code"]
                 message = data["error"]["msg"]
-                await itx.followup.send(f"I'm sorry, but I wasn't able to give a response to that!\n"
-                                        f"> code: {code}\n"
-                                        f"> message: {message}", ephemeral=True)
+                await itx.followup.send(
+                    f"I'm sorry, but I wasn't able to give a response "
+                    f"to that!\n"
+                    f"> code: {code}\n"
+                    f"> message: {message}",
+                    ephemeral=True
+                )
                 return
             elif "didyoumeans" in data:
                 didyoumeans = {}
@@ -269,39 +362,68 @@ async def math(self, itx: discord.Interaction[Bot], query: str):
                     for option in data["didyoumeans"]:
                         didyoumeans[option["score"]] = option["val"]
                 else:
-                    didyoumeans[data["didyoumeans"]["score"]] = data["didyoumeans"]["val"]
-                options_sorted = sorted(didyoumeans.items(), key=lambda x: float(x[0]), reverse=True)
+                    didyoumeans[data["didyoumeans"]["score"]] \
+                        = data["didyoumeans"]["val"]
+                options_sorted = sorted(
+                    didyoumeans.items(),
+                    key=lambda x: float(x[0]),
+                    reverse=True
+                )
                 options = [value for _, value in options_sorted]
                 options_str = "\n> ".join(options)
-                await itx.followup.send(f"I'm sorry, but I wasn't able to give a response to that! However, here "
-                                        f"are some possible improvements to your prompt:\n"
-                                        f"> {options_str}", ephemeral=True)
+                await itx.followup.send(
+                    f"I'm sorry, but I wasn't able to give a response to "
+                    f"that! However, here are some possible improvements "
+                    f"to your prompt:\n"
+                    f"> {options_str}",
+                    ephemeral=True
+                )
                 return
             elif "languagemsg" in data:  # x does not support [language].
-                await itx.followup.send(f"Error:\n> {data['languagemsg']['english']}\n"
-                                        f"> {data['languagemsg']['other']}", ephemeral=True)
+                await itx.followup.send(
+                    f"Error:\n> {data['languagemsg']['english']}\n"
+                    f"> {data['languagemsg']['other']}",
+                    ephemeral=True
+                )
                 return
             elif "futuretopic" in data:  # x does not support [language].
-                await itx.followup.send(f"Error:\n> {data['futuretopic']['topic']}\n"
-                                        f"> {data['futuretopic']['msg']}", ephemeral=True)
+                await itx.followup.send(
+                    f"Error:\n> {data['futuretopic']['topic']}\n"
+                    f"> {data['futuretopic']['msg']}",
+                    ephemeral=True
+                )
                 return
             # why aren't these in the documentation? cmon wolfram, please.
             elif "tips" in data:
                 # not sure if this is put into a list if there are multiple.
-                await itx.followup.send(f"Error:\n> {data['tips']['text']}", ephemeral=True)
+                await itx.followup.send(
+                    f"Error:\n> {data['tips']['text']}",
+                    ephemeral=True
+                )
                 return
             elif "examplepage" in data:
                 # not sure if this is put into a list if there are multiple.
                 await itx.followup.send(
-                    f"Here is an example page for the things you can do with {data['examplepage']['category']}:\n"
-                    f"> {data['examplepage']['url']}", ephemeral=True)
+                    f"Here is an example page for the things you can do with "
+                    f"{data['examplepage']['category']}:\n"
+                    f"> {data['examplepage']['url']}",
+                    ephemeral=True
+                )
                 return
             else:
-                # welp. Apparently you can get *no* info in the output as well!! UGHHHHH
-                await itx.followup.send("Error: No further info\n"
-                                        "It appears you filled in something for which I can't get extra feedback..\n"
-                                        "Feel free to report the situation to MysticMia#7612", ephemeral=True)
+                # welp. Apparently you can get *no* info in the output
+                #  as well!! UGHHHHH
+                await itx.followup.send(
+                    "Error: No further info\n"
+                    "It appears you filled in something for which I can't "
+                    "get extra feedback..\n"
+                    "Feel free to report the situation to MysticMia#7612",
+                    ephemeral=True
+                )
                 return
-        # await itx.followup.send("debug; It seems you reached the end of the function without "
-        #                         "actually getting a response! Please report the query to MysticMia#7612",
-        #                         ephemeral=True)
+        # await itx.followup.send(
+        #     "debug; It seems you reached the end of the function without "
+        #     "actually getting a response! Please report the query to "
+        #     "MysticMia#7612",
+        #     ephemeral=True
+        # )
diff --git a/extensions/addons/views/math_sendpublicbutton.py b/extensions/addons/views/math_sendpublicbutton.py
index b19bad8..c4b7894 100644
--- a/extensions/addons/views/math_sendpublicbutton.py
+++ b/extensions/addons/views/math_sendpublicbutton.py
@@ -10,9 +10,17 @@ def __init__(self, timeout=180):
         self.timeout = timeout
 
     @discord.ui.button(label='Send Publicly', style=discord.ButtonStyle.gray)
-    async def send_publicly(self, itx: discord.Interaction[Bot], _button: discord.ui.Button):
+    async def send_publicly(
+            self,
+            itx: discord.Interaction[Bot],
+            _button: discord.ui.Button
+    ):
         self.value = 1
         await itx.response.edit_message(content="Sent successfully!")
         cmd_mention = itx.client.get_command_mention("math")
-        await itx.followup.send(f"**{itx.user.mention} shared a {cmd_mention} output:**\n" + itx.message.content,
-                                ephemeral=False, allowed_mentions=discord.AllowedMentions.none())
+        await itx.followup.send(
+            f"**{itx.user.mention} shared a {cmd_mention} output:**\n"
+            + itx.message.content,
+            ephemeral=False,
+            allowed_mentions=discord.AllowedMentions.none()
+        )
diff --git a/extensions/compliments/cogs/compliments.py b/extensions/compliments/cogs/compliments.py
index afbc8f9..5969b2c 100644
--- a/extensions/compliments/cogs/compliments.py
+++ b/extensions/compliments/cogs/compliments.py
@@ -1,4 +1,8 @@
-import random  # random compliment from list, random user pronouns from their role list, and random keyboard mash
+import random
+import typing
+
+# ^ random compliment from list, random user pronouns from their role
+#  list, and random keyboard mash.
 
 import motor.core
 
@@ -10,7 +14,8 @@
 from resources.checks.command_checks import module_not_disabled_check
 from resources.customs import Bot
 from resources.utils.utils import log_to_guild
-# ^ to warn when bot can't add headpat reaction (typically because user blocked Rina)
+# ^ to warn when bot can't add headpat reaction (typically because
+#  user blocked Rina)
 
 from extensions.compliments.views import ConfirmPronounsView
 
@@ -21,6 +26,7 @@ async def _choose_and_send_compliment(
         compliment_type: str,
         async_rina_db: motor.core.AgnosticDatabase
 ):
+    # todo: split function into multiple functions
     quotes = {
         "fem_quotes": [
             # "Was the sun always this hot? or is it because of you?",
@@ -33,7 +39,8 @@ async def _choose_and_send_compliment(
             "Amazing! Perfect! Beautiful! How **does** she do it?!",
             "I can tell that you are a very special and talented girl!",
             "Here, have this cute sticker!",
-            "Beep boop :zap: Oh no! my circuits overloaded! Her cuteness was too much for me to handle!",
+            ("Beep boop :zap: Oh no! my circuits overloaded! Her cuteness was "
+             "too much for me to handle!"),
         ],
         "masc_quotes": [
             "You are the best man out there.",
@@ -47,9 +54,8 @@ async def _choose_and_send_compliment(
             "You're such a gentleman!",
             "You always know how to make people feel welcome and included :D",
             "Your intelligence and knowledge never cease to amaze me :O",
-            "Beep boop :zap: Oh no! my circuits overloaded! His aura was so strong that I couldn't generate a cool "
-            "compliment!",
-
+            ("Beep boop :zap: Oh no! my circuits overloaded! His aura was so "
+             "strong that I couldn't generate a cool compliment!"),
         ],
         "they_quotes": [
             "I can tell that you are a very special and talented person!",
@@ -58,11 +64,15 @@ async def _choose_and_send_compliment(
         "it_quotes": [
             "I bet you do the crossword puzzle in ink!",
         ],
-        "unisex_quotes": [  # unisex quotes are added to each of the other quotes later on.
+        "unisex_quotes": [
+            # unisex quotes are added to each of the other quotes later on.
             "Hey I have some leftover cookies.. \\*wink wink\\*",
-            # "_Let me just hide this here-_ hey wait, are you looking?!", # it were meant to be cookies TwT
+            # "_Let me just hide this here-_ hey wait, are you looking?!",
+            # ^ it was meant to be cookies TwT
             "Would you like a hug?",
-            # "Would you like to walk in the park with me? I gotta walk my catgirls", # got misinterpreted too
+            # ("Would you like to walk in the park with me? I gotta walk"
+            #  " my catgirls"),
+            # ^ got misinterpreted too
             "morb",
             "You look great today!",
             "You light up the room!",
@@ -75,9 +85,12 @@ async def _choose_and_send_compliment(
             "You always know how to put a positive spin on things!",
             "You make the world a better place just by being in it",
             "Your strength and resilience is truly inspiring.",
-            "You have a contagious positive attitude that lifts up those around you.",
-            "Your positive energy is infectious and makes everyone feel welcomed!",
-            "You have a great sense of style and always look so put together <3",
+            ("You have a contagious positive attitude that lifts up "
+             "those around you."),
+            ("Your positive energy is infectious and makes everyone "
+             "feel welcomed!"),
+            ("You have a great sense of style and always look so put "
+             "together <3"),
             "You are a truly unique and wonderful person!",
         ]
     }
@@ -95,7 +108,7 @@ async def _choose_and_send_compliment(
         else:
             quotes[x] += quotes["unisex_quotes"]
 
-    collection = async_rina_db["complimentblacklist"]
+    collection = async_rina_db["complimentblacklist"]  # todo: use DatabaseKeys
     query = {"user": user.id}
     search: dict[str, int | list] = await collection.find_one(query)
     blacklist: list = []
@@ -121,48 +134,75 @@ async def _choose_and_send_compliment(
                 dec += 1
     if len(quotes[compliment_type]) == 0:
         quotes[compliment_type].append(
-            "No compliment quotes could be given... You and/or this person have blacklisted every quote.")
+            "No compliment quotes could be given... You and/or this person "
+            "have blacklisted every quote."
+        )
 
     base = f"{itx.user.mention} complimented {user.mention}!\n"
     # cmd_mention = client.get_command_mention("developer_request")
     # cmd_mention1 = client.get_command_mention("complimentblacklist")
-    suffix = ""  # ("\n\nPlease give suggestions for compliments! DM <@262913789375021056>, make a staff ticket, "
-    #               "or use {cmd_mention} to suggest one. Do you dislike this compliment? Use {cmd_mention1} "
-    #               "`location:being complimented` `mode:Add` `string: ` and block specific words (or the "
-    #               "letters \"e\" and \"o\" to block every compliment")
-    if itx.response.is_done():  # should happen if user used the modal to select a pronoun role
+    suffix = ""  # (
+    #     f"\n\nPlease give suggestions for compliments! DM "
+    #     f"<@262913789375021056>, make a staff ticket, or use {cmd_mention} "
+    #     f"to suggest one. Do you dislike this compliment? Use "
+    #     f"{cmd_mention1} `location:being complimented` `mode:Add` "
+    #     f"`string: ` and block specific words (or the letters \"e\" and "
+    #     f"\"o\" to block every compliment"
+    # )
+    if itx.response.is_done():  # todo: add "give compliment back" button
+        # should happen if user used the modal to select a pronoun role
         try:
-            await itx.channel.send(content=base + random.choice(quotes[compliment_type]) + suffix,
-                                   allowed_mentions=discord.AllowedMentions(everyone=False, users=[user], roles=False,
-                                                                            replied_user=False))
-        except discord.Forbidden:  # in channel where rina is not allowed to send
-            await itx.followup.send(content=base + random.choice(quotes[compliment_type]) + suffix,
-                                    allowed_mentions=discord.AllowedMentions(everyone=False, users=[user], roles=False,
-                                                                             replied_user=False))
+            # todo: make a util function to "send or followup")
+            await itx.channel.send(
+                content=base + random.choice(quotes[compliment_type]) + suffix,
+                allowed_mentions=discord.AllowedMentions(
+                    everyone=False, users=[user], roles=False,
+                    replied_user=False)
+            )
+        except discord.Forbidden:
+            # can't send in channel, follow up to interaction instead.
+            await itx.followup.send(
+                content=base + random.choice(quotes[compliment_type]) + suffix,
+                allowed_mentions=discord.AllowedMentions(
+                    everyone=False, users=[user], roles=False,
+                    replied_user=False)
+            )
     else:
-        await itx.response.send_message(base + random.choice(quotes[compliment_type]) + suffix,
-                                        allowed_mentions=discord.AllowedMentions(everyone=False, users=[user],
-                                                                                 roles=False, replied_user=False))
+        await itx.response.send_message(
+            base + random.choice(quotes[compliment_type]) + suffix,
+            allowed_mentions=discord.AllowedMentions(
+                everyone=False, users=[user], roles=False, replied_user=False)
+        )
 
 
-async def _send_confirm_gender_modal(client: Bot, itx: discord.Interaction[Bot], user: discord.User | discord.Member):
+async def _send_confirm_gender_modal(
+        client: Bot,
+        itx: discord.Interaction[Bot],
+        user: discord.User | discord.Member
+) -> None:
     # Define a simple View that gives us a confirmation menu
     view = ConfirmPronounsView(timeout=60)
     await itx.response.send_message(
-        f"{user.mention} doesn't have any pronoun roles! Which pronouns would like to use for the compliment?",
+        f"{user.mention} doesn't have any pronoun roles! Which pronouns "
+        f"would like to use for the compliment?",
         view=view, ephemeral=True)
     await view.wait()
     if view.value is None:
         await itx.edit_original_response(content=':x: Timed out...', view=None)
     else:
-        await _choose_and_send_compliment(itx, user, view.value, client.async_rina_db)
+        await _choose_and_send_compliment(itx, user, view.value,
+                                          client.async_rina_db)
 
 
-async def _rina_used_deflect_and_it_was_very_effective(message):
+async def _rina_used_deflect_and_it_was_very_effective(
+        message: discord.Message
+) -> None:
     """
-    Rina's secret superpower: deflecting compliments :>. Don't question it.
+    Rina's secret superpower: deflecting compliments :>. Don't
+    question it.
 
-    :param message: The message to analyze for compliment reflection purposes.
+    :param message: The message to analyze for compliment
+     reflection purposes.
     """
     responses = [
         "I'm not cute >_<",
@@ -173,20 +213,26 @@ async def _rina_used_deflect_and_it_was_very_effective(message):
         "I don't think so.",
         "Haha. Good joke. Tell me another tomorrow",
         "No, I'm !cute.",
-        "[shocked] Wha- w. .. w what?? .. NOo? no im nott?\nwhstre you tslking about?",
-        "Oh you were talking to me? I thought you were talking about everyone else here,",
+        ("[shocked] Wha- w. .. w what?? .. NOo? no im nott?\nwhstre you "
+         "tslking about?"),
+        ("Oh you were talking to me? I thought you were talking about "
+         "everyone else here,"),
         "Maybe.. Maybe I am cute.",
         "If the sun was dying, would you still think I was cute?",
         "Awww. Thanks sweety, but you've got the wrong number",
         ":joy: You *reaaally* think so? You've gotta be kidding me.",
-        "If you're gonna be spamming this, .. maybe #general isn't the best channel for that.",
-        "Such nice weather outside, isn't it? What- you asked me a question?\nNo you didn't, you're just "
-        "talking to yourself.",
-        "".join(random.choice("acefgilrsuwnop" * 3 + ";;  " * 2) for _ in range(random.randint(10, 25))),
+        ("If you're gonna be spamming this, .. maybe #general isn't the "
+         "best channel for that."),
+        ("Such nice weather outside, isn't it? What- you asked me a "
+         "question?\nNo you didn't, you're just talking to yourself."),
+        ("".join(random.choice("acefgilrsuwnop" * 3 + ";;  " * 2)
+                 for _ in range(random.randint(10, 25)))),
         # 3:2 letters to symbols
-        "Oh I heard about that! That's a way to get randomized passwords from a transfem!",
-        "Cuties are not gender-specific. For example, my cat is a cutie!\nOh wait, species aren't the same "
-        "as genders. Am I still a catgirl then? Trans-species?",
+        ("Oh I heard about that! That's a way to get randomized passwords "
+         "from a transfem!"),
+        ("Cuties are not gender-specific. For example, my cat is a cutie!\n"
+         "Oh wait, species aren't the same as genders. Am I still a catgirl "
+         "then? Trans-species?"),
         "...",
         "Hey that's not how it works!",
         "Hey my lie detector said you are lying.",
@@ -194,8 +240,10 @@ async def _rina_used_deflect_and_it_was_very_effective(message):
         "k",
         (message.author.nick or message.author.name) + ", stop lying >:C",
         "BAD!",
-        "https://cdn.discordapp.com/emojis/920918513969950750.webp?size=4096&quality=lossless",
-        "[Checks machine]; Huh? Is my lie detector broken? I should fix that..",
+        ("https://cdn.discordapp.com/emojis/920918513969950750.webp"
+         "?size=4096&quality=lossless"),
+        ("[Checks machine]; Huh? Is my lie detector broken? I should "
+         "fix that.."),
     ]
     femme_responses = [
         "If you think I'm cute, then you must be uber-cute!!",
@@ -203,16 +251,19 @@ async def _rina_used_deflect_and_it_was_very_effective(message):
         "You too!",
         "No, you are <3",
         "Nope. I doubt it. There's no way I can be as cute as you",
-        "You gotta praise those around you as well. " + (message.author.nick or message.author.name) +
-        ", for example, is very cute.",
-        "Oh by the way, did I say " + (message.author.nick or message.author.name) +
-        " was cute yet? I probably didn't. " + (message.author.nick or message.author.name) +
-        "? You're very cute",
+        ("You gotta praise those around you as well. "
+         + (message.author.nick or message.author.name)
+         + ", for example, is very cute."),
+        ("Oh by the way, did I say "
+         + (message.author.nick or message.author.name)
+         + " was cute yet? I probably didn't. "
+         + (message.author.nick or message.author.name)
+         + "? You're very cute"),
         "You know I'm not a mirror, right?",
         "*And the oscar for cutest responses goes to..  YOU!!*",
         "You're also part of the cuties set",
-        "Hey, you should be talking about yourself first! After all, how do you keep up with being such "
-        "a cutie all the time?"
+        ("Hey, you should be talking about yourself first! After all, how do "
+         "you keep up with being such a cutie all the time?")
     ]
     # check if user would like femme responses telling them they're cute
     for role in message.author.roles:
@@ -220,17 +271,23 @@ async def _rina_used_deflect_and_it_was_very_effective(message):
             responses += femme_responses
     respond = random.choice(responses)
     if respond == "BAD!":
+        # noinspection LongLine
         await message.channel.send(
             "https://cdn.discordapp.com/emojis/902351699182780468.gif?size=56&quality=lossless",
-            allowed_mentions=discord.AllowedMentions.none())
-    await message.channel.send(respond, allowed_mentions=discord.AllowedMentions.none())
+            allowed_mentions=discord.AllowedMentions.none()
+        )
+    await message.channel.send(
+        respond,
+        allowed_mentions=discord.AllowedMentions.none()
+    )
 
 
 async def _add_to_blacklist(itx, db_location, string):
     """
     Add a string to the command executor's blacklist.
     :param itx: The interaction with the user/executor, and itx.client.
-    :param db_location: Whether to add it to the sending or receiving blacklist.
+    :param db_location: Whether to add it to the sending or
+     receiving blacklist.
     :param string: The string to add to the blacklist.
     :return: The new blacklist.
     """
@@ -241,7 +298,11 @@ async def _add_to_blacklist(itx, db_location, string):
     if search is not None:
         blacklist = search.get(db_location, [])
     blacklist.append(string)
-    await collection.update_one(query, {"$set": {db_location: blacklist}}, upsert=True)
+    await collection.update_one(
+        query,
+        {"$set": {db_location: blacklist}},
+        upsert=True
+    )
     return blacklist
 
 
@@ -249,6 +310,16 @@ class Compliments(commands.Cog):
     def __init__(self, client: Bot):
         self.client = client
 
+    @staticmethod
+    def _contains_cuteness_assignment(msg: str):
+        # todo: upgrade cute-call detection hardware
+        return (
+            ((("cute" or "cutie" or "adorable" in msg)
+              and "not" in msg)
+             or "uncute" in msg)
+            and "not uncute" not in msg
+        )
+
     @commands.Cog.listener()  # Rina reflecting cuteness compliments
     async def on_message(self, message: discord.Message):
         if message.author.bot:
@@ -256,17 +327,20 @@ async def on_message(self, message: discord.Message):
 
         if self.client.user.mention in message.content.split():
             msg = message.content.lower()
-            if (
-                    ((("cute" or "cutie" or "adorable" in msg) and "not" in msg) or "uncute" in msg) and
-                    "not uncute" not in msg
-            ):
+            called_cute: bool | None = self._contains_cuteness_assignment(
+                msg.content.lower())
+            if called_cute is True:
                 try:
                     await message.add_reaction("<:this:960916817801535528>")
                 except (discord.HTTPException or discord.NotFound):
-                    await log_to_guild(self.client, message.guild,
-                                       f"**:warning: Warning: **Couldn't add pat reaction to {message.jump_url}")
+                    await log_to_guild(
+                        self.client,
+                        message.guild,
+                        (f"**:warning: Warning: **Couldn't add pat "
+                         f"reaction to {message.jump_url}")
+                    )
                     raise
-            elif "cutie" in msg or "cute" in msg:
+            elif called_cute is False:
                 await _rina_used_deflect_and_it_was_very_effective(message)
             elif any([x in msg for x in [
                 "can i have a pat",
@@ -295,53 +369,86 @@ async def on_message(self, message: discord.Message):
                 "headpat please"
             ]]):
                 try:
-                    await message.add_reaction("<:TPF_02_Pat:968285920421875744>")  # headpatWait
+                    # todo: make server settings emoji
+                    # todo: make server module toggleable
+                    await message.add_reaction(
+                        "<:TPF_02_Pat:968285920421875744>")
                 except discord.errors.HTTPException:
                     try:
-                        await message.channel.send("Unfortunately I can't give you a headpat (for some reason), "
-                                                   "so have this instead:\n"
-                                                   "<:TPF_02_Pat:968285920421875744>")
+                        await message.channel.send(
+                            "Unfortunately I can't give you a headpat (for "
+                            "some reason), so have this instead:\n"
+                            "<:TPF_02_Pat:968285920421875744>"
+                        )
                     except discord.errors.Forbidden:
                         pass
             else:
                 cmd_mention = self.client.get_command_mention("help")
-                await message.channel.send(f"I use slash commands! Use /`command`  and see what cool things "
-                                           f"might pop up! or try {cmd_mention}\n"
-                                           f"PS: If you're trying to call me cute: no im not",
-                                           delete_after=8)
+                await message.channel.send(
+                    f"I use slash commands! Use /`command`  and see what cool "
+                    f"things might pop up! or try {cmd_mention}\n"
+                    f"PS: If you're trying to call me cute: no im not",
+                    delete_after=8
+                )
 
     @module_not_disabled_check(ModuleKeys.compliments)
-    @app_commands.command(name="compliment", description="Complement someone fem/masc/enby")
+    @app_commands.command(name="compliment",
+                          description="Complement someone fem/masc/enby")
     @app_commands.describe(user="Who do you want to compliment?")
-    async def compliment(self, itx: discord.Interaction[Bot], user: discord.User):
-        # discord.User because discord.Member gets errors.TransformerError in DMs.
-        # Fetch user's roles, and copy the list to prevent modifying the original list of roles
+    async def compliment(
+            self,
+            itx: discord.Interaction[Bot],
+            user: discord.User
+    ):
+        # discord.User because discord.Member gets
+        #  errors.TransformerError in DMs.
         user_roles = getattr(user, "roles", [])[:]
+        # ^ Fetch user's roles, and copy the list to prevent modifying
+        #  the original list of roles.
 
         roles = ["he/him", "she/her", "they/them", "it/its"]
-        random.shuffle(user_roles)  # pick a random order for which pronoun role to pick
+        # pick a random order for which pronoun role to pick
+        random.shuffle(user_roles)
         for role in user_roles:
             if role.name.lower() in roles:  # look for pronoun roles
-                await _choose_and_send_compliment(itx, user, role.name.lower(), itx.client.async_rina_db)
+                await _choose_and_send_compliment(
+                    itx, user, role.name.lower(), itx.client.async_rina_db)
                 return
         await _send_confirm_gender_modal(itx.client, itx, user)
 
-    @app_commands.command(name="complimentblacklist", description="If you dislike words in certain compliments")
+    @app_commands.command(
+        name="complimentblacklist",
+        description="If you dislike words in certain compliments"
+    )
     @app_commands.choices(location=[
-        discord.app_commands.Choice(name='When complimenting someone else', value=1),
-        discord.app_commands.Choice(name='When I\'m being complimented by others', value=2)
+        discord.app_commands.Choice(
+            name='When complimenting someone else', value=1),
+        discord.app_commands.Choice(
+            name='When I\'m being complimented by others', value=2)
     ])
     @app_commands.choices(mode=[
-        discord.app_commands.Choice(name='Add a string to your compliments blacklist', value=1),
-        discord.app_commands.Choice(name='Remove a string from your compliments blacklist', value=2),
-        discord.app_commands.Choice(name='Check your blacklisted strings', value=3)
+        discord.app_commands.Choice(
+            name='Add a string to your compliments blacklist', value=1),
+        discord.app_commands.Choice(
+            name='Remove a string from your compliments blacklist', value=2),
+        discord.app_commands.Choice(
+            name='Check your blacklisted strings', value=3)
     ])
     @app_commands.describe(
-        location="Blacklist when giving compliments / when receiving compliments from others",
-        string="What sentence or word do you want to blacklist? (eg: 'good girl' or 'girl')")
-    async def complimentblacklist(self, itx: discord.Interaction[Bot], location: int, mode: int, string: str = None):
-        itx.response: discord.InteractionResponse[Bot]  # noqa
-        itx.followup: discord.Webhook  # noqa
+        location="Blacklist when giving compliments / when receiving "
+                 "compliments from others",
+        string="What sentence or word do you want to blacklist? "
+               "(eg: 'good girl' or 'girl')"
+    )
+    async def complimentblacklist(
+            self,
+            itx: discord.Interaction[Bot],
+            location: int,
+            mode: int,
+            string: str | None = None
+    ):  # todo: split function into multiple smaller functions
+        itx.response: discord.InteractionResponse[Bot]  # type: ignore
+        itx.followup: discord.Webhook  # type: ignore
         if location == 1:
             db_location = "personal_list"
         elif location == 2:
@@ -352,76 +459,104 @@ async def complimentblacklist(self, itx: discord.Interaction[Bot], location: int
         if mode == 1:  # add an item to the blacklist
             if string is None:
                 await itx.response.send_message(
-                    "With this command, you can blacklist a section of text in compliments. "
-                    "For example, if you don't like being called 'Good girl', you can "
-                    "blacklist this compliment by blacklisting 'good' or 'girl'. \n"
-                    "Or if you don't like hugging people, you can blacklist 'hug'.\n"
-                    "Note: it's case sensitive", ephemeral=True)
+                    "With this command, you can blacklist a section of text "
+                    "in compliments. For example, if you don't like being "
+                    "called 'Good girl', you can blacklist this compliment by "
+                    "blacklisting 'good' or 'girl'. \n"
+                    "Or if you don't like hugging people, you can "
+                    "blacklist 'hug'.\n"
+                    "Note: it's case sensitive",
+                    ephemeral=True
+                )
                 return
             if len(string) > 150:
-                await itx.response.send_message("Please make strings shorter than 150 characters...", ephemeral=True)
+                await itx.response.send_message(
+                    "Please make strings shorter than 150 characters...",
+                    ephemeral=True
+                )
                 return
             await itx.response.defer(ephemeral=True)
             blacklist = await _add_to_blacklist(itx, db_location, string)
             await itx.followup.send(
                 f"Successfully added {repr(string)} to your blacklist. "
-                f"({len(blacklist)} item{'s' * (len(blacklist) != 1)} in your blacklist now)",
+                f"({len(blacklist)} item{'s' * (len(blacklist) != 1)} in your "
+                f"blacklist now)",
                 ephemeral=True)
 
         elif mode == 2:  # Remove item from black list
             if string is None:
-                cmd_mention = itx.client.get_command_mention("complimentblacklist")
+                cmd_mention = itx.client.get_command_mention(
+                    "complimentblacklist")
                 await itx.response.send_message(
-                    f"Type the id of the string you want to remove. To find the id, type {cmd_mention} `mode:Check`.",
+                    f"Type the id of the string you want to remove. To find "
+                    f"the id, type {cmd_mention} `mode:Check`.",
                     ephemeral=True)
                 return
             try:
                 string = int(string)
             except ValueError:
                 await itx.response.send_message(
-                    "To remove a string from your blacklist, you must give the id of the string you want to remove. "
-                    "This should be a number... You didn't give a number...",
+                    "To remove a string from your blacklist, you must give "
+                    "the id of the string you want to remove. This should be "
+                    "a number... You didn't give a number...",
                     ephemeral=True)
                 return
             collection = itx.client.async_rina_db["complimentblacklist"]
             query = {"user": itx.user.id}
-            search = await collection.find_one(query)
-            if search is None:
+            search0: dict[
+                typing.Literal["personal_list", "list"],
+                list[str]] = await collection.find_one(query)
+            if search0 is None:
                 await itx.response.send_message(
-                    "There are no items on your blacklist, so you can't remove any either...", ephemeral=True)
+                    "There are no items on your blacklist, so you can't "
+                    "remove any either...",
+                    ephemeral=True
+                )
                 return
-            blacklist = search.get(db_location, [])
+            blacklist = search0.get(db_location, [])
 
             try:
                 del blacklist[string]
             except IndexError:
-                cmd_mention = itx.client.get_command_mention("complimentblacklist")
+                cmd_mention = itx.client.get_command_mention(
+                    "complimentblacklist")
                 await itx.response.send_message(
-                    f"Couldn't delete that ID, because there isn't any item on your list with that ID. "
-                    f"Use {cmd_mention} `mode:Check` to see the IDs assigned to each item on your list",
+                    f"Couldn't delete that ID, because there isn't any item "
+                    f"on your list with that ID. Use {cmd_mention} "
+                    f"`mode:Check` to see the IDs assigned to each item on "
+                    f"your list",
                     ephemeral=True)
                 return
-            await collection.update_one(query, {"$set": {db_location: blacklist}}, upsert=True)
+            await collection.update_one(
+                query,
+                {"$set": {db_location: blacklist}},
+                upsert=True
+            )
             await itx.response.send_message(
-                f"Successfully removed `{string}` from your blacklist. Your blacklist now contains "
-                f"{len(blacklist)} string{'s' * (len(blacklist) != 1)}.",
+                f"Successfully removed `{string}` from your blacklist. Your "
+                f"blacklist now contains {len(blacklist)} "
+                f"string{'s' * (len(blacklist) != 1)}.",
                 ephemeral=True)
 
         elif mode == 3:  # check
             collection = itx.client.async_rina_db["complimentblacklist"]
             query = {"user": itx.user.id}
-            search: dict[str, int | list] = await collection.find_one(query)
-            if search is None:
-                await itx.response.send_message("There are no strings in your blacklist, so... nothing "
-                                                "to list here...",
-                                                ephemeral=True)
+            search1: dict[str, int | list] = await collection.find_one(query)  # type: ignore # noqa
+            if search1 is None:
+                await itx.response.send_message(
+                    "There are no strings in your blacklist, so... nothing "
+                    "to list here...",
+                    ephemeral=True
+                )
                 return
-            blacklist = search.get(db_location, [])
+            blacklist = search1.get(db_location, [])
             length = len(blacklist)
 
             ans = []
             for blackboard_id in range(length):
                 ans.append(f"`{blackboard_id}`: {blacklist[blackboard_id]}")
             ans = '\n'.join(ans)
-            await itx.response.send_message(f"Found {length} string{'s' * (length != 1)}:\n{ans}"[:2000],
-                                            ephemeral=True)
+            await itx.response.send_message(
+                f"Found {length} string{'s' * (length != 1)}:\n{ans}"[:2000],
+                ephemeral=True
+            )
diff --git a/extensions/compliments/views/confirmpronouns.py b/extensions/compliments/views/confirmpronouns.py
index 9b8f0ce..cbc4778 100644
--- a/extensions/compliments/views/confirmpronouns.py
+++ b/extensions/compliments/views/confirmpronouns.py
@@ -6,38 +6,72 @@
 class ConfirmPronounsView(discord.ui.View):
     def __init__(self, timeout=None):
         super().__init__()
+        # Use the value to track which button was pressed
+        # todo: make self.value (pronouns: she/her, he/him, ...) an enum.
         self.value = None
         self.timeout = timeout
 
-    # When the confirm button is pressed, set the inner value to `True` and
-    # stop the View from listening to more input.
-    # We also send the user an ephemeral message that we're confirming their choice.
     @discord.ui.button(label='She/Her', style=discord.ButtonStyle.green)
-    async def feminine(self, itx: discord.Interaction[Bot], _button: discord.ui.Button):
+    async def feminine(
+            self,
+            itx: discord.Interaction[Bot],
+            _button: discord.ui.Button
+    ):
         self.value = "she/her"
-        await itx.response.edit_message(content='Selected She/Her pronouns for compliment', view=None)
+        await itx.response.edit_message(
+            content='Selected She/Her pronouns for compliment',
+            view=None
+        )
         self.stop()
 
     @discord.ui.button(label='He/Him', style=discord.ButtonStyle.green)
-    async def masculine(self, itx: discord.Interaction[Bot], _button: discord.ui.Button):
+    async def masculine(
+            self,
+            itx: discord.Interaction[Bot],
+            _button: discord.ui.Button
+    ):
         self.value = "he/him"
-        await itx.response.edit_message(content='Selected He/Him pronouns for the compliment', view=None)
+        await itx.response.edit_message(
+            content='Selected He/Him pronouns for the compliment',
+            view=None
+        )
         self.stop()
 
     @discord.ui.button(label='They/Them', style=discord.ButtonStyle.green)
-    async def enby_them(self, itx: discord.Interaction[Bot], _button: discord.ui.Button):
+    async def enby_them(
+            self,
+            itx: discord.Interaction[Bot],
+            _button: discord.ui.Button
+    ):
         self.value = "they/them"
-        await itx.response.edit_message(content='Selected They/Them pronouns for the compliment', view=None)
+        await itx.response.edit_message(
+            content='Selected They/Them pronouns for the compliment',
+            view=None
+        )
         self.stop()
 
     @discord.ui.button(label='It/Its', style=discord.ButtonStyle.green)
-    async def enby_its(self, itx: discord.Interaction[Bot], _button: discord.ui.Button):
+    async def enby_its(
+            self,
+            itx: discord.Interaction[Bot],
+            _button: discord.ui.Button
+    ):
         self.value = "it/its"
-        await itx.response.edit_message(content='Selected It/Its pronouns for the compliment', view=None)
+        await itx.response.edit_message(
+            content='Selected It/Its pronouns for the compliment',
+            view=None
+        )
         self.stop()
 
     @discord.ui.button(label='Unisex/Unknown', style=discord.ButtonStyle.grey)
-    async def unisex(self, itx: discord.Interaction[Bot], _button: discord.ui.Button):
+    async def unisex(
+            self,
+            itx: discord.Interaction[Bot],
+            _button: discord.ui.Button
+    ):
         self.value = "unisex"
-        await itx.response.edit_message(content='Selected Unisex/Unknown gender for the compliment', view=None)
+        await itx.response.edit_message(
+            content='Selected Unisex/Unknown gender for the compliment',
+            view=None
+        )
         self.stop()
diff --git a/extensions/crashhandling/cogs/crashhandling.py b/extensions/crashhandling/cogs/crashhandling.py
index 08b912c..4d76830 100644
--- a/extensions/crashhandling/cogs/crashhandling.py
+++ b/extensions/crashhandling/cogs/crashhandling.py
@@ -1,6 +1,8 @@
 from datetime import datetime, timedelta, timezone
 import traceback  # for crash logging
-import sys  # to stop the program (and automatically restart, thanks to pterodactyl)
+import sys
+# ^ to stop the program (and automatically restart, thanks to
+#  pterodactyl)
 
 import discord
 from discord import app_commands
@@ -31,12 +33,16 @@ async def _send_crash_message(
     """
     Sends crash message to Rina's main logging channel
 
-    :param client: The client to fetch logging channel and send the crash message with.
-    :param error_type: Whether the error an 'Error' or an 'AppCommand Error'
+    :param client: The client to fetch logging channel and send the
+     crash message with.
+    :param error_type: Whether the error an 'Error' or
+     an 'AppCommand Error'
     :param traceback_text: The traceback to send.
-    :param error_source: Name of the error source, displayed at the top of the message. Think of event or command.
+    :param error_source: Name of the error source, displayed at the top
+     of the message. Think of event or command.
     :param color: Color of the discord embed.
-    :param itx: Interaction with a potential guild. This might allow Rina to send the crash log to that guild instead.
+    :param itx: Interaction with a potential guild. This might allow
+     Rina to send the crash log to that guild instead.
     """
     log_channel = None
     if hasattr(itx, "guild"):
@@ -68,18 +74,20 @@ async def _send_crash_message(
         return
 
     error_caps = error_type.upper()
-    debug_message = (f"\n\n\n\n[{datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%S.%fZ')}]"
+    time_prefix = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%S.%fZ')
+    debug_message = (f"\n\n\n\n[{time_prefix}]"
                      f"[{error_caps}]: {error_source}"
                      f"\n\n{traceback_text}\n")
     debug(f"{debug_message}", add_time=False)
 
-    # prevent the code block from being escaped by other inner tick marks
+    # Prevent the code block from being escaped by other inner
+    #  tick marks.
     msg = debug_message.replace("``",
                                 "`` ")
     embeds = []
     total_characters = 0
     while len(msg) > 0 and len(embeds) < 10:
-        # 4090 = 4096 (max description length of embeds) - len(2 * "```")
+        # 4090 = 4096 (max embed description length) - len(2 * "```")
         total_characters += len(error_type + ' Log')
         if total_characters < 7:
             # be able to place at least "```a```" in the embed.
@@ -103,25 +111,29 @@ async def _send_crash_message(
 
 async def _reply(itx: discord.Interaction[Bot], message: str) -> None:
     """
-    A helper function to handle replying to an interaction by either using :py:func:`~discord.Webhook.send` or
-    :py:func:`~discord.InteractionResponse.send_message`, depending on if a response has been responded to already.
+    A helper function to handle replying to an interaction by either
+    using :py:func:`~discord.Webhook.send`
+    or :py:func:`~discord.InteractionResponse.send_message`,
+    depending on if a response has been responded to already.
 
     :param itx: The interaction to respond to.
     :param message: The message to respond with.
 
     .. note::
 
-        The function will always try to respond to the interaction ephemerally.
+        The function will always try to respond to the interaction
+        ephemerally.
     """
-    itx.response: discord.InteractionResponse  # noqa
-    itx.followup: discord.Webhook  # noqa
+    itx.response: discord.InteractionResponse  # type: ignore
+    itx.followup: discord.Webhook  # type: ignore
     try:
         if itx.response.is_done():
             await itx.followup.send(message, ephemeral=True)
         else:
             try:
                 await itx.response.send_message(message, ephemeral=True)
-            except discord.errors.NotFound:  # interaction not found, e.g. took too long
+            except discord.errors.NotFound:
+                # interaction not found, e.g. took too long
                 await itx.followup.send(message, ephemeral=True)
     except discord.errors.NotFound:
         pass  # prevent other code from not running
@@ -137,7 +149,8 @@ def __init__(self, client: Bot):
     async def on_message(self, message: discord.Message):
         if message.author.bot:
             return
-        # kill switch, see cmd_addons for other on_message events. (and a few other extensions)
+        # kill switch, see cmd_addons for other on_message events.
+        #  (and a few other extensions)
         if message.author.id == self.client.bot_owner.id:
             cool_keys = [
                 ":restart",
@@ -146,14 +159,17 @@ async def on_message(self, message: discord.Message):
                 ":sudo shutdown",
             ]
             if message.content == ":kill now please stop" or any(
-                    [message.content.startswith(item) for item in cool_keys]):
+                    [message.content.startswith(item)
+                     for item in cool_keys]
+            ):
                 await message.add_reaction("🔄")
                 sys.exit(0)
                 # quitting the program also
         # this will only run if it hasn't already quit, of course
         if message.content.startswith(":sudo "):
             await message.reply(
-                "Cleo.CommandManager.InsufficientPermissionError: Could not run command: No permission\n"
+                "Cleo.CommandManager.InsufficientPermissionError: Could not "
+                "run command: No permission\n"
                 "Tryin to be part of the cool kids? Try reading this:\n"
                 "1 4M 4 V3RY C001 K16!")
             await message.add_reaction("⚠")
@@ -162,9 +178,13 @@ async def on_message(self, message: discord.Message):
 
     async def on_error(self, event: str, *_args, **_kwargs):
         global commanderror_cooldown
-        if datetime.now().astimezone() - commanderror_cooldown < timedelta(seconds=10):
-            # prevent extra log (prevent excessive spam and saving myself some large mentioning chain) if
-            # within 10 seconds
+        if (
+                (datetime.now().astimezone()
+                 - commanderror_cooldown)
+                < timedelta(seconds=10)
+        ):
+            # prevent extra log (prevent excessive spam and saving
+            #  myself some large mentioning chain) if within 10 seconds
             return
 
         potential_guild: discord.Guild | int | None = None
@@ -183,8 +203,8 @@ async def on_error(self, event: str, *_args, **_kwargs):
         exception_name = exception_name.split(".")[-1]  # a.b.Error -> Error
         if exception_name == MissingAttributesCheckFailure.__name__:
             if self.client.server_settings is None:
-                # Don't send crash message. It's obvious attributes are missing
-                #  because no attributes have been loaded yet.
+                # Don't send crash message. It's obvious attributes are
+                #  missing because no attributes have been loaded yet.
                 return
 
             commanderror_cooldown = datetime.now().astimezone()
@@ -193,7 +213,8 @@ async def on_error(self, event: str, *_args, **_kwargs):
             exception_message = exception_message.strip()
             # exception is formatted using Exception.str(), which I overwrote:
             #  ExceptionName: module_name; attribute1, attribute2, attribute3
-            module_source, missing_attributes = exception_message.split("; ", 2)
+            module_source, missing_attributes = \
+                exception_message.split("; ", 2)
             missing_attributes = missing_attributes.split(", ")
             cmd_mention = self.client.get_command_mention("settings")
             await log_to_guild(
@@ -216,7 +237,13 @@ async def on_error(self, event: str, *_args, **_kwargs):
             potential_guild = getattr(potential_guild, "id", potential_guild)
             event = f"guild: {potential_guild}, " + event
 
-        await _send_crash_message(self.client, "Error", msg, event, discord.Colour.from_rgb(r=255, g=77, b=77))
+        await _send_crash_message(
+            self.client,
+            "Error",
+            msg,
+            event,
+            discord.Colour.from_rgb(r=255, g=77, b=77)
+        )
 
     @staticmethod
     async def on_app_command_error(
@@ -255,28 +282,43 @@ async def on_app_command_error(
             if is_admin(itx, itx.user):
                 cmd_mention_help = itx.client.get_command_mention("help")
                 await itx.response.send_message(
-                    f"This module is not enabled! Enable it using the following command:\n"
-                    f"- {cmd_mention_settings} `type:Module` `setting:{error.module_key}` `mode:Enable`\n"
-                    f"Make sure you also set the required attributes for this module. The required "
-                    f"attributes for modules and commands are explained in {cmd_mention_help}.",
+                    f"This module is not enabled! Enable it using the "
+                    f"following command:\n"
+                    f"- {cmd_mention_settings} `type:Module` "
+                    f"`setting:{error.module_key}` `mode:Enable`\n"
+                    f"Make sure you also set the required attributes for this "
+                    f"module. The required attributes for modules and "
+                    f"commands are explained in {cmd_mention_help}.",
                     ephemeral=True)
                 return
-            await itx.response.send_message("This module is not enabled! Ask an admin to enable this module, "
-                                            "or have them hide this command from users in the server settings.",
-                                            ephemeral=True)
+            await itx.response.send_message(
+                "This module is not enabled! Ask an admin to enable this "
+                "module, or have them hide this command from users in the "
+                "server settings.",
+                ephemeral=True
+            )
             return
         elif error_type is MissingAttributesCheckFailure:
             error: MissingAttributesCheckFailure
             cmd_mention_settings = itx.client.get_command_mention("settings")
-            await _reply(itx, f"Your command failed to completely execute because it relied on certain "
-                              f"server attributes that were not defined! An admin will have to run "
-                              f"{cmd_mention_settings} `type:Attribute` `setting: ` for the following attribute(s):\n"
-                              f"> " + ', '.join(error.attributes))
+            await _reply(
+                itx,
+                f"Your command failed to completely execute because it relied "
+                f"on certain server attributes that were not defined! An "
+                f"admin will have to run {cmd_mention_settings} "
+                f"`type:Attribute` `setting: ` for the following "
+                f"attribute(s):\n"
+                f"> " + ', '.join(error.attributes)
+            )
             return
 
-        if datetime.now().astimezone() - appcommanderror_cooldown < timedelta(seconds=60):
-            # prevent extra log (prevent excessive spam and saving myself some large mentioning chain) if
-            # within 1 minute
+        if (
+                (datetime.now().astimezone()
+                 - appcommanderror_cooldown)
+                < timedelta(seconds=60)
+        ):
+            # prevent extra log (prevent excessive spam and saving
+            #  myself some large mentioning chain) if within 1 minute
             await _reply(
                 itx,
                 ("Your command ran into an error, but another crash "
@@ -312,16 +354,21 @@ async def on_app_command_error(
                 if hasattr(error.original, 'status'):
                     error_reply += " " + str(error.original.status)
                     # if error.original.status == "403":
-                    #     await _reply(itx, "Error 403: It seems like I didn't have permissions for this action! "
-                    #                       f"If you believe this is an error, please message or "
-                    #                       f"ping {client.bot_owner}} :)")
+                    #     await _reply(
+                    #         itx,
+                    #         "Error 403: It seems like I didn't "
+                    #         "have permissions for this action! If you "
+                    #         "believe this is an error, please message or "
+                    #         f"ping {client.bot_owner}} :)"
+                    #     )
                 if hasattr(error.original, 'code'):
                     error_reply += " (" + str(error.original.code) + ")"
                 await _reply(
                     itx,
                     error_reply
                     + f". Please report the error and details to "
-                      f"{itx.client.bot_owner} ({itx.client.bot_owner.mention}) "
+                      f"{itx.client.bot_owner} "
+                      f"({itx.client.bot_owner.mention}) "
                       f"by pinging her or sending her a DM. (Though she "
                       f"should have received a message with error details "
                       f"herself as well.\n"
@@ -329,24 +376,39 @@ async def on_app_command_error(
                     + str(error)
                 )
             else:
-                await _reply(itx, "Something went wrong executing your command!\n    " + repr(error)[:1700])
+                await _reply(
+                    itx,
+                    "Something went wrong executing your command!\n    "
+                    + repr(error)[:1700]
+                )
 
         try:
             msg = f"    Executor details: {itx.user} ({itx.user.id})\n"
         except Exception as ex:
-            msg = f"    Executor details: couldn't get interaction details: {repr(ex)}\n"
+            msg = (f"    Executor details: couldn't get interaction "
+                   f"details: {repr(ex)}\n")
             #   f"    command: {error.command}\n" + \
             #   f"    arguments: {error.args}\n"
         if hasattr(error, 'original'):
             if hasattr(error.original, 'code'):
                 msg += f"    code: {error.original.code}\n"
             if hasattr(error.original, 'status'):
-                msg += f"    original error: {error.original.status}: {error.original.text}\n\n"
+                msg += (f"    original error: {error.original.status}: "
+                        f"{error.original.text}\n\n")
                 #    f"   error response:     {error.original.response}\n\n"
         msg += traceback.format_exc()
         # details: /help `page:1` `param2:hey`
-        command_details = f" " + ' '.join(
-            [f"`{k}:{v}`" for k, v in itx.namespace.__dict__.items()])
-        await _send_crash_message(itx.client, "AppCommand Error", msg, command_details,
-                                  discord.Colour.from_rgb(r=255, g=121, b=77), itx=itx)
+        command_details = (
+            f" "
+            + ' '.join([f"`{k}:{v}`"
+                        for k, v in itx.namespace.__dict__.items()])
+        )
+        await _send_crash_message(
+            itx.client,
+            "AppCommand Error",
+            msg,
+            command_details,
+            discord.Colour.from_rgb(r=255, g=121, b=77),
+            itx=itx
+        )
         appcommanderror_cooldown = datetime.now().astimezone()
diff --git a/extensions/customvcs/channel_rename_tracker.py b/extensions/customvcs/channel_rename_tracker.py
index bc84d6f..dc5cedf 100644
--- a/extensions/customvcs/channel_rename_tracker.py
+++ b/extensions/customvcs/channel_rename_tracker.py
@@ -17,7 +17,9 @@ def clear_vc_rename_log(channel_id: int) -> None:
         pass
 
 
-def try_store_vc_rename(channel_id: int, max_rename_limit: int = 2) -> None | int:
+def try_store_vc_rename(
+        channel_id: int, max_rename_limit: int = 2
+) -> None | int:
     """
     Store a new voice channel rename in the storage dictionary.
 
@@ -41,11 +43,17 @@ def try_store_vc_rename(channel_id: int, max_rename_limit: int = 2) -> None | in
         # if there have been 2 or more renames for this channel
         if len(recently_renamed_vcs[channel_id]) >= max_rename_limit:
             # if those renames were made within the last 10 minutes
-            if datetime.now().astimezone() - recently_renamed_vcs[channel_id][0] < timedelta(seconds=600):
+            if (
+                    (datetime.now().astimezone()
+                     - recently_renamed_vcs[channel_id][0])
+                    < timedelta(seconds=600)
+            ):
                 return int(recently_renamed_vcs[channel_id][0].timestamp())
             # clear rename queue log and continue command
-            # (discord allows 2 renames in 10 minutes but can queue rename events, hence '[2:]')
-            recently_renamed_vcs[channel_id] = recently_renamed_vcs[channel_id][2:]
+            # (discord allows 2 renames in 10 minutes but can queue
+            #  rename events, hence '[2:]')
+            recently_renamed_vcs[channel_id] = \
+                recently_renamed_vcs[channel_id][2:]
     else:
         # create and continue command
         recently_renamed_vcs[channel_id] = []
diff --git a/extensions/customvcs/cogs/customvcs.py b/extensions/customvcs/cogs/customvcs.py
index 498a1a1..6bdc1c1 100644
--- a/extensions/customvcs/cogs/customvcs.py
+++ b/extensions/customvcs/cogs/customvcs.py
@@ -4,30 +4,45 @@
 
 from extensions.settings.objects import ModuleKeys, AttributeKeys
 from resources.customs import Bot
-from resources.checks.permissions import is_staff  # to let staff rename other people's custom vcs
-from resources.checks import module_enabled_check, MissingAttributesCheckFailure
+from resources.checks.permissions import is_staff
+# ^ to let staff rename other people's custom vcs
+from resources.checks import (
+    module_enabled_check, MissingAttributesCheckFailure
+)
 from resources.utils.utils import log_to_guild  # to log custom vc changes
 
-from extensions.customvcs.channel_rename_tracker import clear_vc_rename_log, try_store_vc_rename
+from extensions.customvcs.channel_rename_tracker import (
+    clear_vc_rename_log,
+    try_store_vc_rename
+)
 from extensions.customvcs.modals import CustomVcStaffEditorModal
 from extensions.customvcs.utils import is_vc_custom
 
 
-async def _reset_voice_channel_permissions_if_vctable(vctable_prefix: str, voice_channel: discord.VoiceChannel):
+async def _reset_voice_channel_permissions_if_vctable(
+        vctable_prefix: str,
+        voice_channel: discord.VoiceChannel
+):
     """
-    Reset a voice channel's permission overrides if the 'owners' of the voice channel
-    table are no longer present/connected to the channel.
+    Reset a voice channel's permission overrides if the 'owners' of the
+    voice channel table are no longer present/connected to the channel.
 
-    :param vctable_prefix: The prefix of voice channel tables, to remove/rename it if the vctable owners left.
+    :param vctable_prefix: The prefix of voice channel tables, to
+     remove/rename it if the vctable owners left.
     :param voice_channel: The channel to reset permissions for.
 
     .. note::
 
-        This function does not check if the channel is actually a custom voice channel.
+        This function does not check if the channel is actually a
+        custom voice channel.
     """
-    if len(voice_channel.overwrites) > len(
-            voice_channel.category.overwrites):  # if VcTable, reset ownership; and all owners leave: reset all perms
-        reset_vctable = True  # check if no owners left --> all members in the voice channel aren't owner
+    if len(voice_channel.overwrites) > len(  # todo: invert if-statement
+            voice_channel.category.overwrites):
+        # if VcTable, reset ownership; and all owners leave:
+        #  reset all perms
+        reset_vctable = True
+        # check if no owners left --> all members in the voice channel
+        #  aren't owner.
         for target in voice_channel.overwrites:
             if target not in voice_channel.members:
                 continue
@@ -38,22 +53,29 @@ async def _reset_voice_channel_permissions_if_vctable(vctable_prefix: str, voice
             return
 
         try:
+            # reset overrides; error caught in try-except
             await voice_channel.edit(
-                overwrites=voice_channel.category.overwrites)  # reset overrides; error caught in try-except
+                overwrites=voice_channel.category.overwrites)
             # update every user's permissions
             for user in voice_channel.members:
                 await user.move_to(voice_channel)
             await voice_channel.send(
-                "This channel was converted from a VcTable back to a normal CustomVC because all the owners left")
-            # remove channel's name prefix (seperately from the overwrites due to things like ratelimiting)
+                "This channel was converted from a VcTable back to a normal"
+                " CustomVC because all the owners left"
+            )
+            # remove channel's name prefix (seperately from the
+            #  overwrites due to things like ratelimiting)
             if voice_channel.name.startswith(vctable_prefix):
                 new_channel_name = voice_channel.name[len(vctable_prefix):]
                 try_store_vc_rename(voice_channel.id, max_rename_limit=3)
                 # same as `/vctable disband`
-                # allow max 3 renamed: if a staff queued a rename due to rules, it'd be queued at 3.
-                # it would be bad to have it be renamed back to the bad name right after.
-                await voice_channel.edit(name=new_channel_name)  # error caught in try-except
-        except discord.errors.NotFound:  # catch two possible voice_channel.edit() exceptions
+                # allow max 3 renamed: if a staff queued a rename due
+                #  to rules, it'd be queued at 3. It would be bad to
+                #  have it be renamed back to the bad name right after.
+                await voice_channel.edit(name=new_channel_name)
+                # ^ error caught in try-except
+        except discord.errors.NotFound:
+            # catches two possible voice_channel.edit() exceptions
             pass  # event triggers after vc could be deleted already
 
 
@@ -69,8 +91,10 @@ async def _create_new_custom_vc(
 
     :param client: The Bot class to use for logging.
     :param member: The member that triggered the event/function.
-    :param voice_channel: The voice channel the user joined to trigger this function (the customvc hub)
-    :param customvc_category: The category to create the custom voice channel in.
+    :param voice_channel: The voice channel the user joined to trigger
+     this function (the customvc hub)
+    :param customvc_category: The category to create the custom voice
+     channel in.
     :param customvc_hub: The custom voice channel hub channel.
     """
 
@@ -79,40 +103,63 @@ async def _create_new_custom_vc(
     cmd_mention = client.get_command_mention("editvc")
 
     try:
-        vc = await customvc_category.create_voice_channel(default_name, position=voice_channel.position + 1)
+        vc = await customvc_category.create_voice_channel(
+            default_name, position=voice_channel.position + 1)
     except discord.errors.HTTPException:
-        await log_to_guild(client, member.guild, "WARNING: COULDN'T CREATE CUSTOM VOICE CHANNEL: TOO MANY (max 50?)")
+        await log_to_guild(
+            client,
+            member.guild,
+            "WARNING: COULDN'T CREATE CUSTOM VOICE CHANNEL: TOO MANY (max 50?)"
+        )
         raise
 
     try:
-        await member.move_to(vc, reason="Opened a new voice channel through the vc hub thing.")
+        await member.move_to(
+            vc,
+            reason="Opened a new voice channel through the vc hub thing."
+        )
         await vc.send(
-            f"Voice channel <#{vc.id}> ({vc.id}) created by <@{member.id}> ({member.id}). "
-            f"Use {cmd_mention} to edit the name/user limit.",
-            allowed_mentions=discord.AllowedMentions.none())
+            f"Voice channel <#{vc.id}> ({vc.id}) created by "
+            f"<@{member.id}> ({member.id}). Use {cmd_mention} to edit the "
+            f"name/user limit.",
+            allowed_mentions=discord.AllowedMentions.none()
+        )
         for custom_vc in customvc_category.voice_channels:
             if custom_vc.id == customvc_hub or custom_vc.id == vc.id:
                 continue
             await custom_vc.edit(position=custom_vc.position + 1)
     except discord.HTTPException as ex:
         try:
-            await member.move_to(None,
-                                 reason="Couldn't create a new Custom voice channel so kicked them from their "
-                                        "current vc to prevent them staying in the main customvc hub")
-            # no need to delete vc if they are kicked out of the channel, cause then the next event will
-            # notice that they left the channel.
+            await member.move_to(
+                None,
+                reason="Couldn't create a new Custom voice channel so kicked "
+                       "them from their current vc to prevent them staying "
+                       "in the main customvc hub"
+            )
+            # no need to delete vc if they are kicked out of the
+            #  channel, cause then the next event
+            #  (on_voice_state_update) will notice that they left
+            #  the channel.
         except discord.HTTPException:
             await vc.delete()
-        warning = str(ex) + ": User clicked the vcHub too fast, and it couldn't move them to their new channel\n"
+        warning = str(ex) + (": User clicked the vcHub too fast, and it "
+                             "couldn't move them to their new channel\n")
         await log_to_guild(client, member.guild, msg=warning)
         raise
 
-    await log_to_guild(client, member.guild,
-                       warning + f"{member.nick or member.name} ({member.id}) created and joined "
-                                 f"voice channel {vc.id} (with the default name).")
+    await log_to_guild(
+        client,
+        member.guild,
+        warning + f"{member.nick or member.name} ({member.id}) created and "
+                  f"joined voice channel {vc.id} (with the default name)."
+    )
 
 
-async def _handle_delete_custom_vc(client: Bot, member: discord.Member, voice_channel: discord.VoiceChannel):
+async def _handle_delete_custom_vc(
+        client: Bot,
+        member: discord.Member,
+        voice_channel: discord.VoiceChannel
+):
     """
     Handle the deletion of a custom voice channel (and error handling)
 
@@ -124,14 +171,22 @@ async def _handle_delete_custom_vc(client: Bot, member: discord.Member, voice_ch
     try:
         await voice_channel.delete()
     except discord.errors.NotFound:
-        await log_to_guild(client, member.guild,
-                           f":warning: **WARNING!! Couldn't delete CustomVC channel** {member.nick or member.name} "
-                           f"({member.id}) left voice channel \"{voice_channel.name}\" ({voice_channel.id}), and "
-                           f"was the last one in it, but it **could not be deleted**!")
+        await log_to_guild(
+            client,
+            member.guild,
+            f":warning: **WARNING!! Couldn't delete CustomVC channel** "
+            f"{member.nick or member.name} ({member.id}) left voice channel "
+            f"\"{voice_channel.name}\" ({voice_channel.id}), and "
+            f"was the last one in it, but it **could not be deleted**!"
+        )
         raise
-    await log_to_guild(client, member.guild,
-                       f"{member.nick or member.name} ({member.id}) left voice channel \"{voice_channel.name}\" "
-                       f"({voice_channel.id}), and was the last one in it, so it was deleted.")
+    await log_to_guild(
+        client,
+        member.guild,
+        f"{member.nick or member.name} ({member.id}) left voice channel "
+        f"\"{voice_channel.name}\" ({voice_channel.id}), and was the last one "
+        f"in it, so it was deleted."
+    )
 
 
 async def _handle_custom_voice_channel_leave_events(
@@ -141,10 +196,12 @@ async def _handle_custom_voice_channel_leave_events(
         vctable_prefix: str
 ):
     """
-    A helper function to handle the custom voice channel events when a user leaves a channel.
-    This includes: channel deletion, vctable disbanding.
+    A helper function to handle the custom voice channel events when
+    a user leaves a channel. This includes: channel deletion,
+    vctable disbanding.
 
-    :param client: The client to send logs with, and for the vctable prefix.
+    :param client: The client to send logs with, and for the
+     vctable prefix.
     :param member: The member to trigger the leave event.
     :param voice_channel: The voice channel that the member left from.
     """
@@ -154,21 +211,23 @@ async def _handle_custom_voice_channel_leave_events(
         await _handle_delete_custom_vc(client, member, voice_channel)
 
     # todo: move this to vctables cog
-    await _reset_voice_channel_permissions_if_vctable(vctable_prefix, voice_channel)
+    await _reset_voice_channel_permissions_if_vctable(
+        vctable_prefix, voice_channel)
 
 
 class CustomVcs(commands.Cog):
     def __init__(self, client: Bot):
         self.client = client
-        #  # General, #Private, #Quiet, and #Minecraft.
-        #   Later, it also excludes channels starting with the guild's
-        #   AttributeKeys.customvc_blacklist_prefix
 
     @commands.Cog.listener()
     async def on_voice_state_update(
-            self, member: discord.Member, before: discord.VoiceState, after: discord.VoiceState
+            self,
+            member: discord.Member,
+            before: discord.VoiceState,
+            after: discord.VoiceState
     ):
-        if not self.client.is_module_enabled(member.guild, ModuleKeys.custom_vcs):
+        if not self.client.is_module_enabled(
+                member.guild, ModuleKeys.custom_vcs):
             return
 
         customvc_hub: discord.VoiceChannel | None
@@ -199,7 +258,8 @@ async def on_voice_state_update(
             raise MissingAttributesCheckFailure(
                 ModuleKeys.custom_vcs, missing)
 
-        if before.channel is not None and before.channel in before.channel.guild.voice_channels:
+        if (before.channel is not None
+                and before.channel in before.channel.guild.voice_channels):
             if is_vc_custom(before.channel, customvc_category, customvc_hub,
                             blacklisted_channels, vc_blacklist_prefix):
                 # only run if this voice state regards a custom voice channel
@@ -208,9 +268,18 @@ async def on_voice_state_update(
 
         if after.channel is not None:
             if after.channel.id == customvc_hub.id:
-                await _create_new_custom_vc(self.client, member, after.channel, customvc_category, customvc_hub)
-
-    @app_commands.command(name="editvc", description="Edit your voice channel name or user limit")
+                await _create_new_custom_vc(
+                    self.client,
+                    member,
+                    after.channel,
+                    customvc_category,
+                    customvc_hub
+                )
+
+    @app_commands.command(
+        name="editvc",
+        description="Edit your voice channel name or user limit"
+    )
     @app_commands.describe(name="Give your voice channel a name!",
                            limit="Give your voice channel a user limit!")
     @module_enabled_check(ModuleKeys.custom_vcs)
@@ -238,7 +307,8 @@ async def edit_custom_vc(
                 AttributeKeys.log_channel: vc_log,
                 AttributeKeys.custom_vc_category: vc_category,
                 AttributeKeys.vctable_prefix: vctable_prefix,
-                AttributeKeys.custom_vc_blacklist_prefix: vc_blacklist_prefix}.items()
+                AttributeKeys.custom_vc_blacklist_prefix: vc_blacklist_prefix
+            }.items()
                 if value is None]
             raise MissingAttributesCheckFailure(
                 ModuleKeys.custom_vcs, missing)
@@ -251,15 +321,20 @@ async def edit_custom_vc(
                     vc_hub, vc_log, vc_category, vctable_prefix)
                 await itx.response.send_modal(staff_modal)
                 return
-            await itx.response.send_message("You must be connected to a voice channel to use this command",
-                                            ephemeral=True)
+            await itx.response.send_message(
+                "You must be connected to a voice channel to use this command",
+                ephemeral=True
+            )
             return
         channel = itx.user.voice.channel
         if (channel.category != vc_category
                 or channel.id == vc_hub
                 or channel in vc_blacklisted_channels
                 or channel.name.startswith(vc_blacklist_prefix)):
-            await itx.response.send_message("You can't change that voice channel's name!", ephemeral=True)
+            await itx.response.send_message(
+                "You can't change that voice channel's name!",
+                ephemeral=True
+            )
             return
         if name is not None:
             if name.startswith(vc_blacklist_prefix):
@@ -284,8 +359,10 @@ async def edit_custom_vc(
             first_rename_time = try_store_vc_rename(channel.id)
             if first_rename_time:
                 await itx.response.send_message(
-                    f"You can't edit your channel more than twice in 10 minutes! (bcuz discord :P)\n"
-                    f"You can rename it again  ().",
+                    f"You can't edit your channel more than twice in 10 "
+                    f"minutes! (bcuz discord :P)\n"
+                    f"You can rename it again  "
+                    f"().",
                     ephemeral=True)
                 return
 
@@ -295,42 +372,80 @@ async def edit_custom_vc(
         try:
             if not limit and not name:
                 await itx.response.send_message(
-                    "You can edit your channel with this command. Set a value for the name or the maximum user limit.",
+                    "You can edit your channel with this command. Set a value "
+                    "for the name or the maximum user limit.",
                     ephemeral=True)
             if not limit and name:
-                await channel.edit(reason=f"Voice channel renamed from \"{channel.name}\" to \"{name}\"{limit_info}",
-                                   name=name)
-                await log_to_guild(itx.client, itx.guild,
-                                   f"Voice channel ({channel.id}) renamed from \"{old_name}\" to \"{name}\" "
-                                   f"(by {itx.user.nick or itx.user.name}, {itx.user.id})")
-                await itx.response.send_message(warning + f"Voice channel successfully renamed to \"{name}\"",
-                                                ephemeral=True)  # allowed_mentions=discord.AllowedMentions.none())
+                await channel.edit(
+                    reason=f"Voice channel renamed from \"{channel.name}\" "
+                           f"to \"{name}\"{limit_info}",
+                    name=name
+                )
+                await log_to_guild(
+                    itx.client,
+                    itx.guild,
+                    f"Voice channel ({channel.id}) renamed from "
+                    f"\"{old_name}\" to \"{name}\" (by "
+                    f"{itx.user.nick or itx.user.name}, {itx.user.id})"
+                )
+                await itx.response.send_message(
+                    warning + f"Voice channel successfully renamed "
+                              f"to \"{name}\"",
+                    ephemeral=True,
+                    allowed_mentions=discord.AllowedMentions.none()
+                )
             if limit and not name:
-                await channel.edit(reason=f"Voice channel limit edited from \"{old_limit}\" to \"{limit}\"",
-                                   user_limit=limit)
-                await log_to_guild(itx.client, itx.guild,
-                                   f"Voice channel \"{old_name}\" ({channel.id}) edited the user limit "
-                                   f"from \"{old_limit}\" to \"{limit}\" "
-                                   f"(by {itx.user.nick or itx.user.name}, {itx.user.id}){limit_info}")
+                await channel.edit(
+                    reason=f"Voice channel limit edited from \"{old_limit}\" "
+                           f"to \"{limit}\"",
+                    user_limit=limit
+                )
+                await log_to_guild(
+                    itx.client,
+                    itx.guild,
+                    f"Voice channel \"{old_name}\" ({channel.id}) edited the "
+                    f"user limit from \"{old_limit}\" to \"{limit}\" "
+                    f"(by {itx.user.nick or itx.user.name}, {itx.user.id})"
+                    f"{limit_info}"
+                )
                 await itx.response.send_message(
-                    warning + f"Voice channel user limit for \"{old_name}\" successfully edited "
-                              f"from \"{old_limit}\" to \"{limit}\"",
-                    ephemeral=True, allowed_mentions=discord.AllowedMentions.none())
+                    warning + f"Voice channel user limit for \"{old_name}\" "
+                              f"successfully edited from \"{old_limit}\" to "
+                              f"\"{limit}\"",
+                    ephemeral=True,
+                    allowed_mentions=discord.AllowedMentions.none()
+                )
             if limit and name:
                 await channel.edit(
-                    reason=f"Voice channel edited from name: \"{channel.name}\" to \"{name}\" and user limit "
+                    reason=f"Voice channel edited from name: "
+                           f"\"{channel.name}\" to \"{name}\" and user limit "
                            f"from \"{limit}\" to \"{old_limit}\"",
-                    user_limit=limit, name=name)
-                await log_to_guild(itx.client, itx.guild,
-                                   f"{itx.user.nick or itx.user.name} ({itx.user.id}) changed VC ({channel.id}) "
-                                   f"name \"{old_name}\" to \"{name}\" and "
-                                   f"user limit from \"{old_limit}\" to \"{limit}\"{limit_info}")
-                await itx.response.send_message(warning + "Voice channel name and user limit successfully edited.",
-                                                ephemeral=True, allowed_mentions=discord.AllowedMentions.none())
+                    user_limit=limit,
+                    name=name
+                )
+                await log_to_guild(
+                    itx.client,
+                    itx.guild,
+                    f"{itx.user.nick or itx.user.name} ({itx.user.id}) "
+                    f"changed VC ({channel.id}) name \"{old_name}\" to "
+                    f"\"{name}\" and user limit from \"{old_limit}\" to "
+                    f"\"{limit}\"{limit_info}"
+                )
+                await itx.response.send_message(
+                    warning + "Voice channel name and user limit "
+                              "successfully edited.",
+                    ephemeral=True,
+                    allowed_mentions=discord.AllowedMentions.none()
+                )
         except discord.errors.HTTPException as ex:
             ex_message = repr(ex).split("(", 1)[1][1:-2]
-            await log_to_guild(itx.client, itx.guild,
-                               f"Warning! >> {ex_message} << {itx.user.nick or itx.user.name} ({itx.user.id}) "
-                               f"tried to change {old_name} ({channel.id}) to {name}, but wasn't allowed to by "
-                               f"discord, probably because it's in a banned word list for discord's "
-                               f"discovery <@262913789375021056>")
+            await log_to_guild(
+                itx.client,
+                itx.guild,
+                f"Warning! >> {ex_message} << "
+                f"{itx.user.nick or itx.user.name} ({itx.user.id}) "
+                f"tried to change {old_name} ({channel.id}) to {name}, but "
+                f"wasn't allowed to by discord, probably because it's in a "
+                f"banned word list for discord's discovery "
+                f"<@262913789375021056>"
+            )
diff --git a/extensions/customvcs/cogs/vctables.py b/extensions/customvcs/cogs/vctables.py
index 5b31d12..ad75188 100644
--- a/extensions/customvcs/cogs/vctables.py
+++ b/extensions/customvcs/cogs/vctables.py
@@ -5,14 +5,22 @@
 import discord.app_commands as app_commands
 
 from extensions.settings.objects import ModuleKeys, AttributeKeys
-from resources.checks import module_enabled_check, MissingAttributesCheckFailure
+from resources.checks import (
+    module_enabled_check,
+    MissingAttributesCheckFailure,
+)
 from resources.views.generics import GenericTwoButtonView
-from resources.checks.permissions import is_staff  # to prevent people in vc-tables from muting staff.
-from resources.utils.utils import log_to_guild  # to log custom vc changes
+from resources.checks.permissions import is_staff
+# ^ to prevent people in vc-tables from muting staff.
+from resources.utils.utils import log_to_guild
+# ^ to log custom vc changes
 from resources.customs import Bot
 
 from extensions.customvcs.channel_rename_tracker import try_store_vc_rename
-from extensions.customvcs.utils import is_vc_custom, edit_permissionoverwrite
+from extensions.customvcs.utils import (
+    is_vc_custom,
+    edit_permissionoverwrite,
+)
 
 
 # Owner       = Connection perms (and speaking perms)
@@ -21,37 +29,55 @@
 # Participant = Channel view perms (and message history perms)
 
 # region Permission checks
-def _is_vc_table_owner(channel: discord.VoiceChannel, target: discord.Role | discord.Member) -> bool:
+def _is_vc_table_owner(
+        channel: discord.VoiceChannel,
+        target: discord.Role | discord.Member
+) -> bool:
     if target not in channel.overwrites:
         return False
     return channel.overwrites[target].connect
 
 
-def _is_vctable_speaker(channel: discord.VoiceChannel, target: discord.Role | discord.Member) -> bool:
+def _is_vctable_speaker(
+        channel: discord.VoiceChannel,
+        target: discord.Role | discord.Member
+) -> bool:
     if target not in channel.overwrites:
         return False
     return channel.overwrites[target].speak is True
 
 
-def _is_vctable_muted(channel: discord.VoiceChannel, target: discord.Role | discord.Member) -> bool:
+def _is_vctable_muted(
+        channel: discord.VoiceChannel,
+        target: discord.Role | discord.Member
+) -> bool:
     if target not in channel.overwrites:
         return False
     return channel.overwrites[target].speak is False
 
 
-def _is_vctable_participant(channel: discord.VoiceChannel, target: discord.Role | discord.Member) -> bool:
+def _is_vctable_participant(
+        channel: discord.VoiceChannel,
+        target: discord.Role | discord.Member
+) -> bool:
     if target not in channel.overwrites:
         return False
     return channel.overwrites[target].view_channel is True
 
 
-def _is_vctable_authorized(channel, guild: discord.Guild) -> bool:
+def _is_vctable_authorized(
+        channel: discord.VoiceChannel,
+        guild: discord.Guild
+) -> bool:
     if guild.default_role not in channel.overwrites:
         return False
     return channel.overwrites[guild.default_role].speak is False
 
 
-def _is_vctable_locked(channel, guild: discord.Guild) -> bool:
+def _is_vctable_locked(
+        channel: discord.VoiceChannel,
+        guild: discord.Guild
+) -> bool:
     if guild.default_role not in channel.overwrites:
         return False
     return channel.overwrites[guild.default_role].view_channel is False
@@ -72,7 +98,11 @@ def _get_vctable_members_with_predicate(
     return outputs
 
 
-async def _get_current_voice_channel(itx: discord.Interaction[Bot], action: str, from_event: bool = None):
+async def _get_current_voice_channel(
+        itx: discord.Interaction[Bot],
+        action: str,
+        from_event: bool = None
+):
     """Gets the voice channel of the command executor if it's
     a custom voice channel.
 
@@ -83,58 +113,72 @@ async def _get_current_voice_channel(itx: discord.Interaction[Bot], action: str,
 
     :return: The custom voice channel that the executor is in, or
      ``None`` if the user is not in a custom voice channel.
-    :raise MissingAttributesCheckFailure: If any guild attributes are missing.
+    :raise MissingAttributesCheckFailure: If any guild attributes
+     are missing.
     """
     vc_hub: discord.VoiceChannel | None
     vc_category: discord.CategoryChannel | None
     blacklisted_channels: list[discord.VoiceChannel]
-    vc_hub, vc_category, blacklisted_channels, vc_blacklist_prefix = itx.client.get_guild_attribute(
-        itx.guild,
-        AttributeKeys.custom_vc_create_channel,
-        AttributeKeys.custom_vc_category,
-        AttributeKeys.custom_vc_blacklisted_channels,
-        AttributeKeys.custom_vc_blacklist_prefix)
+    vc_blacklist_prefix: str | None
+    vc_hub, vc_category, blacklisted_channels, vc_blacklist_prefix = \
+        itx.client.get_guild_attribute(
+            itx.guild,
+            AttributeKeys.custom_vc_create_channel,
+            AttributeKeys.custom_vc_category,
+            AttributeKeys.custom_vc_blacklisted_channels,
+            AttributeKeys.custom_vc_blacklist_prefix
+        )
     if None in (vc_hub, vc_category, vc_blacklist_prefix):
         missing = [key for key, value in {
             AttributeKeys.custom_vc_create_channel: vc_hub,
             AttributeKeys.custom_vc_category: vc_category,
-            AttributeKeys.custom_vc_blacklist_prefix: vc_blacklist_prefix}.items()
+            AttributeKeys.custom_vc_blacklist_prefix: vc_blacklist_prefix
+        }.items()
             if value is None]
         raise MissingAttributesCheckFailure(ModuleKeys.vc_tables, missing)
 
     if itx.user.voice is None or itx.user.voice.channel is None:
         if not from_event:
-            await itx.response.send_message(f"Couldn't {action}: You aren't connected to a voice channel!",
-                                            ephemeral=True)
+            await itx.response.send_message(
+                f"Couldn't {action}: You aren't connected to a voice channel!",
+                ephemeral=True,
+            )
         return
     channel = itx.user.voice.channel
 
     if not is_vc_custom(channel, vc_category, vc_hub,
                         blacklisted_channels, vc_blacklist_prefix):
         if not from_event:
-            await itx.response.send_message(f"Couldn't {action}: This voice channel is not compatible "
-                                            f"with VcTables!",
-                                            ephemeral=True)
+            await itx.response.send_message(
+                f"Couldn't {action}: This voice channel is not compatible "
+                f"with VcTables!",
+                ephemeral=True,
+            )
         return
 
     return channel
 
 
 async def _get_channel_if_owner(
-        itx: discord.Interaction | discord.Member, action: str, from_event: bool = False
+        itx: discord.Interaction | discord.Member,
+        action: str,
+        from_event: bool = False
 ):
     """
-    Gets the voice channel of the command executor if they are in a custom voice channel that has
-    turned into a vctable AND they are the owner of that table.
+    Gets the voice channel of the command executor if they are in a
+    custom voice channel that has turned into a vctable AND they are the
+    owner of that table.
 
     :param itx: The interaction from the original command.
     :param action: The action to note in the error message.
-    :param from_event: Whether this event executes from a non-command context. Default: False.
-
-    :return: The vctable that the executor is in, or None if the user is not in a custom voice channel or
-     is not owner of the vctable channel.
-    :raise MissingAttributesCheckFailure: If any guild attributes are missing. (carried from
-     :py:meth:`_is_vc_table_owner`)
+    :param from_event: Whether this event executes from a non-command
+     context. Default: False.
+
+    :return: The vctable that the executor is in, or None if the user
+     is not in a custom voice channel or is not owner of the vctable
+     channel.
+    :raise MissingAttributesCheckFailure: If any guild attributes are
+     missing. (carried from :py:meth:`_is_vc_table_owner`)
     """
     channel = await _get_current_voice_channel(itx, action, from_event)
     if channel is None:
@@ -144,22 +188,31 @@ async def _get_channel_if_owner(
         if not from_event:
             cmd_mention = itx.client.get_command_mention('vctable create')
             await itx.response.send_message(
-                f"Invalid permissions: You are not an owner of this VcTable! (Perhaps this isn't a VcTable yet: "
-                f"use {cmd_mention} to make it one!)",
-                ephemeral=True)
+                f"Invalid permissions: You are not an owner of this VcTable! "
+                f"(Perhaps this isn't a VcTable yet: use {cmd_mention} to "
+                f"make it one!)",
+                ephemeral=True,
+            )
         return
     return channel
 
 
-class VcTables(commands.GroupCog, name="vctable", description="Make your voice channels advanced!"):
+class VcTables(
+        commands.GroupCog,
+        name="vctable",
+        description="Make your voice channels advanced!"
+):
     def __init__(self):
         pass
 
     # region Commands
     # region Other commands
-    @app_commands.command(name="create", description="Turn your custom vc into a cool vc")
-    @app_commands.describe(owners="A list of extra owners for your VcTable. Separate with comma",
-                           name="Give the channel a different name (api efficiency)")
+    @app_commands.command(name="create",
+                          description="Turn your custom vc into a cool vc")
+    @app_commands.describe(
+        owners="A list of extra owners for your VcTable. Separate with comma",
+        name="Give the channel a different name (api efficiency)"
+    )
     @module_enabled_check(ModuleKeys.vc_tables)
     async def vctable_create(
             self,
@@ -167,8 +220,8 @@ async def vctable_create(
             owners: str = "",
             name: app_commands.Range[str, 3, 35] | None = None
     ):
-        vctable_prefix: str | None = itx.client.get_guild_attribute(itx.guild,
-                                                                    AttributeKeys.vctable_prefix)
+        vctable_prefix: str | None = itx.client.get_guild_attribute(
+            itx.guild, AttributeKeys.vctable_prefix)
         if vctable_prefix is None:
             raise MissingAttributesCheckFailure(
                 ModuleKeys.vc_tables, [AttributeKeys.vctable_prefix])
@@ -178,9 +231,12 @@ async def vctable_create(
         owners = owners.split(",")
         cmd_mention = itx.client.get_command_mention("vctable add_owner")
         if itx.user.guild is None:
-            await itx.response.send_message("You don't seem to be in this guild, so I can't give you "
-                                            "permissions for this voice channel... Maybe ask @mysticmia about this?",
-                                            ephemeral=True)
+            await itx.response.send_message(
+                "You don't seem to be in this guild, so I can't give you "
+                "permissions for this voice channel... Maybe ask @mysticmia "
+                "about this?",
+                ephemeral=True,
+            )
 
         # region Parse vctable owners
         added_owners = [itx.user]
@@ -190,19 +246,28 @@ async def vctable_create(
                 continue
             mention: str = mention.strip()
             if not (mention[0:2] == "<@" and mention[-1] == ">"):
-                warning = (f"Note: You didn't give a good list of VcTable owners, so I only added the ones prior. "
-                           f"To make more people owner, use {cmd_mention}.\n")
+                warning = (
+                    f"Note: You didn't give a good list of VcTable owners, "
+                    f"so I only added the ones prior. To make more people "
+                    f"owner, use {cmd_mention}.\n"
+                )
                 break
             mention = mention[2:-1]
             for char in mention:
                 if char not in "0123456789":
-                    warning = (f"Note: You didn't give a good list of VcTable owners, so I only added the ones prior. "
-                               f"To make more people owner, use {cmd_mention}.\n")
+                    warning = (
+                        f"Note: You didn't give a good list of VcTable "
+                        f"owners, so I only added the ones prior. To make "
+                        f"more people owner, use {cmd_mention}.\n"
+                    )
                     break
 
             if not mention.isdecimal():
-                warning = (f"Note: You didn't give a good list of VcTable owners, so I only added the ones prior. "
-                           f"To make more people owner, use {cmd_mention}.\n")
+                warning = (
+                    f"Note: You didn't give a good list of VcTable owners, "
+                    f"so I only added the ones prior. To make more people "
+                    f"owner, use {cmd_mention}.\n"
+                )
                 break
             mention_id = int(mention)
             added_owner_ids.append(mention_id)
@@ -211,9 +276,12 @@ async def vctable_create(
             for owner_id in owner_list:
                 owner = itx.guild.get_member(owner_id)
                 if owner is None:
-                    warning = (f"Note: The list of owners you provided contained an unknown server member "
-                               f"({owner_id}), so I only added the ones prior. To make more people owner, "
-                               f"use {cmd_mention}.")
+                    warning = (
+                        f"Note: The list of owners you provided contained an "
+                        f"unknown server member ({owner_id}), so I only added "
+                        f"the ones prior. To make more people owner, "
+                        f"use {cmd_mention}."
+                    )
                     break
 
                 if owner not in added_owners:
@@ -226,32 +294,42 @@ async def vctable_create(
             return
 
         if name == user_vc.name:
-            warning += ("Info: VcTables get a prefix, so the channel name is edited to include it. "
-                        "Bots can only edit a channel's name twice every 10 minutes. If you wanted to also "
-                        "change the channel name, you would need to run /editvc again, adding to that 2-rename limit. "
-                        "For efficiency, you can provide a name here to do both of the edits at once "
-                        "(prefix and rename).\n"
-                        "That means you don't have to give exactly the same name as the one the channel already had.")
+            warning += (
+                "Info: VcTables get a prefix, so the channel name is edited "
+                "to include it. Bots can only edit a channel's name twice "
+                "every 10 minutes. If you wanted to also change the channel "
+                "name, you would need to run /editvc again, adding to that "
+                "2-rename limit. For efficiency, you can provide a name here "
+                "to do both of the edits at once (prefix and rename).\n"
+                "That means you don't have to give exactly the same name as "
+                "the one the channel already had."
+            )
         elif name is None:
             name = user_vc.name
 
         await itx.response.defer(ephemeral=True)
         # if owner present: already VcTable -> stop
         for target in user_vc.overwrites:
-            if _is_vc_table_owner(user_vc, target) and target not in user_vc.category.overwrites:
+            if (_is_vc_table_owner(user_vc, target)
+                    and target not in user_vc.category.overwrites):
                 cmd_mention = itx.client.get_command_mention("vctable owner")
-                await itx.followup.send(f"This channel is already a VcTable! Use {cmd_mention} `mode:Check owners` to "
-                                        f"see who the owners of this table are!",
-                                        ephemeral=True)
+                await itx.followup.send(
+                    f"This channel is already a VcTable! Use {cmd_mention} "
+                    f"`mode:Check owners` to see who the owners of this "
+                    f"table are!",
+                    ephemeral=True)
                 return
 
         first_rename_time = try_store_vc_rename(itx.user.voice.channel.id)
         if first_rename_time:
             await itx.followup.send(
-                f"This channel has been renamed too often in the past 10 minutes! (bcuz discord :P)\n"
-                f"You can turn this into a VcTable in  "
+                f"This channel has been renamed too often in the past "
+                f"10 minutes! (bcuz discord :P)\n"
+                f"You can turn this into a VcTable in "
+                f" "
                 f"().",
-                ephemeral=True)
+                ephemeral=True,
+            )
             return
         # endregion Set warnings and manage odd cases
 
@@ -269,7 +347,8 @@ async def vctable_create(
                 "view_channel": True,
                 "read_message_history": True,
             })
-        # make sure the bot can still see the channel for vc events, even if /vctable lock
+        # make sure the bot can still see the channel for vc events,
+        #  even if /vctable lock.
         bot_overwrites = user_vc.overwrites.get(
             itx.guild.me, discord.PermissionOverwrite())
         new_overwrites[itx.guild.me] = edit_permissionoverwrite(
@@ -278,29 +357,47 @@ async def vctable_create(
 
         # endregion Apply permission overwrites for vctable owners
 
-        owner_taglist = ', '.join([f'<@{user_id}>' for user_id in added_owners])
+        owner_taglist = ', '.join([f'<@{user_id}>'
+                                   for user_id in added_owners])
         cmd_mention = itx.client.get_command_mention("vctable about")
-        await user_vc.send(f"CustomVC converted to VcTable\n"
-                           f"Use {cmd_mention} to learn more!\n"
-                           f"Made {owner_taglist} a VcTable Owner\n"
-                           f"**:warning: If someone is breaking the rules, TELL A MOD** "
-                           f"(don't try to moderate a vc yourself)",
-                           allowed_mentions=discord.AllowedMentions.none())
-        await log_to_guild(itx.client, itx.guild,
-                           f"{itx.user.mention} ({itx.user.id}) turned a CustomVC ({user_vc.id}) into a VcTable")
+        await user_vc.send(
+            f"CustomVC converted to VcTable\n"
+            f"Use {cmd_mention} to learn more!\n"
+            f"Made {owner_taglist} a VcTable Owner\n"
+            f"**:warning: If someone is breaking the rules, TELL A MOD** "
+            f"(don't try to moderate a vc yourself)",
+            allowed_mentions=discord.AllowedMentions.none()
+        )
+        await log_to_guild(
+            itx.client,
+            itx.guild,
+            f"{itx.user.mention} ({itx.user.id}) turned a CustomVC "
+            f"({user_vc.id}) into a VcTable"
+        )
 
         try:
             await user_vc.edit(name=vctable_prefix + name)
-            await itx.followup.send("Successfully converted channel to VcTable and set you as owner.\n" + warning,
-                                    ephemeral=True)
+            await itx.followup.send(
+                "Successfully converted channel to VcTable and set you "
+                "as owner.\n"
+                + warning,
+                ephemeral=True
+            )
         except discord.errors.NotFound:
-            await itx.followup.send("I was unable to name your VcTable, but I managed to set the permissions for it.",
-                                    ephemeral=True)
+            await itx.followup.send(
+                "I was unable to name your VcTable, but I managed to set "
+                "the permissions for it.",
+                ephemeral=True
+            )
 
     @module_enabled_check(ModuleKeys.vc_tables)
-    @app_commands.command(name="disband", description="reset permissions and convert vctable back to customvc")
+    @app_commands.command(
+        name="disband",
+        description="reset permissions and convert vctable back to customvc"
+    )
     async def vctable_disband(self, itx: discord.Interaction):
-        vctable_prefix: str | None = itx.client.get_guild_attribute(itx.guild, AttributeKeys.vctable_prefix)
+        vctable_prefix: str | None = itx.client.get_guild_attribute(
+            itx.guild, AttributeKeys.vctable_prefix)
         if vctable_prefix is None:
             raise MissingAttributesCheckFailure(
                 ModuleKeys.vc_tables, [AttributeKeys.vctable_prefix])
@@ -308,69 +405,103 @@ async def vctable_disband(self, itx: discord.Interaction):
         channel = await _get_channel_if_owner(itx, "disband VcTable")
         if channel is None:
             return
-        await channel.edit(overwrites=channel.category.overwrites)  # reset overrides
+        # reset overrides
+        await channel.edit(overwrites=channel.category.overwrites)
         # update every user's permissions
         for user in channel.members:
             await user.move_to(channel)
         await channel.send(
-            f"{itx.user.mention} disbanded the VcTable and turned it back to a normal CustomVC",
-            allowed_mentions=discord.AllowedMentions.none())
-        # remove channel's name prefix (separately from the overwrites due to things like ratelimiting)
-        await itx.response.send_message("Successfully disbanded VcTable.", ephemeral=True)
+            f"{itx.user.mention} disbanded the VcTable and turned "
+            f"it back to a normal CustomVC",
+            allowed_mentions=discord.AllowedMentions.none()
+        )
+        # remove channel's name prefix (separately from the overwrites
+        #  due to things like ratelimiting)
+        await itx.response.send_message(
+            "Successfully disbanded VcTable.",
+            ephemeral=True
+        )
         if channel.name.startswith(vctable_prefix):
             new_channel_name = channel.name[len(vctable_prefix):]
             try_store_vc_rename(channel.id, max_rename_limit=3)
             # same as on_voice_state_update:
-            # allow max 3 renamed: if a staff queued a rename due to rules, it'd be queued at 3.
-            # it would be bad to have it be renamed back to the bad name right after.
+            # allow max 3 renamed: if a staff queued a rename due to
+            #  rules, it'd be queued at 3. It would be bad to have it
+            #  be renamed back to the bad name right after.
             try:
                 await channel.edit(name=new_channel_name)
             except discord.errors.NotFound:
                 pass
 
-    @app_commands.command(name="about", description="Get information about this CustomVC add-on feature")
+    @app_commands.command(
+        name="about",
+        description="Get information about this CustomVC add-on feature"
+    )
     @module_enabled_check(ModuleKeys.vc_tables)
     async def vctable_help(self, itx: discord.Interaction):
         embed1 = discord.Embed(
             color=discord.Colour.from_rgb(r=255, g=153, b=204),
             title='Custom VC Tables',
-            description="Tables are a system to help keep a focused voice channel on-topic. For example, a watch "
-                        "party or a group gaming session might welcome people joining to participate but not want "
-                        "people to derail/disrupt it. VcTables allow you to have a bit more control over your vc "
-                        "by letting you mute disruptive people or make speaking permissions whitelist-only")
+            description="Tables are a system to help keep a focused voice "
+                        "channel on-topic. For example, a watch party or a "
+                        "group gaming session might welcome people joining to "
+                        "participate but not want people to derail/disrupt "
+                        "it. VcTables allow you to have a bit more control "
+                        "over your vc by letting you mute disruptive people "
+                        "or make speaking permissions whitelist-only"
+        )
         embed2 = discord.Embed(
             color=discord.Colour.from_rgb(r=255, g=153, b=204),
             title='Command documentation and explanation',
-            description=f"Words in brackets [like so] mean they are optional for the command.\n"
-                        f"Most commands are for owners only, like muting, adding/removing permissions. Normal "
-                        f"participants can check who's owner, speaker, or muted though.\n"
-                        f"{itx.client.get_command_mention('vctable about')}: See this help page\n"
-                        f"{itx.client.get_command_mention('vctable create')} `[owners: ]`: "
-                        f"Turns your CustomVC into a VcTable and makes you (and any additional mentioned "
+            description=f"Words in brackets [like so] mean they are optional "
+                        f"for the command.\n"
+                        f"Most commands are for owners only, like muting, "
+                        f"adding/removing permissions. Normal participants "
+                        f"can check who's owner, speaker, or muted though.\n"
+                        f"{itx.client.get_command_mention('vctable about')}: "
+                        f"See this help page\n"
+                        f"{itx.client.get_command_mention('vctable create')}"
+                        f" `[owners: ]`: Turns your CustomVC into a VcTable "
+                        f"and makes you (and any additional mentioned "
                         f"user(s)) the owner\n"
-                        f"{itx.client.get_command_mention('vctable owner')} `mode: ` `user: `: "
-                        f"Add/Remove an owner to your table. If you want to check the owners, then it doesn't "
-                        f"matter what you fill in for 'user'\n"
-                        f"{itx.client.get_command_mention('vctable mute')} `mode: ` `user: `: "
-                        f"Mute/Unmute a user in your table. If you want to check the muted participants, "
+                        f"{itx.client.get_command_mention('vctable owner')} "
+                        f"`mode: ` `user: `: Add/Remove an owner to your "
+                        f"table. If you want to check the owners, then it "
+                        f"doesn't matter what you fill in for 'user'\n"
+                        f"{itx.client.get_command_mention('vctable mute')} "
+                        f"`mode: ` `user: `: Mute/Unmute a user in your "
+                        f"table. If you want to check the muted participants, "
                         f"see ^ (same as for checking owners)\n"
-                        f"{itx.client.get_command_mention('vctable make_authorized_only')}: "
+                        f"{itx.client.get_command_mention(
+                            'vctable make_authorized_only')}: "
                         f"Toggle the whitelist for speaking\n"
-                        f"{itx.client.get_command_mention('vctable speaker')} `mode: ` `user: `: "
-                        f"Add/Remove a speaker to your table. This user gets whitelisted to speak when authorized-only "
-                        f"is enabled. Checking speakers works the same as checking owners and muted users\n"
+                        f"{itx.client.get_command_mention('vctable speaker')}"
+                        f" `mode: ` `user: `: Add/Remove a speaker to your "
+                        f"table. This user gets whitelisted to speak when "
+                        f"authorized-only is enabled. Checking speakers works "
+                        f"the same as checking owners and muted users\n"
                         f"{itx.client.get_command_mention('vctable lock')}: "
-                        f"Similar to make-authorized-only, but for viewing the voice channel and its message history.\n"
-                        f"{itx.client.get_command_mention('vctable participant')} `mode: ` `user: `: "
-                        f"Add/Remove a participant to your table. This user gets whitelisted to view the channel "
-                        f"and message history when the 'lock' is activated.\n")
-        await itx.response.send_message(embeds=[embed1, embed2], ephemeral=True)
+                        f"Similar to make-authorized-only, but for viewing "
+                        f"the voice channel and its message history.\n"
+                        f"{itx.client.get_command_mention(
+                            'vctable participant')} `mode: ` `user: `: "
+                        f"Add/Remove a participant to your table. This user "
+                        f"gets whitelisted to view the channel and message "
+                        f"history when the 'lock' is activated.\n"
+        )  # todo: move to use /help page instead
+        await itx.response.send_message(
+            embeds=[embed1, embed2],
+            ephemeral=True
+        )
 
     # endregion Other commands
 
     # region Edit user permissions
-    @app_commands.command(name="owner", description="Manage the owners of your VcTable")
-    @app_commands.describe(user="Who to add/remove as a VcTable owner (ignore if 'Check')")
+    @app_commands.command(name="owner",
+                          description="Manage the owners of your VcTable")
+    @app_commands.describe(
+        user="Who to add/remove as a VcTable owner (ignore if 'Check')"
+    )
     @app_commands.choices(mode=[
         discord.app_commands.Choice(name='Add owner', value=1),
         discord.app_commands.Choice(name='Remove owner', value=2),
@@ -383,16 +514,25 @@ async def edit_vctable_owners(
             mode: int,
             user: discord.Member | None = None
     ):
+        # todo: make mode use Enum
         if itx.user == user and mode != 3:
             if mode == 1:
-                await itx.response.send_message("You can't set yourself as owner!", ephemeral=True)
+                await itx.response.send_message(
+                    "You can't set yourself as owner!",
+                    ephemeral=True,
+                )
             elif mode == 2:
-                cmd_mention = itx.client.get_command_mention("vctable owner")
-                cmd_mention1 = itx.client.get_command_mention("vctable disband")
-                await itx.response.send_message("You can't remove your ownership of this VcTable!\n"
-                                                f"You can make more people owner with {cmd_mention} `user: ` though..."
-                                                f"If you want to delete the VcTable, you can use {cmd_mention1}",
-                                                ephemeral=True)
+                cmd_mention = itx.client.get_command_mention(
+                    "vctable owner")
+                cmd_mention1 = itx.client.get_command_mention(
+                    "vctable disband")
+                await itx.response.send_message(
+                    "You can't remove your ownership of this VcTable!\n"
+                    f"You can make more people owner with "
+                    f"{cmd_mention} `user: ` though... If you want to delete "
+                    f"the VcTable, you can use {cmd_mention1}",
+                    ephemeral=True,
+                )
             return
 
         if mode == 1:  # add
@@ -401,20 +541,38 @@ async def edit_vctable_owners(
             if channel is None:
                 return
             if user is None:
-                await itx.response.send_message("You can add an owner to your VcTable using this command. Owners "
-                                                "have the ability to add speakers, mute, add other owners, disband "
-                                                "a vctable, or whitelist connecting and speaking. Give this to "
-                                                "people you believe can help you with this.", ephemeral=True)
+                await itx.response.send_message(
+                    "You can add an owner to your VcTable using this command. "
+                    "Owners have the ability to add speakers, mute, add other "
+                    "owners, disband a vctable, or whitelist connecting and "
+                    "speaking. Give this to people you believe can help you "
+                    "with this.",
+                    ephemeral=True,
+                )
                 return
             if _is_vc_table_owner(channel, user):
-                await itx.response.send_message("This user is already an owner!", ephemeral=True)
-                return
-            await channel.set_permissions(user, connect=True, speak=True, view_channel=True,
-                                          read_message_history=True,
-                                          reason="VcTable edited: set as owner (+speaker)")
-            await channel.send(f"{itx.user.mention} added {user.mention} as VcTable owner.",
-                               allowed_mentions=discord.AllowedMentions.none())
-            await itx.response.send_message(f"Successfully added {user.mention} as owner." + warning, ephemeral=True)
+                await itx.response.send_message(
+                    "This user is already an owner!",
+                    ephemeral=True
+                )
+                return
+            await channel.set_permissions(
+                user,
+                connect=True,
+                speak=True,
+                view_channel=True,
+                read_message_history=True,
+                reason="VcTable edited: set as owner (+speaker)",
+            )
+            await channel.send(
+                f"{itx.user.mention} added {user.mention} as VcTable owner.",
+                allowed_mentions=discord.AllowedMentions.none()
+            )
+            await itx.response.send_message(
+                f"Successfully added {user.mention} as owner."
+                + warning,
+                ephemeral=True,
+            )
             if user in channel.members:
                 await user.move_to(channel)
 
@@ -424,44 +582,73 @@ async def edit_vctable_owners(
                 return
             if user is None:
                 cmd_mention = itx.client.get_command_mention("vctable owner")
-                await itx.response.send_message(f"Removing owners is usually a bad sign.. Do not hesitate to make a "
-                                                f"ticket for staff if there's something wrong.\n"
-                                                f"Anyway. You can check current owners with "
-                                                f"{cmd_mention} `mode:Check`. Then mention a user you want "
-                                                f"to delete in the `user: ` argument.",
-                                                ephemeral=True)
+                await itx.response.send_message(
+                    f"Removing owners is usually a bad sign.. Do not hesitate "
+                    f"to make a ticket for staff if there's something wrong.\n"
+                    f"Anyway. You can check current owners with {cmd_mention} "
+                    f"`mode:Check`. Then mention a user you want to delete in "
+                    f"the `user: ` argument.",
+                    ephemeral=True,
+                )
                 return
             if not _is_vc_table_owner(channel, user):
                 await itx.response.send_message(
-                    "This user wasn't an owner yet.. Try taking someone else's ownership away.", ephemeral=True)
+                    "This user wasn't an owner yet.. Try taking someone "
+                    "else's ownership away.",
+                    ephemeral=True
+                )
                 return
-            await channel.set_permissions(user, connect=None,
-                                          reason="VcTable edited: removed as owner")
-            await channel.send(f"{itx.user.mention} removed {user.mention} as VcTable owner",
-                               allowed_mentions=discord.AllowedMentions.none())
-            await itx.response.send_message(f"Successfully removed {user.mention} as owner. Keep in mind "
-                                            f"they are still a `speaker` and a `participant`.",
-                                            ephemeral=True)
+            await channel.set_permissions(
+                user,
+                connect=None,
+                reason="VcTable edited: removed as owner"
+            )
+            await channel.send(
+                f"{itx.user.mention} removed {user.mention} as VcTable owner",
+                allowed_mentions=discord.AllowedMentions.none()
+            )
+            await itx.response.send_message(
+                f"Successfully removed {user.mention} as owner. Keep in mind "
+                f"they are still a `speaker` and a `participant`.",
+                ephemeral=True
+            )
 
         if mode == 3:  # check
             channel = await _get_current_voice_channel(itx, "check owners")
             if channel is None:
                 return
-            owners = _get_vctable_members_with_predicate(channel, _is_vc_table_owner)
-            await itx.response.send_message("Here is a list of this VcTable's owners:\n  " + ', '.join(owners),
-                                            ephemeral=True)
-
-    @app_commands.command(name="speaker", description="Allow users to talk (when authorized_speakers_only is on)")
-    @app_commands.describe(user="Who to add/remove as a VcTable speaker (ignore if 'Check')")
+            owners = _get_vctable_members_with_predicate(
+                channel, _is_vc_table_owner)
+            await itx.response.send_message(
+                "Here is a list of this VcTable's owners:\n  "
+                + ', '.join(owners),
+                ephemeral=True
+            )
+
+    @app_commands.command(
+        name="speaker",
+        description="Allow users to talk (when authorized_speakers_only is on)"
+    )
+    @app_commands.describe(
+        user="Who to add/remove as a VcTable speaker (ignore if 'Check')"
+    )
     @app_commands.choices(mode=[
         discord.app_commands.Choice(name='Add speaker', value=1),
         discord.app_commands.Choice(name='Remove speaker', value=2),
         discord.app_commands.Choice(name='Check speakers', value=3)
     ])
     @module_enabled_check(ModuleKeys.vc_tables)
-    async def edit_vctable_speakers(self, itx: discord.Interaction[Bot], mode: int, user: discord.Member | None = None):
+    async def edit_vctable_speakers(
+            self,
+            itx: discord.Interaction[Bot],
+            mode: int,
+            user: discord.Member | None = None
+    ):
         if itx.user == user and mode != 3:
-            await itx.response.send_message("You can't edit your own speaking permissions!", ephemeral=True)
+            await itx.response.send_message(
+                "You can't edit your own speaking permissions!",
+                ephemeral=True
+            )
             return
 
         if mode == 1:  # add
@@ -469,26 +656,47 @@ async def edit_vctable_speakers(self, itx: discord.Interaction[Bot], mode: int,
             if channel is None:
                 return
             if user is None:
-                cmd_mention = itx.client.get_command_mention("vctable make_authorized_only")
-                await itx.response.send_message(f"You can add a speaker to your VcTable using this command."
-                                                f"Using {cmd_mention}, you can let only those you've selected "
-                                                f"be able to talk in your voice channel. This can be useful if "
-                                                f"you want an on-topic convo or podcast with a select group of "
-                                                f"people :)", ephemeral=True)
-                return
-            warning = "\nThis user was muted before. Making them a speaker removed their mute." if \
-                _is_vctable_muted(channel, user) else ""
+                cmd_mention = itx.client.get_command_mention(
+                    "vctable make_authorized_only")
+                await itx.response.send_message(
+                    f"You can add a speaker to your VcTable using this "
+                    f"command. Using {cmd_mention}, you can let only those "
+                    f"you've selected be able to talk in your voice channel. "
+                    f"This can be useful if you want an on-topic convo or "
+                    f"podcast with a select group of people :)",
+                    ephemeral=True,
+                )
+                return
+            warning = (
+                "\nThis user was muted before. Making them a speaker removed "
+                "their mute." if _is_vctable_muted(channel, user)
+                else ""
+            )
             if _is_vctable_speaker(channel, user):
-                await itx.response.send_message("This user is already a speaker!", ephemeral=True)
+                await itx.response.send_message(
+                    "This user is already a speaker!",
+                    ephemeral=True,
+                )
                 return
             if not _is_vctable_authorized(channel, itx.guild):
-                cmd_mention = itx.client.get_command_mention("vctable make_authorized_only")
-                warning += f"\nThis has no purpose until you enable 'authorized-only' using {cmd_mention}."
-            await channel.set_permissions(user, speak=True,
-                                          reason="VcTable edited: set as speaker")
-            await channel.send(f"{itx.user.mention} made {user.mention} a speaker.",
-                               allowed_mentions=discord.AllowedMentions.none())
-            await itx.response.send_message(f"Successfully made {user.mention} a speaker." + warning, ephemeral=True)
+                cmd_mention = itx.client.get_command_mention(
+                    "vctable make_authorized_only")
+                warning += (f"\nThis has no purpose until you enable "
+                            f"'authorized-only' using {cmd_mention}.")
+            await channel.set_permissions(
+                user,
+                speak=True,
+                reason="VcTable edited: set as speaker",
+            )
+            await channel.send(
+                f"{itx.user.mention} made {user.mention} a speaker.",
+                allowed_mentions=discord.AllowedMentions.none()
+            )
+            await itx.response.send_message(
+                f"Successfully made {user.mention} a speaker."
+                + warning,
+                ephemeral=True,
+            )
             if user in channel.members:
                 await user.move_to(channel)
 
@@ -497,33 +705,54 @@ async def edit_vctable_speakers(self, itx: discord.Interaction[Bot], mode: int,
             if channel is None:
                 return
             if user is None:
-                cmd_mention = itx.client.get_command_mention("vctable owner")
-                cmd_mention2 = itx.client.get_command_mention("vctable speaker")
-                await itx.response.send_message(f"You can remove speakers with this command. This only works if "
-                                                f"the user you're trying to remove is not already a VcTable owner "
-                                                f"(you'll need to use {cmd_mention} `mode:Remove owner` first).\n"
-                                                f"To see current VcTable speakers, use {cmd_mention2} `mode:Check`.",
-                                                ephemeral=True)
+                cmd_mention = itx.client.get_command_mention(
+                    "vctable owner")
+                cmd_mention2 = itx.client.get_command_mention(
+                    "vctable speaker")
+                await itx.response.send_message(
+                    f"You can remove speakers with this command. This only "
+                    f"works if the user you're trying to remove is not "
+                    f"already a VcTable owner (you'll need to use "
+                    f"{cmd_mention} `mode:Remove owner` first).\n"
+                    f"To see current VcTable speakers, use {cmd_mention2} "
+                    f"`mode:Check`. ",
+                    ephemeral=True,
+                )
                 return
             if _is_vc_table_owner(channel, user):
-                await itx.response.send_message("This user is an owner of this VcTable! If you want to reset "
-                                                "their speaking permissions, un-owner them first!",
-                                                ephemeral=True)
+                await itx.response.send_message(
+                    "This user is an owner of this VcTable! If you want to "
+                    "reset their speaking permissions, un-owner them first!",
+                    ephemeral=True,
+                )
                 return
             if not _is_vctable_speaker(channel, user):
-                await itx.response.send_message("This user is not a speaker! You can't unspeech a non-speaker!",
-                                                ephemeral=True)
+                await itx.response.send_message(
+                    "This user is not a speaker! You can't unspeech a "
+                    "non-speaker!",
+                    ephemeral=True,
+                )
                 return
             warning = ""
             if not _is_vctable_authorized(channel, itx.guild):
-                cmd_mention = itx.client.get_command_mention("vctable make_authorized_only")
-                warning = f"\nThis has no purpose until you enable 'authorized-only' using {cmd_mention}."
-            await channel.set_permissions(user, speak=None,
-                                          reason="VcTable edited: removed as speaker")
-            await channel.send(f"{itx.user.mention} removed {user.mention} as speaker.",
-                               allowed_mentions=discord.AllowedMentions.none())
-            await itx.response.send_message(f"Successfully removed {user.mention} as speaker." + warning,
-                                            ephemeral=True)
+                cmd_mention = itx.client.get_command_mention(
+                    "vctable make_authorized_only")
+                warning = (f"\nThis has no purpose until you enable "
+                           f"'authorized-only' using {cmd_mention}.")
+            await channel.set_permissions(
+                user,
+                speak=None,
+                reason="VcTable edited: removed as speaker",
+            )
+            await channel.send(
+                f"{itx.user.mention} removed {user.mention} as speaker.",
+                allowed_mentions=discord.AllowedMentions.none(),
+            )
+            await itx.response.send_message(
+                f"Successfully removed {user.mention} as speaker."
+                + warning,
+                ephemeral=True
+            )
             if user in channel.members:
                 await user.move_to(channel)
 
@@ -531,21 +760,39 @@ async def edit_vctable_speakers(self, itx: discord.Interaction[Bot], mode: int,
             channel = await _get_current_voice_channel(itx, "check speakers")
             if channel is None:
                 return
-            speakers = _get_vctable_members_with_predicate(channel, _is_vctable_speaker)
-            await itx.response.send_message("Here is a list of this VcTable's speakers:\n  " + ', '.join(speakers),
-                                            ephemeral=True)
-
-    @app_commands.command(name="participant", description="Allow users to see channel (if '/vctable lock' is enabled)")
-    @app_commands.describe(user="Who to add/remove as a VcTable participant (ignore if 'Check')")
+            speakers = _get_vctable_members_with_predicate(
+                channel, _is_vctable_speaker)
+            await itx.response.send_message(
+                "Here is a list of this VcTable's speakers:\n  "
+                + ', '.join(speakers),
+                ephemeral=True,
+            )
+
+    @app_commands.command(
+        name="participant",
+        description="Allow users to see channel (if '/vctable lock' "
+                    "is enabled)"
+    )
+    @app_commands.describe(
+        user="Who to add/remove as a VcTable participant (ignore if 'Check')"
+    )
     @app_commands.choices(mode=[
         discord.app_commands.Choice(name='Add participant', value=1),
         discord.app_commands.Choice(name='Remove participant', value=2),
         discord.app_commands.Choice(name='Check participants', value=3)
     ])
     @module_enabled_check(ModuleKeys.vc_tables)
-    async def edit_vctable_participants(self, itx: discord.Interaction[Bot], mode: int, user: discord.Member | None = None):
+    async def edit_vctable_participants(
+            self,
+            itx: discord.Interaction[Bot],
+            mode: int,
+            user: discord.Member | None = None
+    ):
         if itx.user == user and mode != 3:
-            await itx.response.send_message("You can't edit your own participation permissions!", ephemeral=True)
+            await itx.response.send_message(
+                "You can't edit your own participation permissions!",
+                ephemeral=True
+            )
             return
 
         if mode == 1:  # add
@@ -554,69 +801,109 @@ async def edit_vctable_participants(self, itx: discord.Interaction[Bot], mode: i
                 return
             if user is None:
                 cmd_mention = itx.client.get_command_mention("vctable lock")
-                await itx.response.send_message(f"You can add a participant to your VcTable using this command."
-                                                f"Using {cmd_mention}, you can let only those you've selected "
-                                                f"be able to see your voice channel and read its messages. This "
-                                                f"can be useful if you want an on-topic convo or private meeting "
-                                                f"with a select group of people :)\n"
-                                                f"Note: Staff can still view and join your voice channel.",
-                                                ephemeral=True)
+                await itx.response.send_message(
+                    f"You can add a participant to your VcTable using this "
+                    f"command. Using {cmd_mention}, you can let only those "
+                    f"you've selected be able to see your voice channel and "
+                    f"read its messages. This can be useful if you want an "
+                    f"on-topic convo or private meeting with a select group "
+                    f"of people :)\n"
+                    f"Note: Staff can still view and join your voice channel.",
+                    ephemeral=True
+                )
                 return
             if _is_vctable_participant(channel, user):
-                await itx.response.send_message("This user is already a participant!", ephemeral=True)
+                await itx.response.send_message(
+                    "This user is already a participant!",
+                    ephemeral=True
+                )
                 return
             warning = ""
             if not _is_vctable_locked(channel, itx.guild):
                 cmd_mention = itx.client.get_command_mention("vctable lock")
-                warning += f"\nThis has no purpose until you activate the 'lock' using {cmd_mention}."
-            await channel.set_permissions(user, view_channel=True, read_message_history=True,
-                                          reason="VcTable edited: set as participant")
-            await channel.send(f"{itx.user.mention} made {user.mention} a participant.",
-                               allowed_mentions=discord.AllowedMentions.none())
-            await itx.response.send_message(f"Successfully made {user.mention} a participant." + warning,
-                                            ephemeral=True)
+                warning += (f"\nThis has no purpose until you activate the "
+                            f"'lock' using {cmd_mention}.")
+            await channel.set_permissions(
+                user,
+                view_channel=True,
+                read_message_history=True,
+                reason="VcTable edited: set as participant",
+            )
+            await channel.send(
+                f"{itx.user.mention} made {user.mention} a participant.",
+                allowed_mentions=discord.AllowedMentions.none()
+            )
+            await itx.response.send_message(
+                f"Successfully made {user.mention} a participant."
+                + warning,
+                ephemeral=True
+            )
 
         if mode == 2:  # remove
             channel = await _get_channel_if_owner(itx, "remove participant")
             if channel is None:
                 return
             if user is None:
-                cmd_mention = itx.client.get_command_mention("vctable owner")
-                cmd_mention2 = itx.client.get_command_mention("vctable participant")
-                await itx.response.send_message(f"You can remove participants with this command. This only works if "
-                                                f"the user you're trying to remove is not already a VcTable owner "
-                                                f"(you'll need to use {cmd_mention} `mode:Remove owner` first).\n"
-                                                f"To see current VcTable participants, use "
-                                                f"{cmd_mention2} `mode:Check`.",
-                                                ephemeral=True)
+                cmd_mention = itx.client.get_command_mention(
+                    "vctable owner")
+                cmd_mention2 = itx.client.get_command_mention(
+                    "vctable participant")
+                await itx.response.send_message(
+                    f"You can remove participants with this command. This "
+                    f"only works if the user you're trying to remove is not "
+                    f"already a VcTable owner (you'll need to use "
+                    f"{cmd_mention} `mode:Remove owner` first).\n"
+                    f"To see current VcTable participants, use {cmd_mention2} "
+                    f"`mode:Check`.",
+                    ephemeral=True,
+                )
                 return
             if not _is_vctable_participant(channel, user):
-                await itx.response.send_message("This user is not a participant! You can't remove the "
-                                                "participation permissions they don't have!",
-                                                ephemeral=True)
+                await itx.response.send_message(
+                    "This user is not a participant! You can't remove the "
+                    "participation permissions they don't have!",
+                    ephemeral=True,
+                )
                 return
             if _is_vc_table_owner(channel, user):
-                await itx.response.send_message("This user is an owner of this VcTable! If you want to reset "
-                                                "their participation permissions, un-owner them first!",
-                                                ephemeral=True)
+                await itx.response.send_message(
+                    "This user is an owner of this VcTable! If you want to "
+                    "reset their participation permissions, un-owner them "
+                    "first!",
+                    ephemeral=True,
+                )
                 return
             if itx.client.is_me(user):
                 await itx.response.send_message(
-                    ":warning: You are trying to hide this channel from the bot that manages this system!\n"
-                    "Your command has been interrupted. Removing this user's viewing permissions would "
-                    "make it unable to edit the voice channel or respond to voice channel events (join/leave).",
-                    ephemeral=True)
+                    ":warning: You are trying to hide this channel from the "
+                    "bot that manages this system!\n"
+                    "Your command has been interrupted. Removing this user's "
+                    "viewing permissions would make it unable to edit the "
+                    "voice channel or respond to voice channel events "
+                    "(join/leave).",
+                    ephemeral=True,
+                )
                 return
             warning = ""
             if _is_vctable_locked(channel, itx.guild):
                 cmd_mention = itx.client.get_command_mention("vctable lock")
-                warning = f"\nThis has no purpose until you activate the 'lock' using {cmd_mention}."
-            await channel.set_permissions(user, view_channel=None, read_message_history=None,
-                                          reason="VcTable edited: removed as participant")
-            await channel.send(f"{itx.user.mention} removed {user.mention} as participant.",
-                               allowed_mentions=discord.AllowedMentions.none())
-            await itx.response.send_message(f"Successfully removed {user.mention} as participant." + warning,
-                                            ephemeral=True)
+                warning = (f"\nThis has no purpose until you activate the "
+                           f"'lock' using {cmd_mention}.")
+            await channel.set_permissions(
+                user,
+                view_channel=None,
+                read_message_history=None,
+                reason="VcTable edited: removed as participant",
+            )
+            await channel.send(
+                f"{itx.user.mention} removed {user.mention} as participant.",
+                allowed_mentions=discord.AllowedMentions.none()
+            )
+            await itx.response.send_message(
+                f"Successfully removed {user.mention} as participant."
+                + warning,
+                ephemeral=True
+            )
             if user in channel.members:
                 await user.move_to(channel)
 
@@ -624,11 +911,16 @@ async def edit_vctable_participants(self, itx: discord.Interaction[Bot], mode: i
             channel = await _get_current_voice_channel(itx, "check speakers")
             if channel is None:
                 return
-            participants = _get_vctable_members_with_predicate(channel, _is_vctable_participant)
+            participants = _get_vctable_members_with_predicate(
+                channel, _is_vctable_participant)
             await itx.response.send_message(
-                "Here is a list of this VcTable's participants:\n  " + ', '.join(participants), ephemeral=True)
+                "Here is a list of this VcTable's participants:\n  "
+                + ', '.join(participants),
+                ephemeral=True,
+            )
 
-    @app_commands.command(name="mute", description="Deny users to talk in your VcTable")
+    @app_commands.command(name="mute",
+                          description="Deny users to talk in your VcTable")
     @app_commands.describe(user="Who to mute/unmute (ignore if 'Check')")
     @app_commands.choices(mode=[
         discord.app_commands.Choice(name='Mute participant', value=1),
@@ -637,10 +929,18 @@ async def edit_vctable_participants(self, itx: discord.Interaction[Bot], mode: i
     ])
     @module_enabled_check(ModuleKeys.vc_tables)
     async def edit_vctable_muted_participants(
-            self, itx: discord.Interaction[Bot], mode: int, user: discord.Member | None = None
+            self,
+            itx: discord.Interaction[Bot],
+            mode: int,
+            user: discord.Member | None = None
     ):
         if itx.user == user and mode != 3:
-            await itx.response.send_message("You can't " + "un" * (mode == 2) + "mute yourself!", ephemeral=True)
+            await itx.response.send_message(
+                "You can't "
+                + "un" * (mode == 2)
+                + "mute yourself!",
+                ephemeral=True,
+            )
             return
 
         if mode == 1:  # mute
@@ -648,32 +948,55 @@ async def edit_vctable_muted_participants(
             if channel is None:
                 return
             if user is None:
-                await itx.response.send_message("Muting someone is usually a bad sign... Don't hesitate to open "
-                                                "a ticket with staff if there's something going on.\n"
-                                                "Anyway. Mention a user in the `user: ` argument to mute that person.",
-                                                ephemeral=True)
+                await itx.response.send_message(
+                    "Muting someone is usually a bad sign... Don't hesitate "
+                    "to open a ticket with staff if there's something going "
+                    "on.\n"
+                    "Anyway. Mention a user in the `user: ` argument to mute "
+                    "that person.",
+                    ephemeral=True,
+                )
                 return
-            warning = ("\nThis user was a speaker before. Muting them overwrote this permissions and "
-                       "removed their speaker permissions") if _is_vctable_speaker(channel, user) else ""
+            warning = (
+                "\nThis user was a speaker before. Muting them "
+                "overwrote this permissions and removed their speaker "
+                "permissions" if _is_vctable_speaker(channel, user)
+                else ""
+            )
             if _is_vctable_muted(channel, user):
-                await itx.response.send_message("This user is already muted!", ephemeral=True)
+                await itx.response.send_message(
+                    "This user is already muted!",
+                    ephemeral=True
+                )
                 return
             if _is_vc_table_owner(channel, user):
                 await itx.response.send_message(
-                    "This user is an owner of this VcTable! If you want to mute them, un-owner them first!",
+                    "This user is an owner of this VcTable! If you want to "
+                    "mute them, un-owner them first!",
                     ephemeral=True)
                 return
 
             if is_staff(itx, user):
                 await itx.response.send_message(
-                    "You can't mute staff members! If you have an issue with staff, make a ticket or DM an admin!",
+                    "You can't mute staff members! If you have an issue "
+                    "with staff, make a ticket or DM an admin!",
                     ephemeral=True)
                 return
-            await channel.set_permissions(user, speak=False, stream=False,
-                                          reason="VcTable edited: muted participant")
-            await channel.send(f"{itx.user.mention} muted {user.mention}.",
-                               allowed_mentions=discord.AllowedMentions.none())
-            await itx.response.send_message(f"Successfully muted {user.mention}." + warning, ephemeral=True)
+            await channel.set_permissions(
+                user,
+                speak=False,
+                stream=False,
+                reason="VcTable edited: muted participant"
+            )
+            await channel.send(
+                f"{itx.user.mention} muted {user.mention}.",
+                allowed_mentions=discord.AllowedMentions.none()
+            )
+            await itx.response.send_message(
+                f"Successfully muted {user.mention}."
+                + warning,
+                ephemeral=True
+            )
             if user in channel.members:
                 await user.move_to(channel)
 
@@ -683,36 +1006,57 @@ async def edit_vctable_muted_participants(
                 return
             if user is None:
                 cmd_mention = itx.client.get_command_mention("vctable mute")
-                await itx.response.send_message(f"This command lets you unmute a previously-muted person. "
-                                                f"To see which people are muted, use {cmd_mention} `mode:Check`\n"
-                                                f"Then simply mention this user in the `user: ` argument.",
-                                                ephemeral=True)
+                await itx.response.send_message(
+                    f"This command lets you unmute a previously-muted person. "
+                    f"To see which people are muted, use {cmd_mention} "
+                    f"`mode:Check`\n"
+                    f"Then simply mention this user in the `user: ` argument.",
+                    ephemeral=True,
+                )
                 return
             if not _is_vctable_muted(channel, user):
                 await itx.response.send_message(
-                    "This user is already unmuted! Let people be silent if they wanna be >:(", ephemeral=True)
+                    "This user is already unmuted! Let people be silent "
+                    "if they wanna be >:(",
+                    ephemeral=True
+                )
                 return
-            await channel.set_permissions(user, speak=None, stream=None,
-                                          reason="VcTable edited: unmuted participant")
-            await channel.send(f"{itx.user.mention} unmuted {user.mention}.",
-                               allowed_mentions=discord.AllowedMentions.none())
-            await itx.response.send_message(f"Successfully unmuted {user.mention}.", ephemeral=True)
+            await channel.set_permissions(
+                user,
+                speak=None,
+                stream=None,
+                reason="VcTable edited: unmuted participant",
+            )
+            await channel.send(
+                f"{itx.user.mention} unmuted {user.mention}.",
+                allowed_mentions=discord.AllowedMentions.none()
+            )
+            await itx.response.send_message(
+                f"Successfully unmuted {user.mention}.",
+                ephemeral=True,
+            )
             if user in channel.members:
                 await user.move_to(channel)
 
         if mode == 3:  # check
-            channel = await _get_current_voice_channel(itx, "check muted users")
+            channel = await _get_current_voice_channel(
+                itx, "check muted users")
             if channel is None:
                 return
-            muted = _get_vctable_members_with_predicate(channel, _is_vctable_muted)
-            await itx.response.send_message("Here is a list of this VcTable's muted users:\n  " + ', '.join(muted),
-                                            ephemeral=True)
+            muted = _get_vctable_members_with_predicate(
+                channel, _is_vctable_muted)
+            await itx.response.send_message(
+                "Here is a list of this VcTable's muted users:\n  "
+                + ', '.join(muted),
+                ephemeral=True,
+            )
 
     # endregion Edit user permissions
 
     # region Edit default role permissions
     @app_commands.command(name="make_authorized_only",
-                          description="Only let users speak if they are whitelisted by the owner")
+                          description="Only let users speak if they are "
+                                      "whitelisted by the owner")
     @module_enabled_check(ModuleKeys.vc_tables)
     async def vctable_authorized_only(self, itx: discord.Interaction):
         channel = await _get_channel_if_owner(itx, "enable authorized-only")
@@ -720,81 +1064,131 @@ async def vctable_authorized_only(self, itx: discord.Interaction):
             return
 
         if _is_vctable_authorized(channel, itx.guild):
-            await channel.set_permissions(itx.guild.default_role,
-                                          speak=None,
-                                          reason="VcTable edited: disaled authorized-only for speaking")
-            await channel.send(f"{itx.user.mention} disabled whitelist for speaking.",
-                               allowed_mentions=discord.AllowedMentions.none())
-            await itx.response.send_message("Successfully disabled speaking whitelist.", ephemeral=True)
+            await channel.set_permissions(
+                itx.guild.default_role,
+                speak=None,
+                reason="VcTable edited: disaled authorized-only for speaking",
+            )
+            await channel.send(
+                f"{itx.user.mention} disabled whitelist for speaking.",
+                allowed_mentions=discord.AllowedMentions.none(),
+            )
+            await itx.response.send_message(
+                "Successfully disabled speaking whitelist.",
+                ephemeral=True,
+            )
             return
 
         # if authorized-only is disabled:
-        view = GenericTwoButtonView(("Confirm", discord.ButtonStyle.green), ("Cancel", discord.ButtonStyle.red))
+        view = GenericTwoButtonView(
+            ("Confirm", discord.ButtonStyle.green),
+            ("Cancel", discord.ButtonStyle.red)
+        )
         cmd_mention = itx.client.get_command_mention("vctable speaker")
-        await itx.response.send_message(f"Enabling authorized-only (a whitelist) will make only owners and speakers "
-                                        f"(people that have been whitelisted) able to talk.\n"
-                                        f"Please make sure everyone is aware of this change. "
-                                        f"To whitelist someone, use {cmd_mention} `mode:Add` `user: `.",
-                                        ephemeral=True,
-                                        view=view)
+        await itx.response.send_message(
+            f"Enabling authorized-only (a whitelist) will make only owners "
+            f"and speakers (people that have been whitelisted) able to talk.\n"
+            f"Please make sure everyone is aware of this change. "
+            f"To whitelist someone, use {cmd_mention} `mode:Add` `user: `.",
+            ephemeral=True,
+            view=view,
+        )
         await view.wait()
         if view.value:
-            await channel.set_permissions(itx.guild.default_role, speak=False,
-                                          reason="VcTable edited: enabled authorized-only for speaking")
-            await channel.send(f"{itx.user.mention} enabled whitelist for speaking.",
-                               allowed_mentions=discord.AllowedMentions.none())
-            for member in channel.members:  # member has no owner or speaking perms, move to same vc?
+            await channel.set_permissions(
+                itx.guild.default_role,
+                speak=False,
+                reason="VcTable edited: enabled authorized-only for speaking",
+            )
+            await channel.send(
+                f"{itx.user.mention} enabled whitelist for speaking.",
+                allowed_mentions=discord.AllowedMentions.none()
+            )
+            for member in channel.members:
+                # member has no owner or speaking perms, move to same vc?
                 if member in channel.overwrites:
-                    if _is_vc_table_owner(channel, member) or _is_vctable_speaker(channel, member):
+                    if (_is_vc_table_owner(channel, member)
+                            or _is_vctable_speaker(channel, member)):
+                        # todo: rename vctable/vc_table to be consistent
                         continue
                 await member.move_to(channel)
             cmd_mention = itx.client.get_command_mention("vctable speaker")
             await itx.edit_original_response(
-                content=f"Successfully enabled whitelist. Use {cmd_mention} `user: ` to let more people speak.",
+                content=f"Successfully enabled whitelist. Use {cmd_mention} "
+                        f"`user: ` to let more people speak.",
                 view=None)
         else:
-            await itx.edit_original_response(content="Cancelling...", view=None)
+            await itx.edit_original_response(
+                content="Cancelling...", view=None)
 
-    @app_commands.command(name="lock", description="Only let users view vc if they are whitelisted by the owner")
+    @app_commands.command(
+        name="lock",
+        description="Only let users view vc if they are whitelisted by "
+                    "the owner"
+    )
     @module_enabled_check(ModuleKeys.vc_tables)
     async def vctable_lock(self, itx: discord.Interaction):
         channel = await _get_channel_if_owner(itx, "enable vctable lock")
         if channel is None:
             return
 
-        # if lock is enabled -> (the role overwrite is not nonexistant and is False):
+        # if lock is enabled -> (the role overwrite is not nonexistant
+        #  and is False):
         if _is_vctable_locked(channel, itx.guild):
-            await channel.set_permissions(itx.guild.default_role,
-                                          view_channel=None, read_message_history=None,
-                                          reason="VcTable edited: disabled viewing lock")
-            await channel.send(f"{itx.user.mention} disabled whitelist for viewing this channel.",
-                               allowed_mentions=discord.AllowedMentions.none())
-            await itx.response.send_message("Successfully disabled viewing whitelist.", ephemeral=True)
+            await channel.set_permissions(
+                itx.guild.default_role,
+                view_channel=None,
+                read_message_history=None,
+                reason="VcTable edited: disabled viewing lock"
+            )
+            await channel.send(
+                f"{itx.user.mention} disabled whitelist for viewing "
+                f"this channel.",
+                allowed_mentions=discord.AllowedMentions.none()
+            )
+            await itx.response.send_message(
+                "Successfully disabled viewing whitelist.",
+                ephemeral=True
+            )
             return
 
         # if lock is disabled:
-        view = GenericTwoButtonView(("Confirm", discord.ButtonStyle.green), ("Cancel", discord.ButtonStyle.red))
+        view = GenericTwoButtonView(
+            ("Confirm", discord.ButtonStyle.green),
+            ("Cancel", discord.ButtonStyle.red)
+        )
         cmd_mention = itx.client.get_command_mention("vctable participant")
         await itx.response.send_message(
-            f"Enabling the lock (a whitelist) will make only owners and participants (people that "
-            f"have been whitelisted) able to see this server and message history.\n"
+            f"Enabling the lock (a whitelist) will make only owners and "
+            f"participants (people that have been whitelisted) able to see "
+            f"this server and message history.\n"
             f"Please make sure everyone is aware of this change. "
             f"To whitelist someone, use {cmd_mention} `mode:Add` `user: `.",
             ephemeral=True,
-            view=view)
+            view=view,
+        )
         await view.wait()
         if view.value:
-            await channel.set_permissions(itx.guild.default_role,
-                                          view_channel=False, read_message_history=False,
-                                          reason="VcTable edited: enabled viewing lock")
-            await channel.send(f"{itx.user.mention} enabled whitelist for viewing the voice channel.",
-                               allowed_mentions=discord.AllowedMentions.none())
+            await channel.set_permissions(
+                itx.guild.default_role,
+                view_channel=False,
+                read_message_history=False,
+                reason="VcTable edited: enabled viewing lock"
+            )
+            await channel.send(
+                f"{itx.user.mention} enabled whitelist for "
+                f"viewing the voice channel.",
+                allowed_mentions=discord.AllowedMentions.none()
+            )
             cmd_mention = itx.client.get_command_mention("vctable participant")
             await itx.edit_original_response(
-                content=f"Successfully enabled whitelist. Use {cmd_mention} `user: ` to let more people speak.",
-                view=None)
+                content=f"Successfully enabled whitelist. Use {cmd_mention} "
+                        f"`user: ` to let more people speak.",
+                view=None,
+            )
         else:
-            await itx.edit_original_response(content="Cancelling...", view=None)
+            await itx.edit_original_response(
+                content="Cancelling...", view=None)
 
     # endregion Edit default role permissions
     # endregion Commands
diff --git a/extensions/customvcs/modals/staffeditor.py b/extensions/customvcs/modals/staffeditor.py
index 7bd76a3..fca516b 100644
--- a/extensions/customvcs/modals/staffeditor.py
+++ b/extensions/customvcs/modals/staffeditor.py
@@ -5,7 +5,10 @@
 from extensions.customvcs.channel_rename_tracker import try_store_vc_rename
 
 
-class CustomVcStaffEditorModal(discord.ui.Modal, title='Edit a custom vc\'s channel'):
+class CustomVcStaffEditorModal(
+        discord.ui.Modal,
+        title='Edit a custom vc\'s channel'
+):
     def __init__(
             self, vc_hub: int, vc_log, vc_category, vctable_prefix
     ):
@@ -14,12 +17,22 @@ def __init__(
         self.vcLog = vc_log
         self.vcCategory = vc_category
 
-        self.channel_id = discord.ui.TextInput(label='Channel Id', placeholder="Which channel do you want to edit",
-                                               required=True)
-        self.name = discord.ui.TextInput(label='Name', placeholder="Give your voice channel a name", required=False,
-                                         max_length=100 - len(vctable_prefix))
-        self.limit = discord.ui.TextInput(label='Limit', placeholder="Give your voice channel a user limit",
-                                          required=False)
+        self.channel_id = discord.ui.TextInput(
+            label='Channel Id',
+            placeholder="Which channel do you want to edit",
+            required=True
+        )
+        self.name = discord.ui.TextInput(
+            label='Name',
+            placeholder="Give your voice channel a name",
+            required=False,
+            max_length=100 - len(vctable_prefix)
+        )
+        self.limit = discord.ui.TextInput(
+            label='Limit',
+            placeholder="Give your voice channel a user limit",
+            required=False
+        )
         self.add_item(self.channel_id)
         self.add_item(self.name)
         self.add_item(self.limit)
@@ -34,9 +47,11 @@ async def on_submit(self, itx: discord.Interaction):
             channel_id = int(str(self.channel_id))
         except ValueError:
             await itx.response.send_message(
-                "Your channel id has to be .. number-able. It contains a non-integer character. In other words, "
-                "there's something other than a number in your Channel Id box",
-                ephemeral=True)
+                "Your channel id has to be .. number-able. It contains "
+                "a non-integer character. In other words, there's something "
+                "other than a number in your Channel Id box",
+                ephemeral=True
+            )
             return
 
         try:
@@ -45,89 +60,145 @@ async def on_submit(self, itx: discord.Interaction):
             else:
                 limit = None
         except ValueError:
-            await itx.response.send_message("I can only set the limit to a whole number...", ephemeral=True)
+            await itx.response.send_message(
+                "I can only set the limit to a whole number...",
+                ephemeral=True
+            )
             return
 
         try:
             channel = itx.guild.get_channel(channel_id)
             if type(channel) is not discord.VoiceChannel:
-                await itx.response.send_message("This isn't a voice channel. You can't edit this channel.",
-                                                ephemeral=True)
+                await itx.response.send_message(
+                    "This isn't a voice channel. You can't edit this channel.",
+                    ephemeral=True
+                )
                 return
         except discord.errors.HTTPException:
-            await itx.response.send_message("Retrieving this channel failed. Perhaps a connection issue?",
-                                            ephemeral=True)
+            await itx.response.send_message(
+                "Retrieving this channel failed. Perhaps a connection issue?",
+                ephemeral=True
+            )
             return
         except Exception:
             await itx.response.send_message(
-                "Sorry, I couldn't find that channel. Are you sure you have the correct **voice** channel id?",
-                ephemeral=True)
+                "Sorry, I couldn't find that channel. Are you sure you "
+                "have the correct **voice** channel id?",
+                ephemeral=True
+            )
             raise
 
         warning = ""
-        if getattr(channel.category, "id") not in [self.vcCategory] or channel.id == self.vcHub:
+        if (getattr(channel.category, "id") not in [self.vcCategory]
+                or channel.id == self.vcHub):
             await itx.response.send_message(
-                "You can't change that voice channel's name (not with this command, at least)!", ephemeral=True)
+                "You can't change that voice channel's name (not with "
+                "this command, at least)!",
+                ephemeral=True
+            )
             return
 
         first_rename_time = try_store_vc_rename(channel.id, max_rename_limit=4)
         if first_rename_time:
             await itx.response.send_message(
-                f"You can't edit channels more than twice in 10 minutes. Discord API would queue the edit instead.\n"
-                f"I have queued the previous two renaming edits. You can queue another rename "
-                f" ().",
-                ephemeral=True)
+                f"You can't edit channels more than twice in 10 minutes. "
+                f"Discord API would queue the edit instead.\n"
+                f"I have queued the previous two renaming edits. You can "
+                f"queue another rename  "
+                f"().",
+                ephemeral=True
+            )
             return
 
         limit_info = ""
         old_name = channel.name
         old_limit = channel.user_limit
         if old_name.startswith('〙') != name.startswith('〙'):
-            warning += ("You're renaming a vc channel with a '〙' symbol. This symbol is used to blacklist "
-                        "voice channels from being automatically removed when the last user leaves. If you "
-                        "want a channel to stay (and not be deleted) when no-one is in it, you should start "
-                        "the channel with the symbol.\n")
+            # todo: fix
+            warning += (
+                "You're renaming a vc channel with a '〙' symbol. This symbol "
+                "is used to blacklist voice channels from being automatically "
+                "removed when the last user leaves. If you want a channel to "
+                "stay (and not be deleted) when no-one is in it, you should "
+                "start the channel with the symbol.\n"
+            )
         try:
             if not limit and not name:
                 await itx.response.send_message(
-                    "You can edit a channel with this command. Set a value for the name or the maximum user limit.",
+                    "You can edit a channel with this command. Set a value "
+                    "for the name or the maximum user limit.",
                     ephemeral=True)
             if limit and not name:
-                await channel.edit(reason=f"Staff: Voice channel limit edited from \"{old_limit}\" to \"{limit}\"",
-                                   user_limit=limit)
-                await log_to_guild(itx.client, itx.guild,
-                                   f"Staff: Voice channel \"{old_name}\" ({channel.id}) edited the user limit from "
-                                   f"\"{old_limit}\" to \"{limit}\""
-                                   f" (by {itx.user.nick or itx.user.name}, {itx.user.id}){limit_info}")
+                await channel.edit(
+                    reason=f"Staff: Voice channel limit edited from "
+                           f"\"{old_limit}\" to \"{limit}\"",
+                    user_limit=limit
+                )
+                await log_to_guild(
+                    itx.client,
+                    itx.guild,
+                    f"Staff: Voice channel \"{old_name}\" ({channel.id}) "
+                    f"edited the user limit from \"{old_limit}\" to "
+                    f"\"{limit}\" (by {itx.user.nick or itx.user.name}, "
+                    f"{itx.user.id}){limit_info}"
+                )
                 await itx.response.send_message(
-                    warning + f"Staff: Voice channel user limit for \"{old_name}\" successfully edited from "
-                              f"\"{old_limit}\" to \"{limit}\"",
-                    ephemeral=True, allowed_mentions=discord.AllowedMentions.none())
+                    warning
+                    + f"Staff: Voice channel user limit for \"{old_name}\" "
+                      f"successfully edited from \"{old_limit}\" to "
+                      f"\"{limit}\"",
+                    ephemeral=True,
+                    allowed_mentions=discord.AllowedMentions.none()
+                )
             if not limit and name:
                 await channel.edit(
-                    reason=f"Staff: Voice channel renamed from \"{channel.name}\" to \"{name}\"{limit_info}", name=name)
-                await log_to_guild(itx.client, itx.guild,
-                                   f"Staff: Voice channel ({channel.id}) renamed from "
-                                   f"\"{old_name}\" to \"{name}\""
-                                   f" (by {itx.user.nick or itx.user.name}, {itx.user.id})")
-                await itx.response.send_message(warning + f"Staff: Voice channel successfully renamed to \"{name}\"",
-                                                ephemeral=True)  # allowed_mentions=discord.AllowedMentions.none())
+                    reason=f"Staff: Voice channel renamed from "
+                           f"\"{channel.name}\" to \"{name}\"{limit_info}",
+                    name=name
+                )
+                await log_to_guild(
+                    itx.client,
+                    itx.guild,
+                    f"Staff: Voice channel ({channel.id}) renamed from "
+                    f"\"{old_name}\" to \"{name}\" (by "
+                    f"{itx.user.nick or itx.user.name}, {itx.user.id})"
+                )
+                await itx.response.send_message(
+                    warning
+                    + f"Staff: Voice channel successfully renamed "
+                      f"to \"{name}\"",
+                    ephemeral=True,
+                    allowed_mentions=discord.AllowedMentions.none()
+                )
             if limit and name:
                 await channel.edit(
-                    reason=f"Staff: Voice channel edited from name: \"{channel.name}\" to \"{name}\" "
-                           f"and user limit from: \"{limit}\" to \"{old_limit}\"",
+                    reason=f"Staff: Voice channel edited from name: "
+                           f"\"{channel.name}\" to \"{name}\" and user limit "
+                           f"from: \"{limit}\" to \"{old_limit}\"",
                     user_limit=limit, name=name)
-                await log_to_guild(itx.client, itx.guild,
-                                   f"Staff: {itx.user.nick or itx.user.name} ({itx.user.id}) changed "
-                                   f"VC ({channel.id}) name \"{old_name}\" to \"{name}\" and user limit "
-                                   f"from \"{old_limit}\" to \"{limit}\"{limit_info}")
+                await log_to_guild(
+                    itx.client,
+                    itx.guild,
+                    f"Staff: {itx.user.nick or itx.user.name} "
+                    f"({itx.user.id}) changed VC ({channel.id}) name "
+                    f"\"{old_name}\" to \"{name}\" and user limit from "
+                    f"\"{old_limit}\" to \"{limit}\"{limit_info}"
+                )
                 await itx.response.send_message(
-                    warning + "Staff: Voice channel name and user limit successfully edited.", ephemeral=True,
-                    allowed_mentions=discord.AllowedMentions.none())
+                    warning
+                    + "Staff: Voice channel name and user limit "
+                      "successfully edited.",
+                    ephemeral=True,
+                    allowed_mentions=discord.AllowedMentions.none()
+                )
         except discord.errors.HTTPException as ex:
             ex_message = repr(ex).split("(", 1)[1][1:-2]
-            await log_to_guild(itx.client, itx.guild,
-                               f"Staff: Warning! >> {ex_message} << {itx.user.nick or itx.user.name} ({itx.user.id}) "
-                               f"tried to change {old_name} ({channel.id}) to {name}, but wasn't allowed to by "
-                               f"discord, probably because it's in a banned word list for discord's discovery "
-                               f"<@262913789375021056>")
+            await log_to_guild(
+                itx.client,
+                itx.guild,
+                f"Staff: Warning! >> {ex_message} <<"
+                f" {itx.user.nick or itx.user.name} ({itx.user.id}) tried to "
+                f"change {old_name} ({channel.id}) to {name}, but wasn't "
+                f"allowed to by discord, probably because it's in a banned "
+                f"word list for discord's discovery <@262913789375021056>"
+            )
diff --git a/extensions/emojistats/cogs/emojistats.py b/extensions/emojistats/cogs/emojistats.py
index fcb8f67..18042bf 100644
--- a/extensions/emojistats/cogs/emojistats.py
+++ b/extensions/emojistats/cogs/emojistats.py
@@ -16,6 +16,8 @@
 from resources.checks import not_in_dms_check
 
 
+# todo: convert to TypedDict \/
+
 #   Rina.emojistats         # snippet of <:ask:987785257661108324> in a test db at 2024-02-17T00:06+01:00
 # ------------------------------------------------------
 #               _id = Object('62f3004156483575bb3175de')
@@ -145,7 +147,11 @@ async def on_raw_reaction_add(
     @app_commands.rename(emoji_name="emoji")
     @app_commands.describe(emoji_name="Emoji you want to get data of")
     @app_commands.check(not_in_dms_check)
-    async def get_emoji_data(self, itx: discord.Interaction[Bot], emoji_name: str):
+    async def get_emoji_data(
+            self,
+            itx: discord.Interaction[Bot],
+            emoji_name: str
+    ):
         if ":" in emoji_name:
             emoji_name = emoji_name.strip().split(":")[2][:-1]
         emoji_id = emoji_name
diff --git a/extensions/emojistats/cogs/stickerstats.py b/extensions/emojistats/cogs/stickerstats.py
index 2e04ec6..9dc438d 100644
--- a/extensions/emojistats/cogs/stickerstats.py
+++ b/extensions/emojistats/cogs/stickerstats.py
@@ -11,9 +11,14 @@
 from resources.customs import Bot
 
 
-async def _add_to_sticker_data(sticker_name: str, async_rina_db: motorcore.AgnosticDatabase, sticker_id: str):
+async def _add_to_sticker_data(
+        sticker_name: str,
+        async_rina_db: motorcore.AgnosticDatabase,
+        sticker_id: str
+):
     """
-    Helper function to add sticker data to the mongo database when a sticker is sent in chat.
+    Helper function to add sticker data to the mongo database when
+    a sticker is sent in chat.
 
     :param sticker_name: The sticker name.
     :param async_rina_db: An async link to the MongoDB.
@@ -28,17 +33,30 @@ async def _add_to_sticker_data(sticker_name: str, async_rina_db: motorcore.Agnos
     location = "messageUsedCount"
 
     # increment the usage of the sticker in the dictionary
-    await collection.update_one(query, {"$inc": {location: 1}}, upsert=True)
-    await collection.update_one(query, {
-        "$set": {"lastUsed": datetime.now().timestamp(), "name": sticker_name}}, upsert=True)
+    await collection.update_one(
+        query,
+        {"$inc": {location: 1}},
+        upsert=True,
+    )
+    await collection.update_one(
+        query,
+        {"$set": {
+            "lastUsed": datetime.now().timestamp(),
+            "name": sticker_name
+        }},
+        upsert=True,
+    )
 
 
 class StickerStats(commands.Cog):
     def __init__(self, client: Bot):
         self.client = client
 
-    stickerstats = app_commands.Group(name='stickertats',
-                                      description='Get information about sticker usage in messages and reactions')
+    stickerstats = app_commands.Group(
+        name='stickertats',
+        description='Get information about sticker usage in messages '
+                    'and reactions'
+    )
 
     @commands.Cog.listener()
     async def on_message(self, message: discord.Message):
@@ -46,22 +64,35 @@ async def on_message(self, message: discord.Message):
             return
 
         for sticker in message.stickers:
+            # todo: resolve shared sticker issue
             # if sticker in message.guild.stickers:
-            #     # only track if it's actually a guild's sticker; not some outsider one
-            await _add_to_sticker_data(sticker.name, self.client.async_rina_db, str(sticker.id))
+            #     # only track if it's actually a guild's sticker; not
+            #     #  some outsider one.
+            await _add_to_sticker_data(
+                sticker.name,
+                self.client.async_rina_db,
+                str(sticker.id)
+            )
 
-    @stickerstats.command(name="getstickerdata", description="Get sticker usage data from an ID!")
+    @stickerstats.command(name="getstickerdata",
+                          description="Get sticker usage data from an ID!")
     @app_commands.rename(sticker_name="sticker")
     @app_commands.describe(sticker_name="Sticker you want to get data of")
     @app_commands.check(not_in_dms_check)
-    async def get_sticker_data(self, itx: discord.Interaction[Bot], sticker_name: str):
+    async def get_sticker_data(
+            self,
+            itx: discord.Interaction[Bot],
+            sticker_name: str
+    ):
         if ":" in sticker_name:
-            # idk why people would, but idk the format for stickers so ill just assume  or something idk
+            # idk why people would, but idk the format for stickers so
+            #  ill just assume  or something idk.
             sticker_name = sticker_name.strip().split(":")[1][:-1]
         sticker_id = sticker_name
         if not sticker_id.isdecimal():
             await itx.response.send_message(
-                "You need to fill in the ID of the sticker. This ID can't contain other characters. Only numbers.",
+                "You need to fill in the ID of the sticker. This ID"
+                " can't contain other characters. Only numbers.",
                 ephemeral=True)
             return
 
@@ -69,42 +100,55 @@ async def get_sticker_data(self, itx: discord.Interaction[Bot], sticker_name: st
         query = {"id": sticker_id}
         sticker_response = await collection.find_one(query)
         if sticker_response is None:
-            await itx.response.send_message("That sticker doesn't have data yet. It hasn't been used since "
-                                            "we started tracking the data yet. (, ,"
-                                            "or since Rina joined the server)",
-                                            ephemeral=True)
+            await itx.response.send_message(
+                "That sticker doesn't have data yet. It hasn't been used "
+                "since we started tracking the data yet. "
+                "(, , "
+                "or since Rina joined the server)",
+                ephemeral=True,
+            )
             return
 
         msg_used = sticker_response.get('messageUsedCount', 0)
 
-        sticker_search = (sticker_response["name"] + ":" + sticker_response["id"])
+        sticker_search = (sticker_response["name"]
+                          + ":" + sticker_response["id"])
         sticker = sticker_search
         msg_last_use_time = datetime.fromtimestamp(
             sticker_response['lastUsed'],
             tz=timezone.utc
         ).strftime('%Y-%m-%d (yyyy-mm-dd) at %H:%M:%S')
 
-        await itx.response.send_message(f"Data for {sticker}" + "  ".replace(':', '\\:') +
-                                        f"(`{sticker}`)\n"
-                                        f"messageUsedCount: {msg_used}\n"
-                                        f"Last used: {msg_last_use_time}",
-                                        ephemeral=True)
-
-    @stickerstats.command(name="get_unused_stickers", description="Get the least-used stickers")
-    @app_commands.describe(public="Do you want everyone in this channel to be able to see this result?",
-                           max_results="How many stickers do you want to retrieve at most? (may return fewer)",
-                           used_max="Up to how many times may the sticker have been used? (default: 10)")
+        await itx.response.send_message(
+            f"Data for {sticker}" + "  ".replace(':', '\\:')
+            + f"(`{sticker}`)\n"
+              f"messageUsedCount: {msg_used}\n"
+              f"Last used: {msg_last_use_time}",
+            ephemeral=True)
+
+    @stickerstats.command(name="get_unused_stickers",
+                          description="Get the least-used stickers")
+    @app_commands.describe(
+        public="Do you want everyone in this channel to be able to see "
+               "this result?",
+        max_results="How many stickers do you want to retrieve at most? "
+                    "(may return fewer)",
+        used_max="Up to how many times may the sticker have been used? "
+                 "(default: 10)")
     @app_commands.check(not_in_dms_check)
     async def get_unused_stickers(
-            self, itx: discord.Interaction[Bot], public: bool = False,
-            max_results: int = 10, used_max: int = sys.maxsize
+            self,
+            itx: discord.Interaction[Bot],
+            public: bool = False,
+            max_results: int = 10,
+            used_max: int = sys.maxsize,
     ):
         await itx.response.defer(ephemeral=not public)
 
         unused_stickers = []
 
         collection = itx.client.async_rina_db["stickerstats"]
-        query = {
+        query: dict = {
             "$expr": {
                 "$lte": [
                     {"$add": [
@@ -120,8 +164,9 @@ async def get_unused_stickers(
         if used_max < 0:
             used_max = 0
         if used_max != sys.maxsize:
-            # only limit query on '_UsedCount' if you want to limit for it.
-            # Some entries don't have a value for it- Don't want to search for a "0" then.
+            # Only limit query on '_UsedCount' if the user wants to
+            #  put a limit on it. Some entries don't have a value for
+            #  it, so don't search for a "0" then.
             query["messageUsedCount"] = {"$lte": used_max}
 
         sticker_stats: list[dict[str, str | int | bool]] = \
@@ -160,20 +205,29 @@ async def get_unused_stickers(
             output = output[:(2000 - len(header) - len(warning) - 5)] + warning
         await itx.followup.send(content=header + output, ephemeral=not public)
 
-    @stickerstats.command(name="getstickertop10", description="Get top 10 most used stickers")
+    @stickerstats.command(name="getstickertop10",
+                          description="Get top 10 most used stickers")
     @app_commands.check(not_in_dms_check)
     async def get_sticker_top_10(self, itx: discord.Interaction):
         collection = itx.client.async_rina_db["stickerstats"]
         output = ""
         for source_type in ["messageUsedCount"]:
             results = []
-            async for sticker in collection.find({}, limit=10, sort=[(source_type, DESCENDING)]):
-                sticker_full = "<" + sticker["name"] + "\\:" + sticker["id"] + ">"
+            async for sticker in collection.find(
+                    {},
+                    limit=10,
+                    sort=[(source_type, DESCENDING)]
+            ):
+                sticker_full = ("<" + sticker["name"]
+                                + "\\:" + sticker["id"] + ">")
                 try:
-                    results.append(f"> **{sticker[source_type]}**: {sticker_full}")
+                    results.append(
+                        f"> **{sticker[source_type]}**: {sticker_full}")
                 except KeyError:
-                    # leftover sticker doesn't have a value for messageUsedCount yet
+                    # leftover sticker doesn't have a value for
+                    #  messageUsedCount yet
                     pass
-            output += "\nTop 10 stickers for " + source_type.replace("UsedCount", "") + "s:\n"
+            output += ("\nTop 10 stickers for "
+                       + source_type.replace("UsedCount", "") + "s:\n")
             output += '\n'.join(results)
         await itx.response.send_message(output, ephemeral=True)
diff --git a/extensions/getmemberdata/cogs/memberdata.py b/extensions/getmemberdata/cogs/memberdata.py
index f65d0ba..ccf0959 100644
--- a/extensions/getmemberdata/cogs/memberdata.py
+++ b/extensions/getmemberdata/cogs/memberdata.py
@@ -1,5 +1,6 @@
 import asyncio
-# for sleep(0.1) to prevent blocking: allow discord and other processes to send a heartbeat and function.
+# for sleep(0.1) to prevent blocking: allow discord and other processes
+#  to send a heartbeat and function.
 from datetime import datetime, timezone
 import matplotlib.pyplot as plt
 import pandas as pd  # for graphing member joins/leaves/verifications
@@ -22,16 +23,21 @@ async def _add_to_data(member, event_type, async_rina_db: AgnosticDatabase):
         data = await collection.find_one(query)
 
     try:
-        # see if this user already has data, if so, add a new joining time to the list
+        # see if this user already has data, if so, add a new joining
+        #  time to the list
         data[event_type][str(member.id)].append(datetime.now().timestamp())
     except IndexError:
         data[event_type][str(member.id)] = [datetime.now().timestamp()]
     except KeyError:
         data[event_type] = {}
         data[event_type][str(member.id)] = [datetime.now().timestamp()]
-    await collection.update_one(query,
-                                {"$set": {f"{event_type}.{member.id}": data[event_type][str(member.id)]}},
-                                upsert=True)
+    await collection.update_one(
+        query,
+        {"$set": {
+            f"{event_type}.{member.id}": data[event_type][str(member.id)]
+        }},
+        upsert=True
+    )
 
 
 class MemberData(commands.Cog):
@@ -44,26 +50,50 @@ async def on_member_join(self, member):
 
     @commands.Cog.listener()
     async def on_member_remove(self, member):
-        role = discord.utils.find(lambda r: r.name == 'Verified', member.guild.roles)
+        role = discord.utils.find(lambda r: r.name == 'Verified',
+                                  member.guild.roles)
         if role in member.roles:
-            await _add_to_data(member, "left verified", self.client.async_rina_db)
+            await _add_to_data(
+                member,
+                "left verified",
+                self.client.async_rina_db
+            )
         else:
-            await _add_to_data(member, "left unverified", self.client.async_rina_db)
+            await _add_to_data(
+                member,
+                "left unverified",
+                self.client.async_rina_db
+            )
 
     @commands.Cog.listener()
     async def on_member_update(self, before, after):
-        role = discord.utils.find(lambda r: r.name == 'Verified', before.guild.roles)
+        role = discord.utils.find(lambda r: r.name == 'Verified',
+                                  before.guild.roles)
         if role not in before.roles and role in after.roles:
-            await _add_to_data(after, "verified", self.client.async_rina_db)
+            await _add_to_data(
+                after,
+                "verified",
+                self.client.async_rina_db
+            )
 
-    @app_commands.command(name="getmemberdata", description="See joined, left, and recently verified users in x days")
-    @app_commands.describe(lower_bound="Get data from [period] days ago",
-                           upper_bound="Get data up to [period] days ago",
-                           doubles="If someone joined twice, are they counted double? (y/n or 1/0)",
-                           public="Send the output to everyone in the channel")
+    @app_commands.command(
+        name="getmemberdata",
+        description="See joined, left, and recently verified users in x days"
+    )
+    @app_commands.describe(
+        lower_bound="Get data from [period] days ago",
+        upper_bound="Get data up to [period] days ago",
+        doubles="If someone joined twice, are they counted double? "
+                "(y/n or 1/0)",
+        public="Send the output to everyone in the channel"
+    )
     @app_commands.check(not_in_dms_check)
     async def get_member_data(
-            self, itx: discord.Interaction[Bot], lower_bound: str, upper_bound: str = None, doubles: bool = False,
+            self,
+            itx: discord.Interaction[Bot],
+            lower_bound: str,
+            upper_bound: str = None,
+            doubles: bool = False,
             public: bool = False
     ):
         # todo: split function into multiple subfunctions.
@@ -73,32 +103,50 @@ async def get_member_data(
             lower_bound = float(lower_bound)
             upper_bound = float(upper_bound)
             if lower_bound <= 0:
-                await itx.response.send_message("Your period (data in the past [x] days) has to be above 0!",
-                                                ephemeral=True)
+                await itx.response.send_message(
+                    "Your period (data in the past [x] days) has to be "
+                    "above 0!",
+                    ephemeral=True)
                 return
             # if period < 0.035:
-            #     await itx.response.send_message("Idk why but it seems to break when period is smaller than 0.0035, "
-            #                                     "so better not use it.", ephemeral=True)
+            #     await itx.response.send_message(
+            #         "Idk why but it seems to break when period is "
+            #         "smaller than 0.0035, so better not use it.",
+            #         ephemeral=True
+            #     )
             #     return
-            # todo: figure out why you can't fill in less than 0.0035: ValueError: All arrays must be of the same length
+            # todo: figure out why you can't fill in less than 0.0035:
+            #  ValueError: All arrays must be of the same length
             if lower_bound > 10958:
-                await itx.response.send_message("... I doubt you'll be needing to look 30 years into the past..",
-                                                ephemeral=True)
+                await itx.response.send_message(
+                    "... I doubt you'll be needing to look 30 years into "
+                    "the past..",
+                    ephemeral=True
+                )
                 return
             if upper_bound > lower_bound:
                 await itx.response.send_message(
-                    "Your upper bound can't be bigger (-> more days ago) than the lower bound!", ephemeral=True)
+                    "Your upper bound can't be bigger (-> more days ago) "
+                    "than the lower bound!",
+                    ephemeral=True
+                )
                 return
         except ValueError:
-            await itx.response.send_message("Your period has to be a number for the amount of days that have passed",
-                                            ephemeral=True)
+            await itx.response.send_message(
+                "Your period has to be a number for the amount of days "
+                "that have passed",
+                ephemeral=True
+            )
             return
 
-        accuracy = (lower_bound - upper_bound) * 2400  # divide graph into 36 sections : 86400/36=2400
+        # divide graph into 36 sections : 86400/36=2400
+        accuracy = (lower_bound - upper_bound) * 2400
         lower_bound *= 86400  # days to seconds
         upper_bound *= 86400
-        # Get a list of people (in this server) that joined at certain times. Maybe round these to a certain
-        # factor (don't overstress the x-axis). These certain times are in a period of "now" and "[period] seconds ago"
+        # Get a list of people (in this server) that joined at certain
+        #  times. Maybe round these to a certain factor (don't
+        #  overstress the x-axis). These certain times are in a period
+        #  of "now" and "[period] seconds ago".
         totals = {}
         results = {}
         warning = ""
@@ -111,12 +159,14 @@ async def get_member_data(
         data = await collection.find_one(query)
         if data is None:
             await itx.response.send_message(
-                "Not enough data is configured to do this action! Please hope someone joins sometime soon lol",
+                "Not enough data is configured to do this action! Please "
+                "hope someone joins sometime soon lol",
                 ephemeral=True)
             return
         await itx.response.defer(ephemeral=not public)
 
-        # gather timestamps in timeframe, as well as the lowest and highest timestamps
+        # gather timestamps in timeframe, as well as the lowest and
+        # highest timestamps
         for y in data:
             if type(data[y]) is not dict:
                 continue
@@ -124,14 +174,16 @@ async def get_member_data(
             results[y] = {}
             for member in data[y]:
                 for time in data[y][member]:
-                    # if the current time minus the amount of seconds in every day in the period since now, is
-                    # still older than more recent joins, append it
+                    # if the current time minus the amount of seconds in
+                    # every day in the period since now, is still older than
+                    # more recent joins, append it
                     if current_time - lower_bound < time:
                         if current_time - upper_bound > time:
                             column.append(time)
                             if not doubles:
                                 break
-            await asyncio.sleep(0.1)  # allow heartbeat or recognising other commands
+            # allow heartbeat or recognising other commands
+            await asyncio.sleep(0.1)
             totals[y] = len(column)
             for time in range(len(column)):
                 column[time] = int(column[time] / accuracy) * accuracy
@@ -139,9 +191,11 @@ async def get_member_data(
                     results[y][column[time]] += 1
                 else:
                     results[y][column[time]] = 1
-            await asyncio.sleep(0.1)  # allow heartbeat or recognising other commands
+            # allow heartbeat or recognising other commands
+            await asyncio.sleep(0.10)
             if len(column) == 0:
-                warning += f"\nThere were no '{y}' users found for this time period."
+                warning += (f"\nThere were no '{y}' users found for this "
+                            f"time period.")
                 results[y] = {}
             else:
                 time_list = sorted(column)
@@ -150,8 +204,8 @@ async def get_member_data(
                 if max_time < time_list[-1]:
                     max_time = time_list[-1]
 
-        # if the lowest timestamps are lower than the lowest timestamp, then set all missing
-        # data to 0 (up until the graph has data)
+        # if the lowest timestamps are lower than the lowest timestamp,
+        # then set all missing data to 0 (up until the graph has data)
         min_time_db = min_time
         for y in data:
             if type(data[y]) is not dict:
@@ -159,19 +213,27 @@ async def get_member_data(
             min_time = min_time_db
             while min_time <= max_time:
                 if min_time not in results[y]:
-                    # remove the '0' line from before tracking verifiedness of people after leaving
-                    if ((min_time > 1700225500 and y == "left") or  # backwards compatability
+                    # remove the '0' line from before tracking
+                    #  verifiedness of people after leaving
+                    if (
+                            (min_time > 1700225500 and y == "left") or
+                            # ^ backwards compatability
                             (min_time < 1700225000 and y == "left verified") or
-                            (min_time < 1700225000 and y == "left unverified")):
+                            (min_time < 1700225000 and y == "left unverified")
+                    ):
+                        # todo: consider just migrating all 'left' to
+                        #  left-verified.
                         results[y][min_time] = None
                     else:
                         results[y][min_time] = 0
                 min_time += accuracy
 
         for i in results:  # sort data by key
-            results[i] = {timestamp: results[i][timestamp] for timestamp in sorted(results[i])}
+            results[i] = {timestamp: results[i][timestamp]
+                          for timestamp in sorted(results[i])}
 
-        await asyncio.sleep(0.1)  # allow heartbeat or recognising other commands
+        # allow heartbeat or recognising other commands
+        await asyncio.sleep(0.1)
 
         # make graph
         try:
@@ -182,12 +244,16 @@ async def get_member_data(
                 try:
                     d[y] = [results[y][i] for i in results[y]]
                 except KeyError:
-                    # await itx.followup.send(f"{ex} did not have data, thus could not make the graph.")
+                    # await itx.followup.send(
+                    #     f"{ex} did not have data, thus could not make "
+                    #     f"the graph."
+                    # )
                     # return
                     continue
             df = pd.DataFrame(data=d)
             fig, (ax1) = plt.subplots()
-            fig.suptitle(f"Member data from {lower_bound / 86400} to {upper_bound / 86400} days ago")
+            fig.suptitle(f"Member data from {lower_bound / 86400} to "
+                         f"{upper_bound / 86400} days ago")
             fig.tight_layout(pad=1.0)
             color = {
                 "joined": "g",
@@ -209,23 +275,32 @@ async def get_member_data(
 
             tick_loc = [i for i in df['time'][::3]]
             if (lower_bound - upper_bound) / 86400 <= 1:
-                tick_disp = [datetime.fromtimestamp(i, timezone.utc).strftime('%H:%M') for i in tick_loc]
+                tick_disp = [datetime
+                             .fromtimestamp(i, timezone.utc)
+                             .strftime('%H:%M')
+                             for i in tick_loc]
             else:
-                tick_disp = [datetime.fromtimestamp(i, timezone.utc).strftime('%Y-%m-%dT%H:%M') for i in tick_loc]
+                tick_disp = [datetime
+                             .fromtimestamp(i, timezone.utc)
+                             .strftime('%Y-%m-%dT%H:%M')
+                             for i in tick_loc]
 
             # plt.xticks(tick_loc, tick_disp, rotation='vertical')
             # plt.setp(tick_disp, rotation=45, horizontalalignment='right')
-            ax1.set_xticks(tick_loc,
-                           labels=tick_disp,
-                           horizontalalignment='right',
-                           minor=False,
-                           rotation=30)
+            ax1.set_xticks(
+                tick_loc,
+                labels=tick_disp,
+                horizontalalignment='right',
+                minor=False,
+                rotation=30
+            )
             ax1.grid(visible=True, which='major', axis='both')
             fig.subplots_adjust(bottom=0.180, top=0.90, left=0.1, hspace=0.1)
             plt.savefig('outputs/userJoins.png', dpi=300)
         except ValueError:
             await itx.followup.send(
-                "You encountered a ValueError! Mia has been sent an error report to hopefully be able to fix it :)",
+                "You encountered a ValueError! Mia has been sent an error "
+                "report to hopefully be able to fix it :)",
                 ephemeral=True)
             raise
         output = ""
@@ -238,11 +313,13 @@ async def get_member_data(
         except KeyError:
             pass
         try:
-            output += f"`{totals['left verified']}` members left after being verified, "
+            output += (f"`{totals['left verified']}` members left after "
+                       f"being verified, ")
         except KeyError:
             pass
         try:
-            output += f"`{totals['left unverified']}` members left while unverified, "
+            output += (f"`{totals['left unverified']}` members left while "
+                       f"unverified, ")
         except KeyError:
             pass
         try:
@@ -250,6 +327,8 @@ async def get_member_data(
         except KeyError:
             pass
         await itx.followup.send(
-            f"From {lower_bound / 86400} to {upper_bound / 86400} days ago, {output} "
-            f"(with{'out' * (1 - doubles)} doubles)" + warning,
-            file=discord.File('outputs/userJoins.png'))
+            f"From {lower_bound / 86400} to {upper_bound / 86400} days ago, "
+            f"{output} (with{'out' * (1 - doubles)} doubles)"
+            + warning,
+            file=discord.File('outputs/userJoins.png')
+        )
diff --git a/extensions/help/cogs/helpcommand.py b/extensions/help/cogs/helpcommand.py
index f80190f..a3d24e9 100644
--- a/extensions/help/cogs/helpcommand.py
+++ b/extensions/help/cogs/helpcommand.py
@@ -7,22 +7,31 @@
 from resources.utils.stringhelper import replace_string_command_mentions
 
 from extensions.help.helppages import help_pages, aliases, FIRST_PAGE
-from extensions.help.utils import get_nearest_help_pages_from_page, generate_help_page_embed
+from extensions.help.utils import (
+    get_nearest_help_pages_from_page,
+    generate_help_page_embed
+)
 from extensions.help.views.helppage import HelpPageView
 
 
-async def send_help_menu(itx: discord.Interaction[Bot], requested_page: int = FIRST_PAGE):
+async def send_help_menu(
+        itx: discord.Interaction[Bot],
+        requested_page: int = FIRST_PAGE
+):
     if requested_page not in help_pages:
-        min_index, max_index = get_nearest_help_pages_from_page(requested_page, list(help_pages))
-        relative_page_location_details = f"(nearest pages are `{min_index}` and `{max_index}`)."
+        min_index, max_index = get_nearest_help_pages_from_page(
+            requested_page, list(help_pages))
+        relative_page_location_details = (f"(nearest pages are `{min_index}` "
+                                          f"and `{max_index}`).")
         await itx.response.send_message(
             replace_string_command_mentions(
                 (  # easter egg
-                    f"This page (`{requested_page}`) does not exist! " if requested_page != 404 else
+                    f"This page (`{requested_page}`) does not exist! "
+                    if requested_page != 404 else
                     "`404`: Page not found!"
-                ) +
-                f" {relative_page_location_details} " +
-                "Try %%help%% `page:1` or use the page keys to get to the right page number!",
+                ) + f" {relative_page_location_details} "
+                + "Try %%help%% `page:1` or use the page keys to get to the "
+                  "right page number!",
                 itx.client
             ),
             ephemeral=True
@@ -30,19 +39,32 @@ async def send_help_menu(itx: discord.Interaction[Bot], requested_page: int = FI
         return
 
     user_is_staff = is_admin(itx, itx.user)
-    if not user_is_staff and help_pages[requested_page].get("staff_only", False):
+    if (not user_is_staff
+            and help_pages[requested_page].get("staff_only", False)):
         # user is not staff but the page is staff-only
-        await itx.response.send_message("This page is only available to admins.", ephemeral=True)
+        await itx.response.send_message(
+            "This page is only available to admins.",
+            ephemeral=True
+        )
         return
 
-    embed = generate_help_page_embed(help_pages[requested_page], requested_page, itx.client)
+    embed = generate_help_page_embed(
+        help_pages[requested_page],
+        requested_page,
+        itx.client
+    )
 
-    await itx.response.send_message(embed=embed,
-                                    view=HelpPageView(requested_page, help_pages, user_is_staff),
-                                    ephemeral=True)
+    await itx.response.send_message(
+        embed=embed,
+        view=HelpPageView(requested_page, help_pages, user_is_staff),
+        ephemeral=True
+    )
 
 
-async def _help_page_autocomplete(itx: discord.Interaction[Bot], current: str) -> list[app_commands.Choice[int]]:
+async def _help_page_autocomplete(
+        itx: discord.Interaction[Bot],
+        current: str
+) -> list[app_commands.Choice[int]]:
     results = []
     added_pages = []
 
@@ -56,18 +78,22 @@ async def _help_page_autocomplete(itx: discord.Interaction[Bot], current: str) -
             pass
 
         if current_page is not None and current_page in aliases:
-            if user_is_staff or not help_pages[current_page].get("staff_only", False):
-                results.append(app_commands.Choice(name=aliases[current_page][0], value=current_page))
+            if (user_is_staff
+                    or not help_pages[current_page].get("staff_only", False)):
+                results.append(app_commands.Choice(
+                    name=aliases[current_page][0], value=current_page))
                 return results
 
     # search aliases
     for page in aliases:
         for alias in aliases[page]:
-            if not user_is_staff and help_pages[page].get("staff_only", False):
+            if (not user_is_staff
+                    and help_pages[page].get("staff_only", False)):
                 continue
 
             if current.lower() in alias.lower():
-                results.append(app_commands.Choice(name=alias[:100], value=page))
+                results.append(app_commands.Choice(
+                    name=alias[:100], value=page))
                 added_pages.append(page)
                 break  # only add each page once
         if len(results) >= 10:
@@ -78,14 +104,17 @@ async def _help_page_autocomplete(itx: discord.Interaction[Bot], current: str) -
 
     # search page descriptions
     for page in help_pages:
-        if not user_is_staff and help_pages[page].get("staff_only", False):
+        if (not user_is_staff
+                and help_pages[page].get("staff_only", False)):
             continue
 
         if page in added_pages:
             continue
         if current.lower() in help_pages[page]["description"].lower():
-            results.append(app_commands.Choice(name=help_pages[page]["title"][:100], value=page))
-            # added_pages.append(page)  # unnecessary because it won't iterate pages after this.
+            results.append(app_commands.Choice(
+                name=help_pages[page]["title"][:100], value=page))
+            # added_pages.append(page)
+            # ^ unnecessary because it won't iterate pages after this.
         if len(results) >= 10:
             break
 
@@ -96,14 +125,23 @@ class HelpCommand(commands.Cog):
     def __init__(self):
         pass
 
-    @app_commands.command(name="help", description="A help command to learn more about me!")
-    @app_commands.describe(page="What page do you want to jump to? (useful if sharing commands)")
+    @app_commands.command(name="help",
+                          description="A help command to learn more about me!")
+    @app_commands.describe(page="What page do you want to jump to? (useful "
+                                "if sharing commands)")
     @app_commands.autocomplete(page=_help_page_autocomplete)
-    async def help(self, itx: discord.Interaction[Bot], page: int = FIRST_PAGE):
+    async def help(
+            self,
+            itx: discord.Interaction[Bot],
+            page: int = FIRST_PAGE
+    ):
         await send_help_menu(itx, page)
 
-    @app_commands.command(name="commands", description="A help command to learn more about me!")
-    @app_commands.describe(page="What page do you want to jump to? (useful if sharing commands)")
+    @app_commands.command(name="commands",
+                          description="A help command to learn more about me!")
+    @app_commands.describe(page="What page do you want to jump to? "
+                                "(useful if sharing commands)")
     @app_commands.autocomplete(page=_help_page_autocomplete)
-    async def commands(self, itx: discord.Interaction[Bot], page: int = FIRST_PAGE):
+    async def commands(self, itx: discord.Interaction[Bot],
+                       page: int = FIRST_PAGE):
         await send_help_menu(itx, page)
diff --git a/extensions/help/helppage.py b/extensions/help/helppage.py
index 44cd2fe..0c910bc 100644
--- a/extensions/help/helppage.py
+++ b/extensions/help/helppage.py
@@ -1,4 +1,4 @@
-from typing import TypedDict
+from typing import TypedDict, Required
 
 
 class HelpPage(TypedDict, total=False):
@@ -13,7 +13,7 @@ class HelpPage(TypedDict, total=False):
 
         Discord embeds only allow up to 10 fields.
     """
-    title: str
-    description: str
+    title: Required[str]
+    description: Required[str]
     fields: list[tuple[str, str]]
     staff_only: bool
diff --git a/extensions/help/helppages.py b/extensions/help/helppages.py
index 65f8965..2aee417 100644
--- a/extensions/help/helppages.py
+++ b/extensions/help/helppages.py
@@ -8,50 +8,67 @@
 help_pages: dict[int, HelpPage] = {
     0: HelpPage(  # fallback default page
         title="Rina's fallback help page",
-        description="""Hi there! This bot has a whole bunch of commands. Let me introduce you to some:
-%%add_poll_reactions%%: Add an up-/down-vote emoji to a message (for voting)
-%%commands%% or %%help%%: See this help page
-%%compliment%%: Rina can compliment others (matching their pronoun role)
-%%convert_unit%%: Convert a value from one to another! Distance, speed, currency, etc.
-%%dictionary%%: Search for an lgbtq+-related or dictionary term!
-%%equaldex%%: See LGBTQ safety and rights in a country (with API)
-%%math%%: Ask Wolfram|Alpha for math or science help
-%%nameusage gettop%%: See how many people are using the same name
-%%qotw%% and %%developer_request%%: Suggest a Question Of The Week or Bot Suggestion to staff
-%%reminder reminders%%: Make or see your reminders
-%%roll%%: Roll some dice with a random result
-%%tag%%: Get information about some of the server's extra features
-%%todo%%: Make, add, or remove items from your to-do list
-%%toneindicator%%: Look up which tone tag/indicator matches your input (eg. /srs)
-
-Make a custom voice channel by joining "Join to create VC" (use %%tag%% `tag:customvc` for more info)
-%%editvc%%: edit the name or user limit of your custom voice channel
-%%vctable about%%: Learn about making your voice chat more on-topic!
-""",
+        description=(
+            "Hi there! This bot has a whole bunch of commands. Let "
+            "me introduce you to some:\n"
+            "%%add_poll_reactions%%: Add an up-/down-vote emoji to a message "
+            "(for voting)\n"
+            "%%commands%% or %%help%%: See this help page\n"
+            "%%compliment%%: Rina can compliment others (matching their "
+            "pronoun role)\n"
+            "%%convert_unit%%: Convert a value from one to another! Distance, "
+            "speed, currency, etc.\n"
+            "%%dictionary%%: Search for an lgbtq+-related or dictionary "
+            "term!\n"
+            "%%equaldex%%: See LGBTQ safety and rights in a country "
+            "(with API)\n"
+            "%%math%%: Ask Wolfram|Alpha for math or science help\n"
+            "%%nameusage gettop%%: See how many people are using the "
+            "same name\n"
+            "%%qotw%% and %%developer_request%%: Suggest a Question Of The "
+            "Week or Bot Suggestion to staff\n"
+            "%%reminder reminders%%: Make or see your reminders\n"
+            "%%roll%%: Roll some dice with a random result\n"
+            "%%tag%%: Get information about some of the server's extra "
+            "features\n"
+            "%%todo%%: Make, add, or remove items from your to-do list\n"
+            "%%toneindicator%%: Look up which tone tag/indicator matches your "
+            "input (eg. /srs)\n"
+            "\n"
+            "Make a custom voice channel by joining \"Join to create VC\" "
+            "(use %%tag%% `tag:customvc` for more info)\n"
+        ),
         fields=[],
     ),
     # region Default pages (home / index)
     1: HelpPage(
         title="Rina's commands",
-        description="Hey there. This is Rina. I'm a bot that does silly things for TransPlace and some linked servers. "
-                    "I'm mostly being maintained by <@262913789375021056>.\n"
-                    "I can do many things like looking up tone indicators or words, or sharing server info and "
-                    "questions of the week. To share my commands, I've made this cool interactive menu.\n"
+        description="Hey there. This is Rina. I'm a bot that does silly "
+                    "things for TransPlace and some linked servers. I'm "
+                    "mostly being maintained by <@262913789375021056>.\n"
+                    "I can do many things like looking up tone indicators "
+                    "or words, or sharing server info and questions of the "
+                    "week. To share my commands, I've made this cool "
+                    "interactive menu.\n"
                     "\n"
-                    "Use the buttons below to cycle through different categories of commands!\n"
+                    "Use the buttons below to cycle through different "
+                    "categories of commands!\n"
                     "\n"
                     "📋 Index (page 2)\n"
                     "◀️ Previous page:\n"
                     "▶️ Next page: Index\n"
                     "🔢 Jump to page\n"
                     "\n"
-                    "Do you have a cool bot idea? Use %%developer_request%% to suggest them to staff!",
+                    "Do you have a cool bot idea? Use %%developer_request%% "
+                    "to suggest them to staff!",
         fields=[],
     ),
     2: HelpPage(
         title="Index",
-        description="Here's a list of all the categories. You can use the :1234: button to quickly hop "
-                    "between these pages. Press :clipboard: to quickly go back to this index page.\n"
+        description="Here's a list of all the categories. You can use the "
+                    ":1234: button to quickly hop "
+                    "between these pages. Press :clipboard: to quickly go "
+                    "back to this index page.\n"
                     "\n"
                     ". . **1:** Welcome page\n"
                     ". . **2:** Index\n"
@@ -69,19 +86,22 @@
     # endregion
 
     # ### Bot functions:
-    # #      add_poll_reactions, version, help, commands, get_rina_command_mention, changechannel
+    # #      add_poll_reactions, version, help, commands,
+    # #      get_rina_command_mention, changechannel
     # ### Utility:
     # #      roll, convert_unit, reminders, todo
     # ### Suggestion commands:
     # #      qotw, developer_request
     # ### Internet search commands:
-    # #      equaldex, math, dictionary, toneindicator (kinda not really API / internet but oh well)
+    # #      equaldex, math, dictionary,
+    # #      toneindicator (kinda not really API / internet but oh well)
     # ### Server search commands:
     # #      getemojidata, getunusedemojis, getemojitop10,
     # #      getstickerdata, getunusedstickers, getstickertop10,
     # #      getmemberdata, nameusage name/gettop
     # ### Chat actions:
-    # #      abababa/awawawa, headpats, not cute, compliment, complimentblacklist
+    # #      abababa/awawawa, headpats, not cute, compliment,
+    # #      complimentblacklist
     # ### Server functions:
     # #      tags, remove_role
     # ### Voice channels:
@@ -90,7 +110,8 @@
     # region Expanded index pages (commands)
     3: HelpPage(  # index: Bot functions
         title="Bot functions",
-        description="This section will list some simple rina-related commands that may help you:\n"
+        description="This section will list some simple rina-related commands "
+                    "that may help you:\n"
                     "\n"
                     ". . **101:** /help and /commands\n"
                     ". . **102:** version\n"
@@ -101,7 +122,8 @@
     ),
     4: HelpPage(  # index: Utility
         title="Utility",
-        description="Some commands help in your daily life, like the following commands:\n"
+        description="Some commands help in your daily life, like the "
+                    "following commands:\n"
                     "\n"
                     ". . **111:** roll\n"
                     ". . **113:** reminder\n"
@@ -111,7 +133,8 @@
     ),
     5: HelpPage(  # index: Suggestion commands
         title="Suggestion commands",
-        description="Sometimes you just want to share your ideas with other people, you know?\b"
+        description="Sometimes you just want to share your ideas with other "
+                    "people, you know?\n"
                     "\n"
                     ". . **121:** developer_request\n"
                     ". . **122:** qotw (question of the week)\n",
@@ -119,7 +142,8 @@
     ),
     6: HelpPage(  # index: Internet search commands
         title="Internet search commands",
-        description="Googling is hard. That's why I made some searching commands!\n"
+        description="Googling is hard. That's why I made some searching "
+                    "commands!\n"
                     "\n"
                     ". . **131:** equaldex (LGBTQ laws in countries)\n"
                     ". . **132:** qotw (question of the week)\n",
@@ -127,8 +151,10 @@
     ),
     7: HelpPage(
         title="Server search commands",
-        description="This section is still being worked on! (help, so much text to write D: )\n"
-                    "Scroll a few pages ahead to see what the rest of the help pages look like!",
+        description="This section is still being worked on! (help, so "
+                    "much text to write D: )\n"
+                    "Scroll a few pages ahead to see what the rest of "
+                    "the help pages look like!",
         fields=[],
     ),
     8: HelpPage(
@@ -154,8 +180,10 @@
     ),
     90: HelpPage(
         title="Bot setup",
-        description="Rina isn't really meant for more than 1 server. But.. I tried my best :D\n"
-                    "There are some commands to help customize the features in your server.\n"
+        description="Rina isn't really meant for more than 1 server. But.. "
+                    "I tried my best :D\n"
+                    "There are some commands to help customize the features "
+                    "in your server.\n"
                     "\n"
                     ". . **901:** settings",
         fields=[],
@@ -164,111 +192,130 @@
     # region Bot Functions
     101: HelpPage(  # /help and /commands
         title="The help command and command list.",
-        description="Obviously the most important command on the bot, this command attempts to explain "
-                    "every command and function in the bot. This page will also be the first page "
-                    "to show the layout of these commands.\n"
+        description="Obviously the most important command on the bot, this "
+                    "command attempts to explain every command and function "
+                    "in the bot. This page will also be the first page to "
+                    "show the layout of these commands.\n"
                     "\n"
-                    "After running the command, Rina replies with an embed showing information about "
-                    "the bot or command. There are interactable buttons to go back to start, jump to the "
-                    "previous or next page, or jump to a specific page number. You can also provide a "
-                    "`page ` argument to jump to a page immediately. Useful for sharing help command "
-                    "pages.\n"
+                    "After running the command, Rina replies with an embed "
+                    "showing information about the bot or command. There "
+                    "are interactable buttons to go back to start, jump to "
+                    "the previous or next page, or jump to a specific page "
+                    "number. You can also provide a `page ` argument to "
+                    "jump to a page immediately. Useful for sharing help "
+                    "command pages.\n"
                     ""
                     "Running /help and /commands both bring up this embed.",
         fields=[
             (
                 "Parameters",
-                "`page`: The page to immediately jump to. Useful for sharing commands with other users.\n"
+                "`page`: The page to immediately jump to. Useful for sharing "
+                "commands with other users.\n"
                 "- (optional) Any number."
             ),
             (
                 "Examples",
                 "- %%help%%\n"
-                "  - The simplest way to learn Rina's commands. Brings you to the first page.\n"
+                "  - The simplest way to learn Rina's commands. Brings you "
+                "to the first page.\n"
                 "- %%help%% `page:1`\n"
                 "  - brings up the help page for this /help command.\n"
                 "- %%commands%% `page:3`\n"
-                "  - An alias for this command. Brings you to the Bot Functions index page."
+                "  - An alias for this command. Brings you to the "
+                "Bot Functions index page."
             )
         ],
     ),
     102: HelpPage(  # /version
         title="Bot version",
-        description="You can run %%version%% at any time. It will show you the bot's current "
-                    "version and when it was started up. The message will only be visible for "
-                    "you, unless you are a staff member.\n"
-                    "This might someties also show that there is a new version available. Rina "
-                    "will automatically update to it when she restarts.",
+        description="You can run %%version%% at any time. It will show you "
+                    "the bot's current version and when it was started up. "
+                    "The message will only be visible for you, unless you "
+                    "are a staff member.\n"
+                    "This might someties also show that there is a new "
+                    "version available. Rina will automatically update to "
+                    "it when she restarts.",
         fields=[
             (
                 "Examples",
                 "- %%version%%\n"
-                "  - Gives Rina's current version, and if there is a newer version."
+                "  - Gives Rina's current version, and if there is a "
+                "newer version."
             )
         ],
     ),
     103: HelpPage(  # get_rina_command_mention
         title="Sharing Rina's commands the cooler way :sunglasses:",
-        description="Rina has a lot of commands, all in a big list. Other bots may also have "
-                    "commands that are put in this same list.. Finding the right command "
-                    "between all those /help commands with the same name can be quite "
+        description="Rina has a lot of commands, all in a big list. Other "
+                    "bots may also have commands that are put in this same "
+                    "list.. Finding the right command between all those "
+                    "/help commands with the same name can be quite "
                     "difficult.\n"
                     "\n"
-                    "For that reason, this cool command lets you do things like -> %%help%% <- this "
-                    "for Rina!\n"
+                    "For that reason, this cool command lets you do things "
+                    "like -> %%help%% <- this for Rina!\n"
                     "\n"
-                    "On a technical level, all commands have a name and unique ID, like "
-                    "so: `%%help%%`. Other bot commands with the same name have a different "
-                    "ID. Mismatched IDs will autofill incorrect on Android, and try to run "
-                    "a nonexisting command:  (``).",
+                    "On a technical level, all commands have a name and "
+                    "unique ID, like so: `%%help%%`. Other bot commands "
+                    "with the same name have a different ID.",
         fields=[
             (
                 "Parameters",
-                "`command`: The command you want to convert into a cool mention.\n"
+                "`command`: The command you want to convert into a cool "
+                "mention.\n"
                 "- The name of one of Rina's commands.\n"
-                "- If it's an invalid command, rina will still give a response but it won't be in a mention format.\n"
+                "- If it's an invalid command, rina will still give a "
+                "response but it won't be in a mention format.\n"
             ),
             (
                 "Examples",
                 "- %%get_rina_command_mention%% `command:/version`\n"
-                "  - Gives you the input, a mention preview, and a copyable mention.\n"
+                "  - Gives you the input, a mention preview, and a "
+                "copyable mention.\n"
                 "- %%get_rina_command_mention%% `command:add_poll_reactions`\n"
-                "  - Gives you the command mention information for /add_poll_reactions.\n"
+                "  - Gives you the command mention information for "
+                "/add_poll_reactions.\n"
             )
         ],
     ),
     104: HelpPage(  # /add_poll_reactions
         title="Adding voting emojis / Creating tiny polls",
-        description="Sometimes you just want to see everyone's opinion on something, so you "
-                    "add a thumbs up and thumbs down emoji to your message. But what if you "
-                    "want to cast a vote yourself too? Rina's got you covered.\n"
+        description="Sometimes you just want to see everyone's opinion on "
+                    "something, so you add a thumbs up and thumbs down emoji "
+                    "to your message. But what if you want to cast a vote "
+                    "yourself too? Rina's got you covered.\n"
                     "\n"
-                    "This command lets you add between 2 and 3 emojis, allowing you to get "
-                    "positive, negative, and neutral reactions. You can also run the command "
-                    "again for even more emojis! Reuse a previous emoji if you only want to "
+                    "This command lets you add between 2 and 3 emojis, "
+                    "allowing you to get positive, negative, and neutral "
+                    "reactions. You can also run the command again for even "
+                    "more emojis! Reuse a previous emoji if you only want to "
                     "add 1 new one.",
         fields=[
             (
                 "Parameters",
-                # /add_poll_reactions message_id: upvote_emoji: downvote_emoji: neutral_emoji:
                 "`message_id`: The message to add emojis to.\n"
-                "- A message ID. Right click a message and click \"Copy Message ID\". The command must be run in the "
-                "same channel as this message.\n"
+                "- A message ID. Right click a message and click "
+                "\"Copy Message ID\". The command must be run in the same "
+                "channel as this message.\n"
                 "`upvote_emoji`: The first emoji to add to the message.\n"
                 "- An emoji or emoji ID.\n"
                 "`downvote_emoji`: The last emoji to add to the message.\n"
                 "- An emoji or emoji ID.\n"
-                "`neutral_emoji`: A middle emoji to add to the message. Will be added after the upvote but before "
-                "the downvote emoji.\n"
+                "`neutral_emoji`: A middle emoji to add to the message. Will "
+                "be added after the upvote but before the downvote emoji.\n"
                 "- (optional) An emoji or emoji ID.\n"
             ),
             (
-                "Examples",  # discord emojis don't work in code blocks :(, so gotta use Unicode.
-                "- %%add_poll_reactions%% `message_id:1963131994116722778` `upvote_emoji:🐟` `downvote_emoji:🐢`\n"
+                # discord emojis don't work in code blocks :(, so
+                #  I gotta use Unicode.
+                "Examples",
+                "- %%add_poll_reactions%% `message_id:1963131994116722778` "
+                "`upvote_emoji:🐟` `downvote_emoji:🐢`\n"
                 "  - Adds a fish and then a turtle to a message with the id.\n"
                 "- %%add_poll_reactions%% `message_id:1134140122115838003` "
                 "`upvote_emoji:👍` `downvote_emoji:👎` `neutral_emoji:🤷`\n"
-                "  - Adds a thumbs up, then a person shrugging, then a thumbs down emoji."
+                "  - Adds a thumbs up, then a person shrugging, then a "
+                "thumbs down emoji."
             )
         ],
     ),
@@ -297,10 +344,11 @@
     111: HelpPage(  # dice rolls
         title="Rolling dice",  # /roll
         description="Have you ever wanted to roll virtual dice? Now you can!\n"
-                    "Rina lets you roll dice with many kinds of faces. Rolling a 2-faced die (aka, a coin)? sure!\n"
+                    "Rina lets you roll dice with many kinds of faces. "
+                    "Rolling a 2-faced die (aka, a coin)? sure!\n"
                     "\n"
-                    "Because this command also allows an __advanced input__, go to the next page for "
-                    "more info: __**102**__\n",
+                    "Because this command also allows an __advanced input__, "
+                    "go to the next page for more info: __**102**__\n",
         fields=[
             (
                 "Parameters",
@@ -308,97 +356,125 @@
                 "- Any number between 1 and 999999.\n"
                 "`faces`: How many sides does every die have?\n"
                 "- Any number between 1 and 999999.\n"
-                "`mod`: Do you want to add a modifier? (eg. add 2 after rolling the dice)\n"
+                "`mod`: Do you want to add a modifier? (eg. add 2 after "
+                "rolling the dice)\n"
                 "- (optional) Any (negative) (decimal) number.\n"
                 "`advanced`: Roll more advanced! example: 1d20+3*2d4\n"
-                "- (optional) String of your advanced dice roll. See page __**102**__ for more info."
+                "- (optional) String of your advanced dice roll. See page "
+                "__**102**__ for more info."
             ),
             (
                 "Examples",
                 "- %%roll%% `dice:3` `faces:6`\n"
-                "  - Rolls 3 dice with 6 faces (3d6), aka rolling 3 normal dice.\n"
+                "  - Rolls 3 dice with 6 faces (3d6), aka rolling 3 "
+                "normal dice.\n"
                 "- %%roll%% `dice:6` `faces:2`\n"
                 "  - Rolls 6 dice with 2 faces (6d2), aka flipping 6 coins.\n"
                 "- %%roll%% `dice:3` `faces:2` `mod:4`\n"
                 "  - Flip 3 coins, then add '4' to the final outcome.\n"
-                "  - eg. 1 + 2 + 2 = 5 (3 dice with 2 faces), then add 4 to the final result = 5 + 4 = 9.\n"
+                "  - eg. 1 + 2 + 2 = 5 (3 dice with 2 faces), then add 4 to "
+                "the final result = 5 + 4 = 9.\n"
                 "- %%roll%% `dice:1` `faces:1` `advanced:help`\n"
-                "  - See next page (__**102**__) for more information about advanced dice rolls."
+                "  - See next page (__**102**__) for more information about "
+                "advanced dice rolls."
             )
         ],
     ),
     112: HelpPage(  # dice rolls advanced
         title="Advanced dice rolls",  # /roll advanced: ...
-        description="In the case that the simple dice roll command is not enough, you can roll complexer combinations "
-                    "of dice rolls using the advanced option.\n"
-                    "The advanced mode lets you use addition, subtraction, multiplication, and custom dice.\n"
-                    "To use the advanced function, you must give a value to the required parameters first ('dice' and "
-                    "'faces') (the inserted value is ignored).\n"
-                    "Supported characters are: `0123456789d+*-`. Spaces are ignored. Multiplication goes before "
+        description="In the case that the simple dice roll command is not "
+                    "enough, you can roll complexer combinations of dice "
+                    "rolls using the advanced option.\n"
+                    "The advanced mode lets you use addition, subtraction, "
+                    "multiplication, and custom dice.\n"
+                    "To use the advanced function, you must give a value to "
+                    "the required parameters first ('dice' and 'faces') (the "
+                    "inserted value is ignored).\n"
+                    "Supported characters are: `0123456789d+*-`. Spaces are "
+                    "ignored. Multiplication goes before "
                     "addition/subtraction.",
         fields=[
             (
                 "Examples",
                 "- %%roll%% `dice:1` `faces:1` `advanced:3d2 + 1`\n"
-                "  - Flip 3 coins (dice with 2 faces), then add 1 to the total.\n"
+                "  - Flip 3 coins (dice with 2 faces), then add 1 to "
+                "the total.\n"
                 "- %%roll%% `dice:9999` `faces:9999` `advanced:2d20*2`\n"
-                "  - Roll two 20-sided dice, add their eyes, and then multiply the total by 2.\n"
+                "  - Roll two 20-sided dice, add their eyes, and then "
+                "multiply the total by 2.\n"
                 "- %%roll%% `dice:1` `faces:1` `advanced:2*1d20+2*1d20`\n"
                 "  - Same as the above.\n"
                 "- %%roll%% `dice:1` `faces:1` `advanced:5d6*2d6-3d6`\n"
-                "  - Multiply the outcome of 5 dice with the outcome of 2 dice, and subtract the outcome of 3 dice."
+                "  - Multiply the outcome of 5 dice with the outcome of 2 "
+                "dice, and subtract the outcome of 3 dice."
             )
         ],
     ),
     113: HelpPage(  # reminders
         title="Reminders",  # /reminder
-        description="With this feature, you'll never forget your tasks anymore! (so long as Rina is online...).\n"
-                    "This command lets you create, remove, and view your reminders.",
+        description="With this feature, you'll never forget your tasks "
+                    "anymore! (so long as Rina is online...).\n"
+                    "This command lets you create, remove, and view your "
+                    "reminders.",
         fields=[
             (
                 "reminder remindme",
                 "- %%reminder remindme%% `time: ` `reminder: `\n"
                 "`time`: When do you want to be reminded?\n"
-                "- Use a format like 1mo10d9h8m7s for years/months/weeks/days/hours/min/sec.\n"
+                "- Use a format like 1mo10d9h8m7s for years/months/weeks/days/"
+                "hours/min/sec.\n"
                 # TODO: clarify; copy paste ValueError help message?
-                "- Use a format like 2026-12-01 or 2026-12-01T15:43:23 for a reminder in December 2026.\n"
-                "- Use a format like  or just 01234567 to use a Unix timestamp.\n"
+                "- Use a format like 2026-12-01 or 2026-12-01T15:43:23 for a "
+                "reminder in December 2026.\n"
+                "- Use a format like  or just 01234567 to use a "
+                "Unix timestamp.\n"
                 "`reminder`: What would you like to be reminded of?"
             ),
             (
                 "reminder reminders",
                 "- %%reminder reminders%% [`item: `]\n"
-                "  - Show a list of active reminders. They each have a number. Use `item:number` to view more "
-                "information about a specific reminder."
+                "  - Show a list of active reminders. They each have a "
+                "number. Use `item:number` to view more information about a "
+                "specific reminder."
             ),
             (
                 "reminder remove",
                 "- %%reminder remove%% `item: `\n"
-                "  - Remove a reminder. Use %%reminder reminders%% to see a list of reminders. Each has a number. "
-                "Use that number to remove the reminder like so: `item:number`."
+                "  - Remove a reminder. Use %%reminder reminders%% to see a "
+                "list of reminders. Each has a number. Use that number to "
+                "remove the reminder like so: `item:number`."
             )
         ],
     ),
     114: HelpPage(  # convert unit
         title="Converting units",
-        description="Since international communities often share stories about temperature and speed, it's not "
-                    "uncommon to have to look up how many feet go in a centimeter. This module lets you easily "
-                    "converty a large amount of units in multiple categories! Currencies are fetched live using "
+        description="Since international communities often share stories "
+                    "about temperature and speed, it's not uncommon to have "
+                    "to look up how many feet go in a centimeter. This module "
+                    "lets you easily converty a large amount of units in "
+                    "multiple categories! Currencies are fetched live using "
                     "an online website (api)!\n"
-                    "The units (`from` and `to_unit`) will autocomplete depending on the selected category.",
+                    "The units (`from` and `to_unit`) will autocomplete "
+                    "depending on the selected category.",
         fields=[
             (
                 "Parameters",
-                "`mode`: What type of unit do you want to compare? length/time/currency/...\n"
-                "`from`: What unit are you trying to convert? (eg. Celsius / Fahrenheit)\n"
-                "`value`: How much of this unit are you converting? (think __20__ degrees Celsius)\n"
-                "`to_unit`: What unit to convert to? *C -> *F : fill in Fahrenheit.\n"
-                "`public`: (optional) Do you want to show this to everyone in chat?\n"
+                "`mode`: What type of unit do you want to compare? "
+                "length/time/currency/...\n"
+                "`from`: What unit are you trying to convert? (eg. Celsius / "
+                "Fahrenheit)\n"
+                "`value`: How much of this unit are you converting? (think "
+                "__20__ degrees Celsius)\n"
+                "`to_unit`: What unit to convert to? *C -> *F : fill "
+                "in Fahrenheit.\n"
+                "`public`: (optional) Do you want to show this to everyone "
+                "in chat?\n"
                 "- Default: False"
             ),
             (
                 "Examples",
-                "- %%convert_unit%% `mode:Length (miles,km,inch)` `from:meter` `value:80` `to_unit:yard`\n"
+                "- %%convert_unit%% `mode:Length (miles,km,inch)` "
+                "`from:meter` `value:80` `to_unit:yard`\n"
                 "  - Convert 80 meters to yards = about 87.5 yd\n"
                 ""
             )
@@ -406,34 +482,43 @@
     ),
     115: HelpPage(  # to-do list
         title="Todo lists",
-        description="For the forgetful, or if you just want a nice grocery list, this command is for you! You can "
-                    "easily add and remove to-do notes, and they're saved throughout all servers with me "
-                    "(TransPlace, EnbyPlace, Transonance, etc.)",
+        description="For the forgetful, or if you just want a nice grocery "
+                    "list, this command is for you! You can easily add and "
+                    "remove to-do notes, and they're saved throughout all "
+                    "servers with me (TransPlace, EnbyPlace, Transonance, "
+                    "etc.)",
         fields=[
             (
                 "Parameters",
-                "`mode`: Do you want to __Add__, __Remove__, or __Check__ (view) your to-do list?\n"
-                "- if you want to add to / remove from your to-do list, you must also give the `todo` value below.\n"
+                "`mode`: Do you want to __Add__, __Remove__, or __Check__ "
+                "(view) your to-do list?\n"
+                "- if you want to add to / remove from your to-do list, you "
+                "must also give the `todo` value below.\n"
                 "`todo`:\n"
                 "- __Add__: What to-do would you like to add?\n"
-                "- __Remove__: What to-do item would you like to remove from your list? Give the number of the to-do "
-                "list as it's shown with %%todo%% `mode:Check`"
+                "- __Remove__: What to-do item would you like to remove from "
+                "your list? Give the number of the to-do list as it's shown "
+                "with %%todo%% `mode:Check`"
             ),
             (
                 "Examples",
-                "- %%todo%% `mode:Add something to your to-do list` `todo:Create a nice help command`\n"
+                "- %%todo%% `mode:Add something to your to-do list` "
+                "`todo:Create a nice help command`\n"
                 "  - Add \"Create a nice help command\"\n"
                 "- %%todo%% `mode:Check`\n"
-                "  - Your to-do list would look like this. Note how each item starts with a number. Use this number "
-                "to remove them.\n"
+                "  - Your to-do list would look like this. Note how each "
+                "item starts with a number. Use this number to remove them.\n"
                 "    - Found 4 to-do items:\n"
                 "    - `0`: Create a nice help command\n"
                 "    - `1`: Share cool command with everyone\n"
                 "- %%todo%% `mode:Remove to-do` `todo:0`\n"
-                "  - Remove to-do item number 0 from your list. Use %%todo%% `mode:Check` to see what number to use "
-                "to remove the right item.\n"
-                "  - Keep in mind that the order will shift after you've removed an item, so redo the `check` command "
-                "to make sure you're removing the right command when removing multiple to-do items at once!"
+                "  - Remove to-do item number 0 from your list. Use %%todo%% "
+                "`mode:Check` to see what number to use to remove the "
+                "right item.\n"
+                "  - Keep in mind that the order will shift after you've "
+                "removed an item, so redo the `check` command to make sure "
+                "you're removing the right command when removing multiple "
+                "to-do items at once!"
             )
         ],
     ),
@@ -441,50 +526,61 @@
     # region Suggestion commands
     121: HelpPage(  # developer_request
         title="Send suggestions for bot developers!",
-        description="If you ever have any suggestions or ideas for any bots, feel free "
-                    "to use this command. This can include:\n"
+        description="If you ever have any suggestions or ideas for any bots, "
+                    "feel free to use this command. This can include:\n"
                     "- Bigger features like starboard,\n"
                     "- Smaller features like a %%add_poll_reactions%%,\n"
                     "- Silly features like %%compliment%%,\n"
                     "- A spelling mistake of 1 mistyped character.\n"
-                    "It doesn't have to be much :) so long as you explain your ideas "
-                    "(or you would have to DM a staff member to give extra explanation).",
+                    "It doesn't have to be much :) so long as you explain "
+                    "your ideas (or you would have to DM a staff member to "
+                    "give extra explanation).",
         fields=[
             (
                 "Parameters",
                 "`suggestion`: The idea you would like to share.\n"
-                "- A message between 20 and 1500 characters. Try to be detailed so we can figure "
-                "out what you want without having to ask for more details :)\n"
+                "- A message between 20 and 1500 characters. Try to be "
+                "detailed so we can figure out what you want without having "
+                "to ask for more details :)\n"
             ),
             (
                 "Examples (real)",
-                "- %%developer_request%% `suggestion:idea for rina: make tags just post the message directly "
-                "into the channel instead of as a command follow-up so it doesn't show the \"message could "
-                "not be loaded\" error :3`\n"
-                "  - Sends a message to a developer channel on the staff server.\n"
-                "- %%developer_request%% `suggestion:For Amari, make voice channels not give xp if someone is "
-                "alone in a voice channel, to prevent people camping in vc alone (or falling asleep in vc) for xp`\n"
+                "- %%developer_request%% `suggestion:idea for rina: make tags "
+                "just post the message directly into the channel instead of "
+                "as a command follow-up so it doesn't show the \"message "
+                "could not be loaded\" error :3`\n"
+                "  - Sends a message to a developer channel on the "
+                "staff server.\n"
+                "- %%developer_request%% `suggestion:For Amari, make voice "
+                "channels not give xp if someone is alone in a voice "
+                "channel, to prevent people camping in vc alone (or "
+                "falling asleep in vc) for xp`\n"
                 "  - yeah, it does the thingy"
             )
         ],
     ),
     122: HelpPage(  # qotw
         title="Questions of the week",
-        description="You might have seen a Question of the Week pop up roughly once a week, \n"
-                    "If you have any cool questions you want to ask to the server, feel free to ask them!\n"
+        description="You might have seen a Question of the Week pop up "
+                    "roughly once a week, \n"
+                    "If you have any cool questions you want to ask to the "
+                    "server, feel free to ask them!\n"
                     "\n"
-                    "Questions will be sent in a channel on the staff server, where we can vote, discuss, "
-                    "or give suggestions. To give your questions a better chance of being picked, make "
+                    "Questions will be sent in a channel on the staff server, "
+                    "where we can vote, discuss, or give suggestions. To give "
+                    "your questions a better chance of being picked, make "
                     "sure they can be answered by everyone.",
         fields=[
             (
                 "Parameters",
                 "`question`: The question you want to suggest.\n"
-                " - A question of max 400 characters to propose to the staff server.\n"
+                " - A question of max 400 characters to propose to the "
+                "staff server.\n"
             ),
             (
                 "Examples",
-                "- %%qotw%% `question:do you have a favourite flower or plant?`\n"
+                "- %%qotw%% `question:do you have a favourite flower "
+                "or plant?`\n"
                 "  - Sends your question to the staff team to vote :D\n"
                 "- %%qotw%% `question:Should cleo make more qotws?`\n"
                 "  - yes"
@@ -495,18 +591,22 @@
     # region Internet search commands
     131: HelpPage(  # equaldex
         title="Get LGBTQ laws from countries!",
-        description="There's many countries in the world, and each has different "
-                    "laws, of course. One website put these laws together and made "
-                    "a nice overview for it :D. This command retrieves and shows you "
-                    "some of their data.",
+        description="There's many countries in the world, and each has "
+                    "different laws, of course. One website put these laws "
+                    "together and made a nice overview for it :D. This "
+                    "command retrieves and shows you some of their data.",
         fields=[
             (
                 "Parameters",
                 "`country_id`: The ID of the country you want to look up.\n"
-                "- The short ID form of a country. Some examples: America: US, Germany: DE, Canada: CA\n"
-                "- For more information, see [Wikipedia/ISO_3166#Officially_assigned_code_elements]"
-                "(https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements)\n"
-                "- It appears lowercase country codes don't work :p. So use uppercase: \"de\" vs \"DE\"."
+                "- The short ID form of a country. Some examples: "
+                "America: US, Germany: DE, Canada: CA\n"
+                "- For more information, see "
+                "[Wikipedia/ISO_3166#Officially_assigned_code_elements]"
+                "(https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2"
+                "#Officially_assigned_code_elements)\n"
+                "- It appears lowercase country codes don't work :p. So use "
+                "uppercase: \"de\" vs \"DE\"."
             ),
             (
                 "Examples",
@@ -639,45 +739,60 @@
     # region Bot setup
     900: HelpPage(  # /settings
         title="Server settings",
-        description="Rina has many commands and features. Some commands need setup beforehand, like "
-                    "a logging channel or role ids. This command lets you personalize rina for *your* "
+        description="Rina has many commands and features. Some commands "
+                    "need setup beforehand, like a logging channel or role "
+                    "ids. This command lets you personalize rina for *your* "
                     "server!",
         fields=[
             (
                 "Parameters",
                 "`type`: The type of setting you want to change.\n"
-                "- Attribute: IDs like staff role ids, starboard channel ids, blacklisted channels, vctable prefixes\n"
+                "- Attribute: IDs like staff role ids, starboard channel "
+                "ids, blacklisted channels, vctable prefixes\n"
                 "- Module: Choose which modules are currently running.\n"
                 "`setting`: The attribute or module you want to change.\n"
-                "- This will be autocompleted based on what you selected for `type`.\n"
+                "- This will be autocompleted based on what you selected "
+                "for `type`.\n"
                 "`mode`: How do you want to change your selected setting?\n"
-                "- View: Don't change anything, just view the current value for the selected setting.\n"
+                "- View: Don't change anything, just view the current value "
+                "for the selected setting.\n"
                 "- [Module] Enable: Turn on a module.\n"
                 "- [Module] Disable: Turn off a module.\n"
                 "- [Attribute] Set: Set a value for the selected setting.\n"
-                "- [Attribute] Delete: Unset a value for the selected setting.\n"
-                "- [Attribute] Add: Used for lists. Add a value to the list for the selected setting.\n"
-                "- [Attribute] Remove: Used for lists. Remove a value from the list for the selected setting.\n"
+                "- [Attribute] Delete: Unset a value for the selected "
+                "setting.\n"
+                "- [Attribute] Add: Used for lists. Add a value to the list "
+                "for the selected setting.\n"
+                "- [Attribute] Remove: Used for lists. Remove a value from "
+                "the list for the selected setting.\n"
             ),
             (
                 "Parameters (continued)",
                 "`value`: The value you want to give this setting.\n"
-                "- You don't have to set a value if you selected `mode:View`.\n"
-                "- With `mode:Remove` (for lists), you should fill in the value that is currently in the list"
-                "(not the index of the item in the list).\n"
+                "- You don't have to set a value if you selected "
+                "`mode:View`.\n"
+                "- With `mode:Remove` (for lists), you should fill in the "
+                "value that is currently in the list (not the index of the "
+                "item in the list).\n"
             ),
             (
                 "Examples",
-                "- %%settings%% `type:Module` `setting:starboard` `mode:Enable`\n"
+                "- %%settings%% `type:Module` `setting:starboard` "
+                "`mode:Enable`\n"
                 "  - Enable the 'starboard' module.\n"
-                "- %%settings%% `type:Attribute` `setting:log_channel` `mode:Set` `value:123456789012345678`\n"
-                "  - Set the 'log_channel' attribute to '123456789012345678', the channel id.\n"
-                "- %%settings%% `type:Attribute` `setting:poll_reaction_blacklisted_channels` "
+                "- %%settings%% `type:Attribute` `setting:log_channel` "
+                "`mode:Set` `value:123456789012345678`\n"
+                "  - Set the 'log_channel' attribute to '123456789012345678', "
+                "the channel id.\n"
+                "- %%settings%% `type:Attribute` "
+                "`setting:poll_reaction_blacklisted_channels` "
                 "`mode:Add` `value:general`\n"
-                "  - poll_reaction_blacklisted_channels is a list of channels: [1,2,3]. "
+                "  - poll_reaction_blacklisted_channels is a list of "
+                "channels: [1,2,3]. "
                 "Add the `general` channel to this list.\n"
-                "  - When clicking autocomplete results, the value may be the name of the channel. You can use this "
-                "method, or fill in the ID manually and ignore the autocomplete result.\n"
+                "  - When clicking autocomplete results, the value may be "
+                "the name of the channel. You can use this method, or fill "
+                "in the ID manually and ignore the autocomplete result.\n"
             )
         ],
         staff_only=True
@@ -688,7 +803,8 @@
                     "information is repeated very often, and being able to "
                     "copy-paste that information would be very practical. "
                     "This command lets you add these custom tags.\n"
-                    "For more info, look at page __**0**__.",  # todo: add /tag helppage
+                    "For more info, look at page __**0**__.",
+        # todo: add /tag helppage
         fields=[
             (
                 "Parameters",
@@ -702,11 +818,11 @@
             (
                 "Examples",
                 "- %%tag-manage%% `mode:Create` `tag_name:avoid politics`\n"
-                "  - Opens a modal to fill in details about this tag. See below "
-                "for information about the modal.\n"
+                "  - Opens a modal to fill in details about this tag. See "
+                "below for information about the modal.\n"
                 "- %%tag-manage%% `mode:Delete` `tag_name:avoid politics`\n"
-                "  - Deletes the tag we just created. You can only delete custom "
-                "tags.\n"
+                "  - Deletes the tag we just created. You can only delete "
+                "custom tags.\n"
             ),
             (
                 "Tag Creation Modal",
diff --git a/extensions/help/utils.py b/extensions/help/utils.py
index 3fb3005..dd20c66 100644
--- a/extensions/help/utils.py
+++ b/extensions/help/utils.py
@@ -6,41 +6,63 @@
 from extensions.help.helppage import HelpPage
 
 
-def generate_help_page_embed(page: HelpPage, page_number: int, client: Bot) -> discord.Embed:
+def generate_help_page_embed(
+        page: HelpPage,
+        page_number: int,
+        client: Bot
+) -> discord.Embed:
     """
-    Helper function to generate an embed for a specific help page. This command is mainly to
-    prevent inconsistencies between the /help calling and updating functions.
-    Page fields are appended after the description, in the order they are given in the list.
+    Helper function to generate an embed for a specific help page. This
+    command is mainly to prevent inconsistencies between the /help
+    calling and updating functions. Page fields are appended after the
+    description, in the order they are given in the list.
 
     :param page: The help page to reference.
-    :param page_number: The page number of the help page. This number is added as footer, but is also used for the
-         hue (HSV) value of the embed color.
+    :param page_number: The page number of the help page. This number is
+     added as footer, but is also used for the hue (HSV) value of the
+     embed color.
     :param client: The bot instance for :py:meth:`Client.get_command_mention`.
 
     :return: An embed of the given help page.
     """
-    embed = discord.Embed(color=discord.Color.from_hsv((180 + page_number * 10) / 360, 0.4, 1),
-                          title=page["title"],
-                          description=replace_string_command_mentions(page["description"], client))
+    embed = discord.Embed(
+        color=discord.Color.from_hsv(
+            h=(180 + page_number * 10) / 360,
+            s=0.4,
+            v=1
+        ),
+        title=page["title"],
+        description=replace_string_command_mentions(
+            page["description"],
+            client
+        ),
+    )
     if "fields" in page:
         for field in page["fields"]:
-            embed.add_field(name=replace_string_command_mentions(field[0], client),
-                            value=replace_string_command_mentions(field[1], client),
-                            inline=False)
+            embed.add_field(
+                name=replace_string_command_mentions(field[0], client),
+                value=replace_string_command_mentions(field[1], client),
+                inline=False
+            )
     embed.set_footer(text="page: " + str(page_number))
     return embed
 
 
-def get_nearest_help_pages_from_page(page: int, pages: list[int]) -> tuple[int, int]:
+def get_nearest_help_pages_from_page(
+        page: int,
+        pages: list[int]
+) -> tuple[int, int]:
     """
     Get the two nearest page numbers to a given page number.
 
-    :param page: The page number to get the two nearest page numbers for.
+    :param page: The page number to get the two nearest page
+     numbers for.
     :param pages: A (sorted) list of page numbers to reference (e.g.
      `[1, 2, 3, 4, 5, 6, 10, 11, 12, 21, 22, 23, 31, 32, 101, 111, 112]`)
 
-    :return: The two nearest page numbers. The first is found by looking before, the second looks ahead.
-     If looping around, [0] may be greater than [1].
+    :return: The two nearest page numbers. The first is found by looking
+     before, the second looks ahead. If looping around, [0] may
+     greater than [1].
     """
     if page > pages[-1]:
         return pages[-1], pages[0]
@@ -53,7 +75,9 @@ def get_nearest_help_pages_from_page(page: int, pages: list[int]) -> tuple[int,
             min_index -= 1
         while max_index not in pages:
             max_index += 1
-        assert min_index != max_index, (f"'min_index' ({min_index}) and 'max_index' ({max_index}) should point to "
-                                        f"the two nearest help pages to 'page' ({page}), being the same implies "
-                                        f"the page was in-range in the first place.")
+        assert min_index != max_index, (
+            f"'min_index' ({min_index}) and 'max_index' ({max_index}) should "
+            f"point to the two nearest help pages to 'page' ({page}), being "
+            f"the same implies the page was in-range in the first place."
+        )
         return min_index, max_index
diff --git a/extensions/help/views/helppage.py b/extensions/help/views/helppage.py
index 1896a60..eb1bef0 100644
--- a/extensions/help/views/helppage.py
+++ b/extensions/help/views/helppage.py
@@ -6,7 +6,10 @@
 from resources.utils.stringhelper import replace_string_command_mentions
 from resources.views.generics import PageView
 
-from extensions.help.utils import generate_help_page_embed, get_nearest_help_pages_from_page
+from extensions.help.utils import (
+    generate_help_page_embed,
+    get_nearest_help_pages_from_page
+)
 from extensions.help.helppage import HelpPage
 
 
@@ -15,7 +18,8 @@ async def update_page(
             self, itx: discord.Interaction[Bot], view: PageView
     ) -> None:
         page_key = list(self.pages)[self.page]
-        embed = generate_help_page_embed(self.pages[page_key], page_key, itx.client)
+        embed = generate_help_page_embed(
+            self.pages[page_key], page_key, itx.client)
         await itx.response.edit_message(
             embed=embed,
             view=view
@@ -23,13 +27,21 @@ async def update_page(
 
     # region buttons
     @discord.ui.button(emoji="📋", style=discord.ButtonStyle.gray)
-    async def go_to_index(self, itx: discord.Interaction[Bot], _: discord.ui.Button):
+    async def go_to_index(
+            self,
+            itx: discord.Interaction[Bot],
+            _: discord.ui.Button
+    ):
         self.page = 2
         self.update_button_colors()
         await self.update_page(itx, self)
 
     @discord.ui.button(emoji="🔢", style=discord.ButtonStyle.gray)
-    async def jump_to_page(self, itx: discord.Interaction[Bot], _: discord.ui.Button):
+    async def jump_to_page(
+            self,
+            itx: discord.Interaction[Bot],
+            _: discord.ui.Button
+    ):
         help_page_indexes = list(self.pages)
         jump_page_modal = SingleLineModal(
             "Jump to a help page",
@@ -49,27 +61,40 @@ async def jump_to_page(self, itx: discord.Interaction[Bot], _: discord.ui.Button
             await jump_page_modal.itx.response.send_message(
                 "Error: Invalid number.\n"
                 "\n"
-                "This button lets you jump to a help page (number). To see what kinds of help "
-                "pages there are, go to the index page (page 2, or click the 📋 button).\n"
-                "An example of a help page is page 3: `Utility`. To go to this page, you can "
-                "either use the previous/next buttons (◀️ and ▶️) to navigate there, or click "
-                "the 🔢 button: This button opens a modal.\n"
-                "In this modal, you can put in the page number you want to jump to. Following "
-                "from our example, if you type in '3', it will bring you to page 3; `Utility`.\n"
-                "Happy browsing!", ephemeral=True)
+                "This button lets you jump to a help page (number). To see "
+                "what kinds of help pages there are, go to the index page "
+                "(page 2, or click the 📋 button).\n"
+                "An example of a help page is page 3: `Utility`. To go to "
+                "this page, you can either use the previous/next buttons "
+                "(◀️ and ▶️) to navigate there, or click the 🔢 button: "
+                "This button opens a modal.\n"
+                "In this modal, you can put in the page number you want to "
+                "jump to. Following from our example, if you type in '3', "
+                "it will bring you to page 3; `Utility`.\n"
+                "Happy browsing!",
+                ephemeral=True
+            )
             return
 
         if page_guess not in help_page_indexes:
-            min_index, max_index = get_nearest_help_pages_from_page(page_guess, help_page_indexes)
-            relative_page_location_details = f" (nearest pages are `{min_index}` and `{max_index}`)."
+            min_index, max_index = get_nearest_help_pages_from_page(
+                page_guess, help_page_indexes)
+            relative_page_location_details \
+                = f" (nearest pages are `{min_index}` and `{max_index}`)."
+            output = replace_string_command_mentions(
+                (  # easter egg
+                    f"This page (`{page_guess}`) does not exist! "
+                    if page_guess != 404 else
+                    "`404`: Page not found!"
+                ) + f" {relative_page_location_details} "
+                    f"Try %%help%% `page:1` or use the page keys to get "
+                    f"to the right page number!",
+                itx.client
+            ),
             await jump_page_modal.itx.response.send_message(
-                replace_string_command_mentions(
-                    (f"This page (`{page_guess}`) does not exist! "
-                     if page_guess != 404 else "`404`: Page not found!") +  # easter egg
-                    f" {relative_page_location_details} Try %%help%% `page:1` or use the page keys "
-                    f"to get to the right page number!",
-                    itx.client),
-                ephemeral=True)
+                output,
+                ephemeral=True
+            )
             return
 
         self.page = list(self.pages).index(page_guess)
@@ -78,7 +103,12 @@ async def jump_to_page(self, itx: discord.Interaction[Bot], _: discord.ui.Button
 
     # endregion buttons
 
-    def __init__(self, first_page_key: int, page_dict: dict[int, HelpPage], can_view_staff_pages: bool) -> None:
+    def __init__(
+            self,
+            first_page_key: int,
+            page_dict: dict[int, HelpPage],
+            can_view_staff_pages: bool
+    ) -> None:
         if not can_view_staff_pages:
             page_numbers = list(page_dict)
             for x in range(len(page_numbers)):
@@ -87,5 +117,10 @@ def __init__(self, first_page_key: int, page_dict: dict[int, HelpPage], can_view
 
         self.pages = page_dict
         first_page_index = list(self.pages).index(first_page_key)
-        super().__init__(first_page_index, len(self.pages) - 1, self.update_page)
-        self._children.append(self._children.pop(1))  # move jump_to_page button to the end of the view
+        super().__init__(
+            first_page_index,
+            len(self.pages) - 1,
+            self.update_page  # todo: remove redundant self.update_page arg
+        )
+        # move jump_to_page button to the end of the view
+        self._children.append(self._children.pop(1))
diff --git a/extensions/message_reactions/cogs/banappealreaction.py b/extensions/message_reactions/cogs/banappealreaction.py
index b2797ec..a850404 100644
--- a/extensions/message_reactions/cogs/banappealreaction.py
+++ b/extensions/message_reactions/cogs/banappealreaction.py
@@ -15,8 +15,10 @@ async def on_message(self, message: discord.Message):
         if not self.client.is_module_enabled(
                 message.guild, ModuleKeys.ban_appeal_reactions):
             return
-        ban_appeal_webhook_id: discord.User | None = self.client.get_guild_attribute(
-            message.guild, AttributeKeys.ban_appeal_webhook_id)
+        ban_appeal_webhook_id: discord.User | None = \
+            self.client.get_guild_attribute(
+                message.guild, AttributeKeys.ban_appeal_webhook_id
+            )
         if ban_appeal_webhook_id is None:
             raise MissingAttributesCheckFailure(
                 ModuleKeys.ban_appeal_reactions,
@@ -64,16 +66,20 @@ async def on_message(self, message: discord.Message):
 
         username: str = field_value
         try:
-            thread = await message.create_thread(name=f"App-{platform[0]}-{username[:80]}",
-                                                 auto_archive_duration=10080)
+            thread = await message.create_thread(
+                name=f"App-{platform[0]}-{username[:80]}",
+                auto_archive_duration=10080
+            )
         except discord.errors.Forbidden:
             # no permission to send message (should be reported to staff
             # server owner I suppose)
             raise
         except discord.errors.HTTPException:
             # I expect this HTTP exception to have code=400 "BAD REQUEST"
-            thread = await message.create_thread(name=f"Appeal-{platform[0]}-Malformed username",
-                                                 auto_archive_duration=10080)
+            thread = await message.create_thread(
+                name=f"Appeal-{platform[0]}-Malformed username",
+                auto_archive_duration=10080
+            )
         await thread.join()
         joiner_msg = await thread.send("user-mention placeholder")
 
diff --git a/extensions/nameusage/cogs/nameusage.py b/extensions/nameusage/cogs/nameusage.py
index 3a122ea..fcfb512 100644
--- a/extensions/nameusage/cogs/nameusage.py
+++ b/extensions/nameusage/cogs/nameusage.py
@@ -1,4 +1,6 @@
-import re  # to remove pronouns from user-/nicknames and split names at capital letters
+import re
+# ^ to remove pronouns from user-/nicknames and split names at
+#  capital letters.
 
 import discord
 import discord.app_commands as app_commands
@@ -9,18 +11,33 @@
 from resources.customs import Bot
 
 
-class NameUsage(commands.GroupCog, name="nameusage", description="Get data about which names are used in which server"):
+class NameUsage(
+        commands.GroupCog,
+        name="nameusage",
+        description="Get data about which names are used in which server"
+):
     def __init__(self):
         pass
 
-    @app_commands.command(name="gettop", description="See how often different names occur in this server")
+    @app_commands.command(
+        name="gettop",
+        description="See how often different names occur in this server"
+    )
     @app_commands.choices(mode=[
-        discord.app_commands.Choice(name='Search most-used usernames', value=1),
-        discord.app_commands.Choice(name='Search most-used nicknames', value=2),
-        discord.app_commands.Choice(name='Search nicks and usernames', value=3),
+        discord.app_commands.Choice(name='Search most-used usernames',
+                                    value=1),
+        discord.app_commands.Choice(name='Search most-used nicknames',
+                                    value=2),
+        discord.app_commands.Choice(name='Search nicks and usernames',
+                                    value=3),
     ])
     @app_commands.check(not_in_dms_check)
-    async def nameusage_gettop(self, itx: discord.Interaction[Bot], mode: int):
+    async def nameusage_gettop(
+            self,
+            itx: discord.Interaction[Bot],
+            mode: int
+    ):
+        # todo: split this function into multiple smaller functions
         await itx.response.defer(ephemeral=True)
         sections = {}
         for member in itx.guild.members:
@@ -59,7 +76,12 @@ async def nameusage_gettop(self, itx: discord.Interaction[Bot], mode: int):
                     _name_backup = new_name + " "
                     while new_name != _name_backup:
                         _name_backup = new_name
-                        new_name = re.sub(pronoun, "", new_name, flags=re.IGNORECASE)
+                        new_name = re.sub(
+                            pronoun,
+                            "",
+                            new_name,
+                            flags=re.IGNORECASE
+                        )
 
                 names[index] = new_name
 
@@ -75,7 +97,11 @@ def add(member_name_part):
                         parts = []
                         match = 1
                         while match:
-                            match = re.search("[A-Z][a-z]*[A-Z]", section, re.MULTILINE)
+                            match = re.search(
+                                "[A-Z][a-z]*[A-Z]",
+                                section,
+                                re.MULTILINE
+                            )
                             if match:
                                 caps = match.span()[1] - 1
                                 parts.append(section[:caps])
@@ -111,27 +137,44 @@ def add(member_name_part):
 
         result_page = pages[page]
         result_page2 = pages[page + 1]
-        mode_text = "usernames" if mode == 1 else "nicknames" if mode == 2 else "usernames and nicknames"
+        mode_text = ("usernames" if mode == 1
+                     else "nicknames" if mode == 2
+                     else "usernames and nicknames")
         embed_title = f'Most-used {mode_text} leaderboard!'
         embed = discord.Embed(color=8481900, title=embed_title)
         embed.add_field(name="Column 1", value=result_page)
         embed.add_field(name="Column 2", value=result_page2)
-        embed.set_footer(text="page: " + str(page + 1) + " / " + str(int(len(pages) / 2)))
+        embed.set_footer(
+            text="page: "
+                 + str(page + 1)
+                 + " / "
+                 + str(int(len(pages) / 2))
+        )
         view = GetTopPageView(pages, embed_title, timeout=60)
         await itx.followup.send("", embed=embed, view=view, ephemeral=True)
         await view.wait()
         if view.value is None:
             await itx.edit_original_response(view=None)
 
-    @app_commands.command(name="name", description="See how often different names occur in this server")
+    @app_commands.command(
+        name="name",
+        description="See how often different names occur in this server"
+    )
     @app_commands.describe(name="What specific name are you looking for?")
     @app_commands.choices(search_type=[
         discord.app_commands.Choice(name='usernames', value=1),
         discord.app_commands.Choice(name='nicknames', value=2),
-        discord.app_commands.Choice(name='Search both nicknames and usernames', value=3),
+        discord.app_commands.Choice(name='Search both nicknames and usernames',
+                                    value=3),
     ])
     @app_commands.check(not_in_dms_check)
-    async def nameusage_name(self, itx: discord.Interaction[Bot], name: str, search_type: int, public: bool = False):
+    async def nameusage_name(
+            self,
+            itx: discord.Interaction[Bot],
+            name: str,
+            search_type: int,
+            public: bool = False
+    ):
         await itx.response.defer(ephemeral=not public)
         count = 0
         type_string = ""
@@ -149,11 +192,14 @@ async def nameusage_name(self, itx: discord.Interaction[Bot], name: str, search_
         elif search_type == 3:  # usernames and nicknames
             for member in itx.guild.members:
                 if member.nick is not None:
-                    if name.lower() in member.nick.lower() or name.lower() in member.name.lower():
+                    if (name.lower() in member.nick.lower()
+                            or name.lower() in member.name.lower()):
                         count += 1
                 elif name.lower() in member.name.lower():
                     count += 1
             type_string = "username or nickname"
         await itx.followup.send(
-            f"I found {count} {'person' if count == 1 else 'people'} with '{name.lower()}' in their {type_string}",
-            ephemeral=not public)
+            f"I found {count} {'person' if count == 1 else 'people'} "
+            f"with '{name.lower()}' in their {type_string}",
+            ephemeral=not public,
+        )
diff --git a/extensions/nameusage/modals/getnamemodal.py b/extensions/nameusage/modals/getnamemodal.py
index 2968bae..61b59bc 100644
--- a/extensions/nameusage/modals/getnamemodal.py
+++ b/extensions/nameusage/modals/getnamemodal.py
@@ -11,36 +11,48 @@ def __init__(self, pages, embed_title, timeout=None):
         self.page = None
 
         self.word = None
-        self.question_text = discord.ui.TextInput(label='What word to look up in the server name list?',
-                                                  placeholder="The word you want to look up",
-                                                  # style=discord.TextStyle.short,
-                                                  # required=True
-                                                  )
+        self.question_text = discord.ui.TextInput(
+            label='What word to look up in the server name list?',
+            placeholder="The word you want to look up",
+            # style=discord.TextStyle.short,
+            # required=True
+        )
         self.add_item(self.question_text)
 
     async def on_submit(self, itx: discord.Interaction):
         self.value = 9  # failed; placeholder
         self.word = self.question_text.value.lower()
         for page_id in range(len(self.pages)):
-            # self.pages[page_id] returns ['15 nora\n13 rose\n9 brand\n8 george\n4 rina\n3 grace\n2 eliza\n','_']
-            # split at \n and " " to get [["15", "nora"], ["13", "rose"], ["9", "brand"], ["8", "george"]] and
-            # compare self.word with the names
-            if self.word in [name.split(" ")[-1] for name in self.pages[page_id].split("\n")]:
+            # self.pages[page_id] returns ['15 nora\n13 rose\n9 brand\n8
+            #  george\n4 rina\n3 grace\n2 eliza\n','_']
+            # split at \n and " " to get [["15", "nora"],
+            #  ["13", "rose"], ["9", "brand"], ["8", "george"]]
+            #  and compare self.word with the names.
+            if (self.word in [name.split(" ")[-1]
+                              for name in self.pages[page_id].split("\n")]):
                 self.page = int(page_id / 2)
                 break
         else:
             await itx.response.send_message(
-                content=f"I couldn't find '{self.word}' in any of the pages! Perhaps nobody chose this name!",
+                content=f"I couldn't find '{self.word}' in any of the pages! "
+                        f"Perhaps nobody chose this name!",
                 ephemeral=True)
             return
         result_page = self.pages[self.page * 2]
         result_page2 = self.pages[self.page * 2 + 1]
-        result_page = result_page.replace(f" {self.word}\n", f" **__{self.word}__**\n")
-        result_page2 = result_page2.replace(f" {self.word}\n", f" **__{self.word}__**\n")
+        result_page = result_page.replace(f" {self.word}\n",
+                                          f" **__{self.word}__**\n")
+        result_page2 = result_page2.replace(f" {self.word}\n",
+                                            f" **__{self.word}__**\n")
         embed = discord.Embed(color=8481900, title=self.embed_title)
         embed.add_field(name="Column 1", value=result_page)
         embed.add_field(name="Column 2", value=result_page2)
-        embed.set_footer(text="page: " + str(self.page + 1) + " / " + str(int(len(self.pages) / 2)))
+        embed.set_footer(
+            text="page: "
+                 + str(self.page + 1)
+                 + " / "
+                 + str(int(len(self.pages) / 2))
+        )
         await itx.response.edit_message(embed=embed)
         self.value = 1
         self.stop()
diff --git a/extensions/nameusage/views/pageview.py b/extensions/nameusage/views/pageview.py
index 4d11e29..e9365f9 100644
--- a/extensions/nameusage/views/pageview.py
+++ b/extensions/nameusage/views/pageview.py
@@ -15,18 +15,24 @@ def __init__(self, pages, embed_title, timeout=None):
         self.embed_title = embed_title
 
     async def _make_page(self):
-        # todo: extract duplicated code to a new PageView subclass. Oh yeah. This should definitely be a PageView.
+        # todo: extract duplicated code to a new PageView subclass.
+        #  Oh yeah. This should definitely be a PageView.
         result_page = self.pages[self.page * 2]
         result_page2 = self.pages[self.page * 2 + 1]
         embed = discord.Embed(color=8481900, title=self.embed_title)
         embed.add_field(name="Column 1", value=result_page)
         embed.add_field(name="Column 2", value=result_page2)
-        embed.set_footer(text="page: " + str(self.page + 1) + " / " + str(int(len(self.pages) / 2)))
+        embed.set_footer(
+            text="page: "
+                 + str(self.page + 1)
+                 + " / "
+                 + str(int(len(self.pages) / 2))
+        )
         return embed
 
-    # When the confirm button is pressed, set the inner value to `True` and
-    # stop the View from listening to more input.
-    # We also send the user an ephemeral message that we're confirming their choice.
+    # When the confirm button is pressed, set the inner value to `True`
+    #  and stop the View from listening to more input. We also send the
+    #  user an ephemeral message that we're confirming their choice.
     @discord.ui.button(label='Previous', style=discord.ButtonStyle.blurple)
     async def previous(self, itx: discord.Interaction[Bot], _button: discord.ui.Button):
         # self.value = "previous"
diff --git a/extensions/qotw/cogs/devrequest.py b/extensions/qotw/cogs/devrequest.py
index 7f5a022..d0aab9a 100644
--- a/extensions/qotw/cogs/devrequest.py
+++ b/extensions/qotw/cogs/devrequest.py
@@ -22,31 +22,46 @@ class DevRequest(commands.Cog):
     def __init__(self, client: Bot):
         self.client = client
 
-    @app_commands.command(name="developer_request", description="Suggest a bot idea to the TransPlace developers!")
+    @app_commands.command(
+        name="developer_request",
+        description="Suggest a bot idea to the TransPlace developers!"
+    )
     @app_commands.describe(suggestion="What idea would you like to share?")
     @module_enabled_check(ModuleKeys.dev_requests)
-    async def developer_request(self, itx: discord.Interaction[Bot], suggestion: app_commands.Range[str, 25, 1500]):
+    async def developer_request(
+            self,
+            itx: discord.Interaction[Bot],
+            suggestion: app_commands.Range[str, 25, 1500]
+    ):
         developer_request_channel: discord.TextChannel | None
         developer_role: discord.Role | None
-        developer_request_channel, developer_role = itx.client.get_guild_attribute(
-            itx.guild, AttributeKeys.developer_request_channel,
-            AttributeKeys.developer_request_reaction_role)
+        developer_request_channel, developer_role = \
+            itx.client.get_guild_attribute(
+                itx.guild,
+                AttributeKeys.developer_request_channel,
+                AttributeKeys.developer_request_reaction_role
+            )
         if developer_request_channel is None or developer_role is None:
+            # noinspection LongLine
             missing = [key for key, value in {
                 AttributeKeys.developer_request_channel: developer_request_channel,
-                AttributeKeys.developer_request_reaction_role: developer_role}.items()
+                AttributeKeys.developer_request_reaction_role: developer_role
+            }.items()
                 if value is None]
-            raise MissingAttributesCheckFailure(ModuleKeys.dev_requests, missing)
+            raise MissingAttributesCheckFailure(
+                ModuleKeys.dev_requests, missing)
 
         if len(suggestion) > 4000:
-            await itx.response.send_message("Your suggestion won't fit! Please make your suggestion shorter. "
-                                            "If you have a special request, you could make a ticket too "
-                                            "(in #contact-staff)",
-                                            ephemeral=True)
+            await itx.response.send_message(
+                "Your suggestion won't fit! Please make your suggestion "
+                "shorter. If you have a special request, you could make a "
+                "ticket too (in #contact-staff)",
+                ephemeral=True)
             return
         await itx.response.defer(ephemeral=True)
 
-        # make uncool embed for the loading period while it sends the copyable version
+        # Make uncool embed for the loading period while it sends the
+        #  copyable version
         embed = discord.Embed(
             color=discord.Colour.from_rgb(r=33, g=33, b=33),
             description="Loading request...",
@@ -58,11 +73,17 @@ async def developer_request(self, itx: discord.Interaction[Bot], suggestion: app
             allowed_mentions=discord.AllowedMentions.none(),
         )
         # make and join a thread under the question
-        thread = await msg.create_thread(name=f"BotRQ-{suggestion[:48]}", auto_archive_duration=10080)
+        thread = await msg.create_thread(
+            name=f"BotRQ-{suggestion[:48]}",
+            auto_archive_duration=10080
+        )
         await thread.join()
         suggestion = suggestion.replace("\\n", "\n")
         # send a plaintext version of the question, and copy a link to it
-        copyable_version = await thread.send(f"{suggestion}", allowed_mentions=discord.AllowedMentions.none())
+        copyable_version = await thread.send(
+            f"{suggestion}",
+            allowed_mentions=discord.AllowedMentions.none()
+        )
 
         # Mention developers in a message edit, adding them all to the thread
         # without mentioning them and do the same for the requester, though
@@ -77,14 +98,18 @@ async def developer_request(self, itx: discord.Interaction[Bot], suggestion: app
                         f"developer request is created. Use "
                         f"{cmd_mention_settings} to add one.")
         else:
-            await joiner_msg.edit(content=f"<@&{developer_role.id}> <@{itx.user.id}>")
+            await joiner_msg.edit(
+                content=f"<@&{developer_role.id}> <@{itx.user.id}>")
             await joiner_msg.delete()
 
-        # edit the uncool embed to make it cool: Show question, link to plaintext, and upvotes/downvotes
+        # edit the uncool embed to make it cool: Show question, link to
+        #  plaintext, and upvotes/downvotes
         embed = discord.Embed(
             color=discord.Colour.from_rgb(r=255, g=255, b=172),
             title='',
-            description=f"{suggestion}\n[Jump to plain version]({copyable_version.jump_url})",
+            description=f"{suggestion}\n"
+                        f"[Jump to plain version]"
+                        f"({copyable_version.jump_url})",
             timestamp=datetime.now()
         )
         embed.set_author(
@@ -97,14 +122,22 @@ async def developer_request(self, itx: discord.Interaction[Bot], suggestion: app
         await msg.edit(embed=embed)
         await msg.add_reaction("⬆️")
         await msg.add_reaction("⬇️")
-        await itx.followup.send("Successfully added your suggestion! The developers will review your idea, "
-                                "and perhaps inform you when it gets added :D", ephemeral=True)
+        await itx.followup.send(
+            "Successfully added your suggestion! The developers will review "
+            "your idea, and perhaps inform you when it gets added :D",
+            ephemeral=True
+        )
 
-    @app_commands.command(name="ping_open_dev_requests",
-                          description="Send a message in closed green dev request threads")
+    @app_commands.command(
+        name="ping_open_dev_requests",
+        description="Send a message in closed green dev request threads"
+    )
     @app_commands.check(is_staff_check)
     @module_enabled_check(ModuleKeys.dev_requests)
-    async def ping_open_developer_requests(self, itx: discord.Interaction[Bot]):
+    async def ping_open_developer_requests(
+            self,
+            itx: discord.Interaction[Bot]
+    ):
         await itx.response.send_message("`[+  ]`: Fetching cached threads.",
                                         ephemeral=True)
         watchlist_channel: discord.TextChannel | None
@@ -120,13 +153,15 @@ async def ping_open_developer_requests(self, itx: discord.Interaction[Bot]):
         async for thread in watchlist_channel.archived_threads(limit=None):
             threads.append(thread)
 
-        await itx.edit_original_response(content="`[#+ ]`: Fetching archived threads...")
+        await itx.edit_original_response(
+            content="`[#+ ]`: Fetching archived threads...")
         archived_thread_ids = [t.id for t in threads]
         for thread in watchlist_channel.threads:
             if thread.archived and thread.id not in archived_thread_ids:
                 threads.append(thread)
 
-        await itx.edit_original_response(content="`[##+]`: Sending messages in threads...")
+        await itx.edit_original_response(
+            content="`[##+]`: Sending messages in threads...")
 
         not_found_count = 0
         ignored_count = 0
@@ -134,7 +169,8 @@ async def ping_open_developer_requests(self, itx: discord.Interaction[Bot]):
 
         for thread in threads:
             try:
-                starter_message = await watchlist_channel.fetch_message(thread.id)
+                starter_message = \
+                    await watchlist_channel.fetch_message(thread.id)
             except discord.errors.NotFound:
                 not_found_count += 1
                 continue  # thread starter message was removed.
@@ -147,30 +183,48 @@ async def ping_open_developer_requests(self, itx: discord.Interaction[Bot]):
             if starter_message.embeds[0].color in [emoji_color_options["🟡"],
                                                    emoji_color_options["🔵"]]:
                 try:
-                    cmd_mention = itx.client.get_command_mention("ping_open_dev_requests")
-                    await thread.send(itx.user.mention + f" poked this thread with {cmd_mention}.\n"
-                                                         "This channel got a message because it was archived and "
-                                                         "the request wasn't marked as completed or rejected.",
-                                      allowed_mentions=discord.AllowedMentions.none())
+                    cmd_mention = itx.client.get_command_mention(
+                        "ping_open_dev_requests")
+                    await thread.send(
+                        itx.user.mention
+                        + f" poked this thread with {cmd_mention}.\n"
+                          f"This channel got a message because it was "
+                          f"archived and the request wasn't marked as "
+                          f"completed or rejected.",
+                        allowed_mentions=discord.AllowedMentions.none()
+                    )
                     pinged_thread_count += 1
                 except discord.Forbidden:
                     failed_threads.append(thread.id)
 
         await itx.edit_original_response(
             content=(f"`[###]`: Pinged {pinged_thread_count} archived "
-                     f"channel{'' if pinged_thread_count == 1 else 's'} successfully!\n"
+                     f"channel{'' if pinged_thread_count == 1 else 's'} "
+                     f"successfully!\n"
                      f"\n"
-                     f"Ignored `{ignored_count}` threads (not by bot or no embeds, etc.)\n"
+                     f"Ignored `{ignored_count}` threads (not by bot or no "
+                     f"embeds, etc.)\n"
                      f"Could not find `{not_found_count}` starter messages.\n"
-                     f"Could not send a message in the following {len(failed_threads)} threads:\n"
-                     f"- {', '.join(['<#' + str(t_id) + '>' for t_id in failed_threads])}")[:2000])
+                     f"Could not send a message in the following "
+                     f"{len(failed_threads)} threads:\n"
+                     f"- {', '.join(['<#' + str(t_id) + '>'
+                                     for t_id in failed_threads])}"
+                     )[:2000]
+        )
 
     @commands.Cog.listener()
-    async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent):
-        if not self.client.is_module_enabled(payload.guild_id, ModuleKeys.dev_requests):
+    async def on_raw_reaction_add(
+            self,
+            payload: discord.RawReactionActionEvent
+    ):
+        if not self.client.is_module_enabled(
+                payload.guild_id, ModuleKeys.dev_requests):
             return
-        dev_request_channel: discord.TextChannel = self.client.get_guild_attribute(
-            payload.guild_id, AttributeKeys.developer_request_channel)
+        dev_request_channel: discord.TextChannel | None = \
+            self.client.get_guild_attribute(
+                payload.guild_id,
+                AttributeKeys.developer_request_channel
+            )
         if dev_request_channel is None:
             raise MissingAttributesCheckFailure(
                 ModuleKeys.dev_requests,
diff --git a/extensions/qotw/cogs/qotw.py b/extensions/qotw/cogs/qotw.py
index 6fb7b7d..b55eca3 100644
--- a/extensions/qotw/cogs/qotw.py
+++ b/extensions/qotw/cogs/qotw.py
@@ -1,11 +1,16 @@
-from datetime import datetime  # to get embed send time for embed because cool (serves no real purpose)
+from datetime import datetime
+# to get embed send time for embed because cool (serves no real purpose)
 
 import discord
 import discord.app_commands as app_commands
 import discord.ext.commands as commands
 
 from extensions.settings.objects import ModuleKeys, AttributeKeys
-from resources.checks import not_in_dms_check, module_enabled_check, MissingAttributesCheckFailure
+from resources.checks import (
+    not_in_dms_check,
+    module_enabled_check,
+    MissingAttributesCheckFailure
+)
 from resources.utils.utils import get_mod_ticket_channel
 from resources.customs import Bot
 
@@ -14,33 +19,44 @@ class QOTW(commands.Cog):
     def __init__(self):
         pass
 
-    @app_commands.command(name="qotw", description="Suggest a question for the weekly queue!")
+    @app_commands.command(
+        name="qotw",
+        description="Suggest a question for the weekly queue!"
+    )
     @app_commands.describe(question="What question would you like to add?")
     @app_commands.check(not_in_dms_check)
     @module_enabled_check(ModuleKeys.qotw)
     async def qotw(self, itx: discord.Interaction[Bot], question: str):
         if len(question) > 400:
-            ticket_channel: discord.abc.Messageable | None = get_mod_ticket_channel(itx.client, guild_id=itx.guild.id)
+            ticket_channel: discord.abc.Messageable | None \
+                = get_mod_ticket_channel(itx.client, guild_id=itx.guild.id)
             if ticket_channel:
-                special_request_string = f"make a ticket (in <#{ticket_channel.id}>)."
+                special_request_string = (f"make a ticket (in "
+                                          f"<#{ticket_channel.id}>).")
             else:
                 special_request_string = "contact staff directly."
             await itx.response.send_message(
-                "Please make your question shorter! (400 characters). If you have a special request, please " +
+                "Please make your question shorter! (400 characters). "
+                "If you have a special request, please " +
                 special_request_string, ephemeral=True)
             await itx.followup.send("-# " + question, ephemeral=True)
             return
 
         # get channel of where this message has to be sent
-        qotw_channel = itx.client.get_guild_attribute(itx.guild,
-                                                      AttributeKeys.qotw_suggestions_channel)
+        qotw_channel = itx.client.get_guild_attribute(
+            itx.guild,
+            AttributeKeys.qotw_suggestions_channel
+        )
         if qotw_channel is None:
             raise MissingAttributesCheckFailure(
-                ModuleKeys.qotw, [AttributeKeys.qotw_suggestions_channel])
+                ModuleKeys.qotw,
+                [AttributeKeys.qotw_suggestions_channel]
+            )
 
         await itx.response.defer(ephemeral=True)
 
-        # make uncool embed for the loading period while it sends the copyable version
+        # make uncool embed for the loading period while it sends
+        #  the copyable version
         embed = discord.Embed(
             color=discord.Colour.from_rgb(r=33, g=33, b=33),
             description="Loading question...",
@@ -55,12 +71,18 @@ async def qotw(self, itx: discord.Interaction[Bot], question: str):
         thread = await msg.create_thread(name=f"QOTW-{question[:50]}")
         await thread.join()
         # send a plaintext version of the question, and copy a link to it
-        copyable_version = await thread.send(f"{question}", allowed_mentions=discord.AllowedMentions.none())
-        # edit the uncool embed to make it cool: Show question, link to plaintext, and upvotes/downvotes
+        copyable_version = await thread.send(
+            f"{question}",
+            allowed_mentions=discord.AllowedMentions.none()
+        )
+        # edit the uncool embed to make it cool: Show question,
+        #  link to plaintext, and upvotes/downvotes
         embed = discord.Embed(
             color=discord.Colour.from_rgb(r=255, g=255, b=172),
             title='',
-            description=f"{question}\n[Jump to plain version]({copyable_version.jump_url})",
+            description=f"{question}\n"
+                        f"[Jump to plain version]"
+                        f"({copyable_version.jump_url})",
             timestamp=datetime.now()
         )
         embed.set_author(
@@ -74,5 +96,7 @@ async def qotw(self, itx: discord.Interaction[Bot], question: str):
         await msg.add_reaction("⬆️")
         await msg.add_reaction("⬇️")
         await itx.followup.send(
-            "Successfully added your question to the queue! (must first be accepted by the staff team)",
-            ephemeral=True)
+            "Successfully added your question to the queue! (must first "
+            "be accepted by the staff team)",
+            ephemeral=True
+        )
diff --git a/extensions/reminders/cogs/reminders.py b/extensions/reminders/cogs/reminders.py
index 5fe8365..5da5fbb 100644
--- a/extensions/reminders/cogs/reminders.py
+++ b/extensions/reminders/cogs/reminders.py
@@ -14,11 +14,20 @@ class RemindersCog(commands.GroupCog, name="reminder"):
     def __init__(self):
         pass
 
-    @app_commands.command(name="remindme", description="Add a reminder for yourself!")
-    @app_commands.describe(reminder_datetime="When would you like me to remind you? (1d2h, 5 weeks, 1mo10d)",
-                           reminder="What would you like me to remind you of?")
+    @app_commands.command(name="remindme",
+                          description="Add a reminder for yourself!")
+    @app_commands.describe(
+        reminder_datetime="When would you like me to remind you? "
+                          "(1d2h, 5 weeks, 1mo10d)",
+        reminder="What would you like me to remind you of?"
+    )
     @app_commands.rename(reminder_datetime='time')
-    async def remindme(self, itx: discord.Interaction[Bot], reminder_datetime: str, reminder: str):
+    async def remindme(
+            self,
+            itx: discord.Interaction[Bot],
+            reminder_datetime: str,
+            reminder: str
+    ):
         # Supported formats:
         # - "next thursday at 3pm"
         # - "tomorrow"
@@ -29,7 +38,8 @@ async def remindme(self, itx: discord.Interaction[Bot], reminder_datetime: str,
         # - ""
         if len(reminder) > 1500:
             await itx.response.send_message(
-                "Please keep reminder text below 1500 characters... Otherwise I can't send you a message about it!",
+                "Please keep reminder text below 1500 characters... Otherwise "
+                "I can't send you a message about it!",
                 ephemeral=True)
             return
 
@@ -57,10 +67,13 @@ async def remindme(self, itx: discord.Interaction[Bot], reminder_datetime: str,
         except MalformedISODateTimeException as ex:
             await itx.response.send_message(
                 f"Couldn't make new reminder:\n> {str(ex.inner_exception)}\n"
-                "You can only use a format like [number][letter], or yyyy-mm-ddThh:mm:ss+0000. Some examples:\n"
-                '    "3mo 0.5d", "in 2 hours, 3.5 mins", "1 year and 3 seconds", "3day4hour", "4d1m", '
+                "You can only use a format like [number][letter], or "
+                "yyyy-mm-ddThh:mm:ss+0000. Some examples:\n"
+                '    "3mo 0.5d", "in 2 hours, 3.5 mins", '
+                '"1 year and 3 seconds", "3day4hour", "4d1m", '
                 '"2023-12-31T23:59+0100", "\n'
-                "Words like \"in\" and \"and\" are ignored, so you can use those for readability if you'd like.\n"
+                "Words like \"in\" and \"and\" are ignored, so you can use "
+                "those for readability if you'd like.\n"
                 '    year = y, year, years\n'
                 '    month = mo, month, months\n'
                 '    week = w, week, weeks\n'
@@ -74,12 +87,16 @@ async def remindme(self, itx: discord.Interaction[Bot], reminder_datetime: str,
         except TimestampParseException as ex:
             await itx.response.send_message(
                 f"Couldn't make new reminder:\n> {str(ex.inner_exception)}\n\n"
-                "You can make a reminder for days in advance, like so: \"4d12h\", \"4day 12hours\" or "
-                "\"in 3 minutes and 2 seconds\"\n"
-                "You can also use ISO8601 format, like '2023-12-31T23:59+0100', or just '2023-12-31'\n"
-                "Or you can use Unix Epoch format: the amount of seconds since January 1970: '1735155750"
+                "You can make a reminder for days in advance, like so: "
+                "\"4d12h\", \"4day 12hours\" or \"in 3 minutes and "
+                "2 seconds\"\n"
+                "You can also use ISO8601 format, like "
+                "'2023-12-31T23:59+0100', or just '2023-12-31'\n"
+                "Or you can use Unix Epoch format: the amount of seconds "
+                "since January 1970: '1735155750"
                 "\n"
-                "If you give a time but not a timezone, I don't want you to get reminded at the wrong time, "
+                "If you give a time but not a timezone, I don't want you to "
+                "get reminded at the wrong time, "
                 "so I'll say something went wrong.\n"
                 f"For more info, use {cmd_mention_help} `page:113`.",
                 ephemeral=True
@@ -88,7 +105,8 @@ async def remindme(self, itx: discord.Interaction[Bot], reminder_datetime: str,
         except MissingQuantityException as ex:
             await itx.response.send_message(
                 f"Couldn't make new reminder:\n> {str(ex)}\n\n"
-                f"Be sure you start the reminder time with a number like \"4 days\".\n"
+                f"Be sure you start the reminder time with a number "
+                f"like \"4 days\".\n"
                 f"For more info, use {cmd_mention_help} `page:113`.",
                 ephemeral=True
             )
@@ -96,10 +114,14 @@ async def remindme(self, itx: discord.Interaction[Bot], reminder_datetime: str,
         except MissingUnitException as ex:
             await itx.response.send_message(
                 f"Couldn't make new reminder:\n> {str(ex)}\n\n"
-                f"Be sure you end the reminder time with a unit like \"4 days\".\n"
-                f"If you intended to use a unix timestamp instead, make sure your timestamp is correct. Any number"
-                f"below 1000000 is parsed in the \"1 day 2 hours\" format, which means not providing a unit will"
-                f"give this error. Note: a unix timestamp of 1000000 is 20 Jan 1970 ( = )\n"
+                f"Be sure you end the reminder time with a unit like "
+                f"\"4 days\".\n"
+                f"If you intended to use a unix timestamp instead, make "
+                f"sure your timestamp is correct. Any number below 1000000 "
+                f"is parsed in the \"1 day 2 hours\" format, which means not "
+                f"providing a unit will give this error. Note: a unix "
+                f"timestamp of 1000000 is 20 Jan 1970 "
+                f"( = )\n"
                 f"For more info, use {cmd_mention_help} `page:113`.",
                 ephemeral=True
             )
@@ -107,16 +129,25 @@ async def remindme(self, itx: discord.Interaction[Bot], reminder_datetime: str,
         except ReminderTimeSelectionMenuTimeOut:
             return
 
-    @app_commands.command(name="reminders", description="Check your list of reminders!")
-    @app_commands.describe(item="Which reminder would you like to know more about? (use reminder-ID)")
-    async def reminders(self, itx: discord.Interaction[Bot], item: int = None):
+    @app_commands.command(name="reminders",
+                          description="Check your list of reminders!")
+    @app_commands.describe(
+        item="Which reminder would you like to know more about? "
+             "(use reminder-ID)"
+    )
+    async def reminders(
+            self,
+            itx: discord.Interaction[Bot],
+            item: int = None
+    ):
         collection = itx.client.rina_db["reminders"]
         query = {"userID": itx.user.id}
         db_data = collection.find_one(query)
         if db_data is None:
             cmd_mention = itx.client.get_command_mention("reminder remindme")
             await itx.response.send_message(
-                f"You don't have any reminders running at the moment!\nUse {cmd_mention} to make a reminder!",
+                f"You don't have any reminders running at the moment!\n"
+                f"Use {cmd_mention} to make a reminder!",
                 ephemeral=True)
             return
 
@@ -125,46 +156,72 @@ async def reminders(self, itx: discord.Interaction[Bot], item: int = None):
             index = 0
             if item is None:
                 for reminder in db_data['reminders']:
-                    out.append(f"ID: `{index}` | Created at:  | "
-                               f"Remind you about: {discord.utils.escape_markdown(reminder['reminder'])}")
+                    reminder_text = discord.utils.escape_markdown(
+                        reminder['reminder'])
+                    out.append(
+                        f"ID: `{index}` | Created at: "
+                        f" | "
+                        f"Remind you about: {reminder_text}"
+                    )
                     index += 1
                 out_msg = "Your reminders:\n" + '\n'.join(out)
                 if len(out_msg) >= 1995:
                     out = []
                     index = 0
                     for reminder in db_data['reminders']:
-                        out.append(f"`{index}` | ")
+                        out.append(f"`{index}` | "
+                                   f"")
                         index += 1
-                    cmd_mention = itx.client.get_command_mention("reminder reminders")
-                    out_msg = ((f"You have {len(db_data['reminders'])} reminders "
-                                f"(use {cmd_mention} `item: ` to get more info about a reminder):\n") +
-                               '\n'.join(out)[:1996])
+                    cmd_mention = itx.client.get_command_mention(
+                        "reminder reminders")
+                    out_msg = (
+                        (f"You have {len(db_data['reminders'])} reminders "
+                         f"(use {cmd_mention} `item: ` to get more info about "
+                         f"a reminder):\n")
+                        + '\n'.join(out)[:1996]
+                    )
                 await itx.response.send_message(out_msg, ephemeral=True)
             else:
                 reminder = db_data['reminders'][item]
+                create_time_str = (f" "
+                                   f"()")
+                remind_time_str = (f" "
+                                   f"()")
+                text = discord.utils.escape_markdown(reminder['reminder'])
                 await itx.response.send_message(
-                    f"Showing reminder `{index}` out of `{len(db_data['reminders'])}`:\n" +
-                    f"  ID: `{index}`\n" +
-                    f"  Created at:              ()\n" +
-                    f"  Reminding you at:  ()\n" +
-                    f"  Remind you about: {discord.utils.escape_markdown(reminder['reminder'])}",
-                    ephemeral=True)
+                    f"Showing reminder `{index}` out of "
+                    f"`{len(db_data['reminders'])}`:\n"
+                    f"  ID: `{index}`\n"
+                    f"  Created at:             {create_time_str}\n"
+                    f"  Reminding you at: {remind_time_str}\n"
+                    f"  Remind you about: {text}",
+                    ephemeral=True,
+                )
+                return
         except IndexError:
             cmd_mention = itx.client.get_command_mention("reminder reminders")
             await itx.response.send_message(
                 f"I couldn't find any reminder with that ID!\n"
-                f"Look for the \"ID: `0`\" at the beginning of your reminder on the reminder list ({cmd_mention})",
-                ephemeral=True)
+                f"Look for the \"ID: `0`\" at the beginning of your reminder "
+                f"on the reminder list ({cmd_mention})",
+                ephemeral=True,
+            )
             return
         except KeyError:
             cmd_mention = itx.client.get_command_mention("reminder remindme")
             await itx.response.send_message(
-                f"You don't have any reminders running at the moment.\nUse {cmd_mention} to make a reminder!",
-                ephemeral=True)
+                f"You don't have any reminders running at the moment.\n"
+                f"Use {cmd_mention} to make a reminder!",
+                ephemeral=True,
+            )
             return
 
-    @app_commands.command(name="remove", description="Remove of your reminders")
-    @app_commands.describe(item="Which reminder would you like to know more about? (use reminder-ID)")
+    @app_commands.command(name="remove",
+                          description="Remove of your reminders")
+    @app_commands.describe(
+        item="Which reminder would you like to know more about? "
+             "(use reminder-ID)"
+    )
     async def remove(self, itx: discord.Interaction[Bot], item: int):
         collection = itx.client.rina_db["reminders"]
         query = {"userID": itx.user.id}
@@ -172,7 +229,8 @@ async def remove(self, itx: discord.Interaction[Bot], item: int):
         if db_data is None:
             cmd_mention = itx.client.get_command_mention("reminder remindme")
             await itx.response.send_message(
-                f"You don't have any reminders running at the moment! (so I can't remove any either..)\n"
+                f"You don't have any reminders running at the moment! (so "
+                f"I can't remove any either..)\n"
                 f"Use {cmd_mention} to make a reminder!",
                 ephemeral=True)
             return
@@ -183,21 +241,29 @@ async def remove(self, itx: discord.Interaction[Bot], item: int):
             cmd_mention = itx.client.get_command_mention("reminder reminders")
             await itx.response.send_message(
                 f"I couldn't find any reminder with that ID!\n"
-                f"Look for the \"ID: `0`\" at the beginning of your reminder on the reminder list ({cmd_mention})",
+                f"Look for the \"ID: `0`\" at the beginning of your reminder "
+                f"on the reminder list ({cmd_mention})",
                 ephemeral=True)
             return
         except KeyError:
             cmd_mention = itx.client.get_command_mention("reminder remindme")
             await itx.response.send_message(
-                f"You don't have any reminders running at the moment. (so I can't remove any either..)\n"
+                f"You don't have any reminders running at the moment. (so "
+                f"I can't remove any either..)\n"
                 f"Use {cmd_mention} to make a reminder!",
                 ephemeral=True)
             return
         query = {"userID": itx.user.id}
         if len(db_data['reminders']) > 0:
-            collection.update_one(query, {"$set": {"reminders": db_data['reminders']}}, upsert=True)
+            collection.update_one(
+                query,
+                {"$set": {"reminders": db_data['reminders']}},
+                upsert=True
+            )
         else:
             collection.delete_one(query)
         await itx.response.send_message(
-            f"Successfully removed this reminder! You have {len(db_data['reminders'])} other reminders going.",
-            ephemeral=True)
+            f"Successfully removed this reminder! You have "
+            f"{len(db_data['reminders'])} other reminders going.",
+            ephemeral=True
+        )
diff --git a/extensions/reminders/objects/bumpreminderobject.py b/extensions/reminders/objects/bumpreminderobject.py
index 51d08a1..a49c96d 100644
--- a/extensions/reminders/objects/bumpreminderobject.py
+++ b/extensions/reminders/objects/bumpreminderobject.py
@@ -7,11 +7,17 @@
 
 
 class BumpReminderObject:
-    def __init__(self, client: Bot, guild: discord.Guild, remindertime: datetime):
+    def __init__(
+            self,
+            client: Bot,
+            guild: discord.Guild,
+            remindertime: datetime
+    ):
         self.client = client
         self.guild = guild
         self.remindertime = remindertime - timedelta(seconds=1.5)
-        client.sched.add_job(self.send_reminder, "date", run_date=self.remindertime)
+        client.sched.add_job(self.send_reminder, "date",
+                             run_date=self.remindertime)
 
     async def send_reminder(self):
         if not self.client.is_module_enabled(
@@ -32,5 +38,7 @@ async def send_reminder(self):
             raise MissingAttributesCheckFailure(
                 ModuleKeys.bump_reminder, missing)
 
-        await bump_channel.send(f"{bump_role.mention} The next bump is ready!",
-                                allowed_mentions=discord.AllowedMentions(roles=[bump_role]))
+        await bump_channel.send(
+            f"{bump_role.mention} The next bump is ready!",
+            allowed_mentions=discord.AllowedMentions(roles=[bump_role])
+        )
diff --git a/extensions/reminders/objects/emoji_animate_type.py b/extensions/reminders/objects/emoji_animate_type.py
index b009a8f..dcee75d 100644
--- a/extensions/reminders/objects/emoji_animate_type.py
+++ b/extensions/reminders/objects/emoji_animate_type.py
@@ -3,7 +3,8 @@
 
 class EmojiAnimateType(Enum):
     """
-    A representation for the animation type of an emoji. Used for `get_unused_emojis`.
+    A representation for the animation type of an emoji. Used for
+    `get_unused_emojis`.
     """
     static = 1
     animated = 2
diff --git a/extensions/reminders/objects/reminderobject.py b/extensions/reminders/objects/reminderobject.py
index 9184177..2a11c66 100644
--- a/extensions/reminders/objects/reminderobject.py
+++ b/extensions/reminders/objects/reminderobject.py
@@ -1,4 +1,6 @@
-import asyncio  # to create new reminder task that runs immediately (from a not-async ReminderObject __init__ function)
+import asyncio
+# to create new reminder task that runs immediately (from a
+#  not-async ReminderObject __init__ function)
 from datetime import datetime, timedelta, timezone
 
 import discord
@@ -71,18 +73,32 @@ def __init__(
             # self.remindertime = self.remindertime.astimezone()
             # self.creationtime = self.creationtime.astimezone()
             if self.remindertime < datetime.now().astimezone():
-                self.alert = ("Your reminder was delayed. Probably because the bot was offline for a while. I "
-                              "hope it didn't cause much of an issue!\n")
+                cmd_mention = self.client.get_command_mention(
+                    "developer_request")
+                self.alert = (
+                    f"Your reminder was delayed. If this was longer than "
+                    f"a day, please report this to the developer using "
+                    f"{cmd_mention}"
+                    # "Probably because the bot was offline for a while. "
+                    # "I hope it didn't cause much of an issue!\n"
+                )
                 try:
                     asyncio.get_event_loop().create_task(self.send_reminder())
                 except RuntimeError:
                     pass
                 return
-            client.sched.add_job(self.send_reminder, "date", run_date=self.remindertime)
+            client.sched.add_job(
+                self.send_reminder,
+                "date",
+                run_date=self.remindertime
+            )
         else:
             if self.remindertime < datetime.now().astimezone():
-                self.alert = ("Your reminder date/time has passed already. Perhaps the bot was offline for "
-                              "a while; perhaps you just filled in a time in the past!\n")
+                self.alert = (
+                    "Your reminder date/time has passed already. Perhaps "
+                    "the bot was offline for a while; perhaps you just "
+                    "filled in a time in the past!\n"
+                )
                 try:
                     asyncio.get_event_loop().create_task(self.send_reminder())
                 except RuntimeError:
@@ -96,36 +112,57 @@ def __init__(
             }
             user_reminders.append(reminder_data)
             query = {"userID": user_id}
-            collection.update_one(query, {"$set": {"reminders": user_reminders}}, upsert=True)
-            # print(f"added job for {self.remindertime} and it's currently {self.creationtime}")
-            client.sched.add_job(self.send_reminder, "date", run_date=self.remindertime)
+            collection.update_one(
+                query,
+                {"$set": {"reminders": user_reminders}},
+                upsert=True
+            )
+            client.sched.add_job(
+                self.send_reminder,
+                "date",
+                run_date=self.remindertime
+            )
 
     async def send_reminder(self):
         user = await self.client.fetch_user(self.userID)
         creationtime = int(self.creationtime.timestamp())
         try:
-            await user.send(f"{self.alert}On , you asked to be reminded of \"{self.reminder}\".")
+            await user.send(
+                f"{self.alert}On , you asked to be "
+                f"reminded of \"{self.reminder}\"."
+            )
         except discord.errors.Forbidden:
-            pass  # I guess this user has no servers in common with Rina anymore. Sucks for them.
+            # I guess this user has no servers in common with Rina
+            # anymore. Sucks for them.
+            pass
         collection = self.client.rina_db["reminders"]
         query = {"userID": self.userID}
         db_data = collection.find_one(query)
+        reminders = db_data["reminders"]
         index_subtraction = 0
-        for reminder_index in range(len(db_data['reminders'])):
-            if db_data['reminders'][reminder_index - index_subtraction]["remindertime"] <= int(
-                    datetime.now().timestamp()):
-                del db_data['reminders'][reminder_index - index_subtraction]
+        for reminder_index in range(len(reminders)):
+            current_index = reminder_index - index_subtraction
+            reminder_time = reminders[current_index]["remindertime"]
+            if (reminder_time <= int(datetime.now().timestamp())):
+                del reminders[current_index]
                 index_subtraction += 1
-                # See... If more than 1 reminder is placed at the same time, we don't want the reminder that is
-                # triggered first to remove both reminders from the database... But it may also not be worth
-                # figuring out if a reminder index exactly matches the one in the database to remove it.
-                # So just remove one of them, and hope the update_one doesn't cause thread-unsafe shenanigans.
-                # Any reminder that isn't deleted from the database (or is accidentally added back after) will
-                # eventually be recreated and sent when the bot restarts.
+                # See... If more than 1 reminder is placed at the same time,
+                #  we don't want the reminder that is triggered first to remove
+                #  both reminders from the database... But it may also not be
+                #  worth figuring out if a reminder index exactly matches the
+                #  one in the database to remove it. So just remove one of
+                #  them, and hope the update_one doesn't cause thread-unsafe
+                #  shenanigans.Any reminder that isn't deleted from the
+                #  database (or is accidentally added back after) will
+                #  eventually be recreated and sent when the bot restarts.
                 # Todo: I should probably handle this better.
                 break
-        if len(db_data['reminders']) > 0:
-            collection.update_one(query, {"$set": {"reminders": db_data['reminders']}}, upsert=True)
+        if len(reminders) > 0:
+            collection.update_one(
+                query,
+                {"$set": {"reminders": reminders}},
+                upsert=True
+            )
         else:
             collection.delete_one(query)
 
@@ -133,7 +170,7 @@ async def send_reminder(self):
 async def _handle_reminder_timestamp_parsing(
         itx: discord.Interaction[Bot],
         reminder_datetime: str
-) -> tuple[datetime, discord.Interaction]:  # todo: docstring
+) -> tuple[datetime, discord.Interaction[Bot]]:  # todo: docstring
     # validate format
     # note: "t" here is lowercase because the reminder_datetime string
     #  gets lowercased...
@@ -142,16 +179,21 @@ async def _handle_reminder_timestamp_parsing(
         # check input character validity
         for char in reminder_datetime:
             if char not in "0123456789-t:+z":  # z for timezone +0000
-                raise ValueError(f"`{char}` cannot be used for a reminder date/time.")
+                raise ValueError(
+                    f"`{char}` cannot be used for a reminder date/time.")
 
         date, time = reminder_datetime.split("t")
         if date.count("-") != 2:
-            raise ValueError("Incorrect date given! Please format the date as YYYY-MM-DD, like 2023-12-31")
+            raise ValueError(
+                "Incorrect date given! Please format the date as "
+                "YYYY-MM-DD, like 2023-12-31"
+            )
 
         has_timezone = "+" in time or "-" in time
         if has_timezone:
-            # to check if the user gave seconds, I count the amount of : in the message
-            # If the timezone is provided you always have ":" extra: 23:04:23+01:00 = 3 colons
+            # to check if the user gave seconds, I count the amount
+            #  of : in the message. If the timezone is provided you always
+            #  have ":" extra: 23:04:23+01:00 = 3 colons
             time = time.split("+")[0]
             time = time.split("-")[0]
         if time.count(":") == 1:
@@ -159,29 +201,44 @@ async def _handle_reminder_timestamp_parsing(
         elif time.count(":") == 2:
             mode = TimestampFormats.DateTimeToSeconds
         else:
-            raise ValueError("Incorrect time given! Please format the time as HH:MM:SS or HH:MM, like 23:59:59")
+            raise ValueError("Incorrect time given! Please format the time "
+                             "as HH:MM:SS or HH:MM, like 23:59:59")
 
     elif reminder_datetime.count("t") > 1:
-        raise ValueError("You should only use 'T' once! Like so: 2023-12-31T23:59+0100. "
-                         "Notice that the date and time are separated by the 'T', and "
-                         "the timezone only by the '+' or '-' sign. Not an additional 'T'. :)")
+        raise ValueError(
+            "You should only use 'T' once! Like so: 2023-12-31T23:59+0100. "
+            "Notice that the date and time are separated by the 'T', and "
+            "the timezone only by the '+' or '-' sign. Not an additional 'T'. "
+            ":)"
+        )
     else:
-        if reminder_datetime.count("-") != 2:  # should contain two dashes for YYYY-MM-DD
-            raise ValueError("Incorrect date given! Please format the date as YYYY-MM-DD, like 2023-12-31")
+        if reminder_datetime.count("-") != 2:
+            # should contain two dashes for YYYY-MM-DD
+            raise ValueError("Incorrect date given! Please format the date "
+                             "as YYYY-MM-DD, like 2023-12-31")
         mode = TimestampFormats.DateNoTime
 
     # error for unimplemented: giving date and time format without a timezone
     if not has_timezone and mode != TimestampFormats.DateNoTime:
-        raise ValueError("Because I don't know your timezone, I can't ensure it'll be sent at the right time. "
-                         "Please add the timezone like so '-0100' or '+0900'.")
+        raise ValueError(
+            "Because I don't know your timezone, I can't ensure it'll be "
+            "sent at the right time. Please add the timezone like so "
+            "'-0100' or '+0900'."
+        )
 
     # convert given time string to valid datetime
-    timestamp_format = ["%Y-%m-%dt%H:%M:%S%z", "%Y-%m-%dt%H:%M%z", "%Y-%m-%d"][mode.value]
+    timestamp_format = [
+        "%Y-%m-%dt%H:%M:%S%z",
+        "%Y-%m-%dt%H:%M%z",
+        "%Y-%m-%d"
+    ][mode.value]
     try:
         timestamp = datetime.strptime(reminder_datetime, timestamp_format)
     except ValueError:
         raise ValueError(
-            f"Incorrect format given! I could not convert {reminder_datetime} to format {timestamp_format}")
+            f"Incorrect format given! I could not convert "
+            f"{reminder_datetime} to format {timestamp_format}"
+        )
 
     # todo: move the above code to a new function
 
@@ -216,14 +273,23 @@ async def _handle_reminder_timestamp_parsing(
     return distance, itx
 
 
-async def _parse_reminder_time(itx: discord.Interaction[Bot], reminder_datetime: str) -> tuple[datetime, datetime]:
+async def _parse_reminder_time(
+        itx: discord.Interaction[Bot],
+        reminder_datetime: str
+) -> tuple[datetime, datetime]:
+    # todo: make followup message a separate method, and initiate it by
+    #  making this function raise a specific exception.
     """
-    Parse a datetime string: relative to today (2d 11h); a ISO8601 timestamp; or a unix timestamp. It outputs
-    the reminder's datetime and interaction's creation time with timezone awareness. If the ISO timestamp gives
-    a date but no time, it will prompt the user to select a time of day, and then returns the respective datetime.
+    Parse a datetime string: relative to today (2d 11h); a ISO8601
+    timestamp; or a unix timestamp. It outputs the reminder's datetime
+    and interaction's creation time with timezone awareness. If the ISO
+    timestamp gives a date but no time, it will prompt the user to
+    select a time of day, and then returns the respective datetime.
 
-    :param itx: The interaction to interpret as creation time, and with which to send a TimeOfDaySelection view.
-    :param reminder_datetime: The string to interpret and convert into the datetime for the reminder.
+    :param itx: The interaction to interpret as creation time, and with
+     which to send a TimeOfDaySelection view.
+    :param reminder_datetime: The string to interpret and convert into
+     the datetime for the reminder.
 
     :return: A tuple of (reminder datetime, interaction creation time)
 
@@ -233,15 +299,25 @@ async def _parse_reminder_time(itx: discord.Interaction[Bot], reminder_datetime:
     :raise MissingQuantityException:
     :raise MissingUnitException:
     :raise ReminderTimeSelectionMenuTimeOutException:
-    """
+    """  # todo: update docstring exceptions
     # Parse reminder input to get a datetime for the reminder scheduler
     creation_time = itx.created_at  # utc
-    assert creation_time.tzinfo == timezone.utc  # it is timezone aware, in utc
+    assert creation_time.tzinfo == timezone.utc
+    # ^ it is timezone-aware, in utc
     distance: datetime
     try:
-        possible_timestamp_datetime = reminder_datetime.replace(" 6:  # 1000000 = 20 Jan 1970
-            distance = datetime.fromtimestamp(int(possible_timestamp_datetime), timezone.utc)
+        possible_timestamp_datetime = (reminder_datetime
+                                       .replace(" 6):
+            # length of 6: 1000000 = 20 Jan 1970
+            # Safe bet it's a unix timestamp, not someone accidentally
+            #  pressing 7 digits.
+            distance = datetime.fromtimestamp(
+                int(possible_timestamp_datetime),
+                timezone.utc
+            )
             if distance < creation_time:
                 raise UnixTimestampInPastException(distance, creation_time)
         else:
@@ -254,11 +330,15 @@ async def _parse_reminder_time(itx: discord.Interaction[Bot], reminder_datetime:
             distance = TimeParser.parse_date(reminder_datetime, creation_time)
     except ValueError:
         try:
-            distance, itx = await _handle_reminder_timestamp_parsing(itx, reminder_datetime)
+            distance, itx = await _handle_reminder_timestamp_parsing(
+                itx, reminder_datetime)
             time_passed = distance - creation_time
             if time_passed > timedelta(days=365 * 3999):
-                raise ValueError("I don't think I can remind you `{}` years into the future..."
-                                 .format(time_passed.days // 365.2425))
+                raise ValueError(
+                    "I don't think I can remind you `{}` years into "
+                    "the future..."
+                    .format(time_passed.days // 365.2425)
+                )
         except ValueError as ex:
             raise TimestampParseException(ex)
 
@@ -272,31 +352,49 @@ async def _create_reminder(
         db_data: list[ReminderDict],
         from_copy: bool = False
 ):
-    reminder_object = ReminderObject(itx.client, creation_time, distance, itx.user.id, reminder, db_data)
+    reminder_object = ReminderObject(
+        itx.client,
+        creation_time,
+        distance,
+        itx.user.id,
+        reminder,
+        db_data
+    )
     _distance = int(distance.timestamp())
     cmd_mention = itx.client.get_command_mention("reminder reminders")
     view = ShareReminder()
     if from_copy:
         # send message without view.
         await itx.response.send_message(
-            f"Successfully created a reminder for you on  for \"{reminder}\"!\n"
+            f"Successfully created a reminder for you on  "
+            f"for \"{reminder}\"!\n"
             f"Use {cmd_mention} to see your list of reminders",
             ephemeral=True
         )
         return
     else:
         await itx.response.send_message(
-            f"Successfully created a reminder for you on  for \"{reminder}\"!\n"
+            f"Successfully created a reminder for you on  "
+            f"for \"{reminder}\"!\n"
             f"Use {cmd_mention} to see your list of reminders",
             view=view, ephemeral=True
         )
 
     await view.wait()
     if view.value == 1:
-        msg = f"{itx.user.mention} shared a reminder on  for \"{reminder}\""
-        copy_view = CopyReminder(_create_reminder, reminder_object, timeout=300)
+        msg = (f"{itx.user.mention} shared a reminder on  "
+               f"for \"{reminder}\"")
+        copy_view = CopyReminder(
+            _create_reminder,
+            reminder_object,
+            timeout=300
+        )
         try:
-            await itx.channel.send(content=msg, view=copy_view, allowed_mentions=discord.AllowedMentions.none())
+            await itx.channel.send(
+                content=msg,
+                view=copy_view,
+                allowed_mentions=discord.AllowedMentions.none()
+            )
         except discord.errors.Forbidden:
             await view.return_interaction.response.send_message(
                 msg, view=copy_view,
diff --git a/extensions/reminders/views/copyreminder.py b/extensions/reminders/views/copyreminder.py
index 9faaa80..8044b35 100644
--- a/extensions/reminders/views/copyreminder.py
+++ b/extensions/reminders/views/copyreminder.py
@@ -13,29 +13,40 @@
 
 
 class CopyReminder(discord.ui.View):
-    def __init__(self, create_reminder_callback, reminder: ReminderObject, timeout=300):
+    def __init__(
+            self,
+            create_reminder_callback,  # todo: add type
+            reminder: ReminderObject,
+            timeout=300
+    ):
         super().__init__()
         self.timeout = timeout
         self.return_interaction: discord.Interaction | None = None
         self.reminder = reminder
 
         # required to prevent circular imports ;-;
-        # AI suggested this to me lol. It's probably the easiest way to fix it.
+        # AI suggested this to me lol. It's probably the easiest
+        #  way to fix it.
         self.create_reminder_callback = create_reminder_callback
 
-        self.add_item(create_simple_button("I also want to be reminded!",
-                                           discord.ButtonStyle.gray,
-                                           self.button_callback))
+        self.add_item(create_simple_button(
+            "I also want to be reminded!",
+            discord.ButtonStyle.gray,
+            self.button_callback
+        ))
 
     async def button_callback(self, itx: discord.Interaction):
-        # Check if user has too many reminders (max 50 allowed (internally chosen limit))
+        # Check if user has too many reminders
+        #  (max 50 allowed (internally chosen limit))
         user_reminders = get_user_reminders(itx.client, itx.user)
         if len(user_reminders) > 50:
             cmd_mention = itx.client.get_command_mention("reminder reminders")
             cmd_mention1 = itx.client.get_command_mention("reminder remove")
-            await itx.response.send_message(f"You already have more than 50 reminders! Use {cmd_mention} to see "
-                                            f"your reminders, and use {cmd_mention1} `item: ` to remove a reminder",
-                                            ephemeral=True)
+            await itx.response.send_message(
+                f"You already have more than 50 reminders! Use {cmd_mention} "
+                f"to see your reminders, and use {cmd_mention1} `item: ` to "
+                f"remove a reminder",
+                ephemeral=True)
             return
         if self.reminder.remindertime < itx.created_at.astimezone():
             cmd_mention = itx.client.get_command_mention("reminder remindme")
diff --git a/extensions/reminders/views/sharereminder.py b/extensions/reminders/views/sharereminder.py
index 9af2b80..ec88b61 100644
--- a/extensions/reminders/views/sharereminder.py
+++ b/extensions/reminders/views/sharereminder.py
@@ -9,7 +9,11 @@ def __init__(self, timeout=300):
         self.value = 0
         self.timeout = timeout
         self.return_interaction: discord.Interaction | None = None
-        self.add_item(create_simple_button("Share reminder in chat", discord.ButtonStyle.gray, self.callback))
+        self.add_item(create_simple_button(
+            "Share reminder in chat",
+            discord.ButtonStyle.gray,
+            self.callback)
+        )
 
     async def callback(self, interaction: discord.Interaction):
         self.value = 1
diff --git a/extensions/reminders/views/timeofdayselection.py b/extensions/reminders/views/timeofdayselection.py
index 0e22cf6..4888fd0 100644
--- a/extensions/reminders/views/timeofdayselection.py
+++ b/extensions/reminders/views/timeofdayselection.py
@@ -15,9 +15,14 @@ def __init__(self, options: list[str], timeout=180):
             def callback(itx):  # pass the button label to the callback
                 return self.callback(itx, option)
 
-            self.add_item(create_simple_button(option, discord.ButtonStyle.green, callback))
+            self.add_item(create_simple_button(
+                option, discord.ButtonStyle.green, callback))
 
-    async def callback(self, interaction: discord.Interaction[Bot], label: str):
+    async def callback(
+            self,
+            interaction: discord.Interaction[Bot],
+            label: str
+    ):
         self.value = label
         self.return_interaction = interaction
         self.stop()
diff --git a/extensions/settings/cogs/settings.py b/extensions/settings/cogs/settings.py
index 7fc4d00..ce48def 100644
--- a/extensions/settings/cogs/settings.py
+++ b/extensions/settings/cogs/settings.py
@@ -157,7 +157,8 @@ async def _value_autocomplete(
                     results.append(app_commands.Choice(name=guild.name,
                                                        value=str(guild.id)))
         elif issubclass(attribute_type, discord.User):
-            # Note: discord.User is a subclass of discord.abc.Messageable, so should be tested before that too.
+            # Note: discord.User is a subclass of discord.abc.Messageable,
+            #  so should be tested before that too.
             # iterate guild members
             for member in itx.guild.members:
                 if (current.lower() in member.name.lower()
@@ -646,8 +647,10 @@ def __init__(self):
     )
     #
     # @app_commands.check(is_admin_check)
-    # @migrate_group.command(name="database",
-    #                        description="Migrate bot settings to new database.")
+    # @migrate_group.command(
+    #     name="database",
+    #     description="Migrate bot settings to new database."
+    # )
     # async def database(
     #         self,
     #         itx: discord.Interaction
@@ -656,7 +659,8 @@ def __init__(self):
     #     await ServerSettings.migrate(itx.client.async_rina_db)
     #     await itx.response.send_message(
     #         "Successfully migrated databases.", ephemeral=True)
-    #     itx.client.server_settings = await ServerSettings.fetch_all(itx.client)
+    #     itx.client.server_settings = await ServerSettings.fetch_all(
+    #         itx.client)
     #     await itx.edit_original_response(
     #         content="Migrated databases and re-fetched all server settings.")
     #
diff --git a/extensions/settings/objects/server_settings.py b/extensions/settings/objects/server_settings.py
index f8e8af9..a41e8cc 100644
--- a/extensions/settings/objects/server_settings.py
+++ b/extensions/settings/objects/server_settings.py
@@ -269,14 +269,19 @@ def get_name_or_id_maybe(attribute1: T) -> T | NameAndIdData:
         return get_name_or_id_maybe(attribute)
 
     @staticmethod
-    async def get_entry(async_rina_db: motor.core.AgnosticDatabase, guild_id: int) -> ServerSettingData | None:
+    async def get_entry(
+            async_rina_db: motor.core.AgnosticDatabase,
+            guild_id: int
+    ) -> ServerSettingData | None:
         """
         Retrieve a database entry for the given guild ID.
 
-        :param async_rina_db: The database with which to retrieve the entry.
-        :param guild_id: The guild id of the guild to retrieve the entry for.
-
-        :return A ServerSettingData or None if there is no entry for the given guild.
+        :param async_rina_db: The database with which to retrieve
+         the entry.
+        :param guild_id: The guild id of the guild to retrieve the
+         entry for.
+        :return A ServerSettingData or None if there is no entry for
+         the given guild.
         """
         collection = async_rina_db[ServerSettings.DATABASE_KEY]
         query = {"guild_id": guild_id}
@@ -286,16 +291,20 @@ async def get_entry(async_rina_db: motor.core.AgnosticDatabase, guild_id: int) -
     # @staticmethod
     # async def migrate(async_rina_db: motor.core.AgnosticDatabase):
     #     """
-    #     Migrate all data from the old guildInfo database to the new server_settings database.
+    #     Migrate all data from the old guildInfo database to the new
+    #     server_settings database.
     #
-    #     :param async_rina_db: The database to reference to look up the old and store the new database.
-    #     :raise IndexError: No online database of the old version was found.
+    #     :param async_rina_db: The database to reference to look up the
+    #      old and store the new database.
+    #     :raise IndexError: No online database of the old version
+    #      was found.
     #     """
     #     old_collection = async_rina_db["guildInfo"]
     #     new_collection = async_rina_db[ServerSettings.DATABASE_KEY]
     #     new_settings = []
     #     async for old_setting in old_collection.find():
-    #         guild_id, attributes = convert_old_settings_to_new(old_setting)
+    #         guild_id, attributes = convert_old_settings_to_new(
+    #             old_setting)
     #         new_setting = ServerSettingData(
     #             guild_id=guild_id,
     #             attribute_ids=attributes,
@@ -314,7 +323,10 @@ async def set_attribute(
             value: Any
     ) -> tuple[bool, bool]:
         if "." in parameter or parameter.startswith("$"):
-            raise ValueError(f"Parameters are not allowed to contain '.' or start with '$'! (parameter: '{parameter}')")
+            raise ValueError(
+                f"Parameters are not allowed to contain '.' or "
+                f"start with '$'! (parameter: '{parameter}')"
+            )  # todo: check if i sanitize the input when responding.
         if parameter not in ServerAttributeIds.__annotations__:
             raise KeyError(f"'{parameter}' is not a valid Server Attribute.")
 
@@ -334,10 +346,14 @@ async def remove_attribute(
             parameter: str
     ) -> tuple[bool, bool]:
         if "." in parameter or parameter.startswith("$"):
-            raise ValueError(f"Parameters are not allowed to contain '.' or start with '$'! (parameter: '{parameter}')")
+            raise ValueError(
+                f"Parameters are not allowed to contain '.' or "
+                f"start with '$'! (parameter: '{parameter}')"
+            )  # todo: check if i sanitize the input when responding.
         collection = async_rina_db[ServerSettings.DATABASE_KEY]
         query = {"guild_id": guild_id}
-        update = {"$unset": {f"attribute_ids.{parameter}": ""}}  # value "" is not used by MongoDB when unsetting.
+        update = {"$unset": {f"attribute_ids.{parameter}": ""}}
+        # value ("") is not used by MongoDB when unsetting.
 
         result = await collection.update_one(query, update, upsert=True)
         return result.modified_count > 0, result.did_upsert
@@ -350,21 +366,26 @@ async def set_module_state(
             value: bool
     ) -> tuple[bool, bool]:
         """
-        Set the state of the given module
+        Set the state of the given module.
 
         :param async_rina_db: The database to edit the module state.
-        :param guild_id: The id of the guild whose module state you want to change/
+        :param guild_id: The id of the guild whose module state you want
+         to change.
         :param module: The name of the module to set.
         :param value: The (new) value of the module.
-
-        :return: A tuple of booleans: whether any documents were changed, and whether any new documents were created.
+        :return: A tuple of booleans: whether any documents were
+         changed, and whether any new documents were created.
         """
         if "." in module or module.startswith("$"):
-            raise ValueError(f"Parameters are not allowed to contain '.' or start with '$'! (parameter: '{module}')")
+            raise ValueError(f"Parameters are not allowed to contain '.'"
+                             f" or start with '$'! (parameter: '{module}')")
         if module not in EnabledModules.__annotations__:
             raise KeyError(f"'{module}' is not a valid Module.")
         if type(value) is not bool:
-            raise TypeError(f"'{module}' must be a boolean, not '{type(value).__name__}'.")
+            raise TypeError(
+                f"'{module}' must be a boolean, not "
+                f"'{type(value).__name__}'."
+            )
 
         collection = async_rina_db[ServerSettings.DATABASE_KEY]
         query = {"guild_id": guild_id}
@@ -378,9 +399,13 @@ async def set_module_state(
     @staticmethod
     async def fetch_all(client: Bot) -> dict[int, ServerSettings]:
         """
-        Load all server settings from database and format into a ServerSettings object.
-        :param client: The bot to use to retrieve matching attribute objects from ids, and for async_rina_db
-        :return: A dictionary of guild_id and a tuple of the server's enabled modules and attributes.
+        Load all server settings from database and format into a
+        ServerSettings object.
+
+        :param client: The bot to use to retrieve matching attribute
+         objects from ids, and for async_rina_db
+        :return: A dictionary of guild_id and a tuple of the server's
+         enabled modules and attributes.
         """
         collection = client.async_rina_db[ServerSettings.DATABASE_KEY]
         settings_data = collection.find()
@@ -392,22 +417,29 @@ async def fetch_all(client: Bot) -> dict[int, ServerSettings]:
                 server_setting = ServerSettings.load(client, setting)
                 server_settings[server_setting.guild.id] = server_setting
             except ParseError as ex:
-                warnings.warn(f"ParseError for {setting["guild_id"]}: " + ex.message)
+                warnings.warn(
+                    f"ParseError for {setting["guild_id"]}: "
+                    + ex.message
+                )
 
         return server_settings
 
     @staticmethod
     async def fetch(client: Bot, guild_id: int) -> ServerSettings:
         """
-        Load a given guild_id's settings from database and format into a ServerSettings object.
+        Load a given guild_id's settings from database and format into
+        a ServerSettings object.
 
-        :param client: The bot to use to retrieve matching attributes from ids, and for async_rina_db
+        :param client: The bot to use to retrieve matching attributes
+         from ids, and for async_rina_db.
         :param guild_id: The guild_id to look up.
 
-        :return: A ServerSettings object, corresponding to the given guild_id.
+        :return: A ServerSettings object, corresponding to the
+         given guild_id.
 
         :raise KeyError: If the given guild_id has no data yet.
-        :raise ParseError: If values from the database could not be parsed.
+        :raise ParseError: If values from the database could not
+         be parsed.
         """
         result = await ServerSettings.get_entry(client.async_rina_db, guild_id)
         if result is None:
@@ -416,28 +448,38 @@ async def fetch(client: Bot, guild_id: int) -> ServerSettings:
         return ServerSettings.load(client, result)
 
     @staticmethod
-    def load(client: discord.Client, settings: ServerSettingData) -> ServerSettings:
+    def load(
+            client: discord.Client,
+            settings: ServerSettingData
+    ) -> ServerSettings:
         """
-        Load all server settings from database and format into a ServerSettings object.
+        Load all server settings from database and format into a
+        ServerSettings object.
 
-        :param client: The client to use to retrieve matching attribute objects from ids.
+        :param client: The client to use to retrieve matching attribute
+         objects from ids.
         :param settings: The settings object to load.
 
-        :return A ServerSettings object with the setting's retrieved guild, enabled modules, and attributes.
+        :return A ServerSettings object with the setting's retrieved
+         guild, enabled modules, and attributes.
 
-        :raise ParseError: If attributes in the data object could not be parsed.
+        :raise ParseError: If attributes in the data object could not
+         be parsed.
         """
         guild_id = settings["guild_id"]
         enabled_modules = settings["enabled_modules"]
         attribute_ids = ServerAttributeIds(**settings["attribute_ids"])
-        guild, attributes = ServerSettings.get_attributes(client, guild_id, attribute_ids)
+        guild, attributes = ServerSettings.get_attributes(
+            client, guild_id, attribute_ids
+        )
         return ServerSettings(
             guild=guild,
             enabled_modules=enabled_modules,
             attributes=attributes
         )
 
-    # todo: perhaps a repair function to remove unknown/migrated keys from the database?
+    # todo: perhaps a repair function to remove unknown/migrated keys
+    #  from the database?
 
     @staticmethod
     def get_attributes(
@@ -446,15 +488,20 @@ def get_attributes(
             attributes: ServerAttributeIds
     ) -> tuple[discord.Guild, ServerAttributes]:
         """
-        Load the guild and all attributes from the given ids, using the given client.
+        Load the guild and all attributes from the given ids, using
+        the given client.
 
-        :param client: The client to use to retrieve matching attribute objects from ids.
-        :param guild_id: The guild id of the guild of the server attributes.
+        :param client: The client to use to retrieve matching attribute
+         objects from ids.
+        :param guild_id: The guild id of the guild of the server
+         attributes.
         :param attributes: The ids of the attributes to load.
 
-        :return A tuple of the loaded guild and its attributes, corresponding with the given ids.
+        :return A tuple of the loaded guild and its attributes,
+         corresponding with the given ids.
 
-        :raise ParseError: The given ServerAttributeIds contains ids that can't be converted to their corresponding
+        :raise ParseError: The given ServerAttributeIds contains
+         ids that can't be converted to their corresponding
          ServerAttributes object.
         """
         invalid_arguments: dict[str, str | list[str]] = {}
diff --git a/extensions/staffaddons/cogs/staffaddons.py b/extensions/staffaddons/cogs/staffaddons.py
index c5aaa08..1f8299b 100644
--- a/extensions/staffaddons/cogs/staffaddons.py
+++ b/extensions/staffaddons/cogs/staffaddons.py
@@ -1,6 +1,8 @@
 from datetime import datetime, timedelta
-# ^ for /delete_week_selfies (within 7 days), and /version startup time parsing to discord unix 
-import requests  # to fetch from GitHub and see Rina is running the latest version
+# ^ for /delete_week_selfies (within 7 days), and /version startup
+#  time parsing to discord unix 
+import requests
+# to fetch from GitHub and see Rina is running the latest version
 
 import discord
 import discord.app_commands as app_commands
@@ -8,9 +10,15 @@
 
 from extensions.settings.objects import ModuleKeys, AttributeKeys
 from resources.customs import Bot
-from resources.checks.permissions import is_staff  # to check if messages in the selfies channel were sent by staff
-from resources.checks import is_staff_check, module_enabled_check, MissingAttributesCheckFailure
-from resources.utils.utils import log_to_guild  # logging when a staff command is used
+from resources.checks.permissions import is_staff
+# ^ to check if messages in the selfies channel were sent by staff
+from resources.checks import (
+    is_staff_check,
+    module_enabled_check,
+    MissingAttributesCheckFailure
+)
+from resources.utils.utils import log_to_guild
+# ^ logging when a staff command is used
 
 
 class StaffAddons(commands.Cog):
@@ -18,40 +26,61 @@ def __init__(self):
         pass
 
     @app_commands.check(is_staff_check)
-    @app_commands.command(name="say", description="Force Rina to repeat your wise words")
+    @app_commands.command(name="say",
+                          description="Force Rina to repeat your wise words")
     @app_commands.describe(text="What will you make Rina repeat?",
                            reply_to_interaction="Show who sent the message?")
-    async def say(self, itx: discord.Interaction[Bot], text: str, reply_to_interaction: bool = False):
+    async def say(
+            self,
+            itx: discord.Interaction[Bot],
+            text: str,
+            reply_to_interaction: bool = False
+    ):
         if reply_to_interaction:
-            await itx.response.send_message(text, ephemeral=False, allowed_mentions=discord.AllowedMentions.none())
+            await itx.response.send_message(
+                text,
+                ephemeral=False,
+                allowed_mentions=discord.AllowedMentions.none()
+            )
             return
 
         await log_to_guild(
             itx.client,
             itx.guild,
-            f"{itx.user.nick or itx.user.name} ({itx.user.id}) said a message using Rina: {text}",
+            (f"{itx.user.nick or itx.user.name} ({itx.user.id})"
+             f" said a message using Rina: {text}"),
             crash_if_not_found=True,
             ignore_dms=True
         )
         try:
-            text = text.replace("[[\\n]]", "\n").replace("[[del]]", "")
-            await itx.channel.send(f"{text}",
-                                   allowed_mentions=discord.AllowedMentions(everyone=False, users=True, roles=True,
-                                                                            replied_user=True))
+            text = (text
+                    .replace("[[\\n]]", "\n")
+                    .replace("[[del]]", ""))
+            await itx.channel.send(
+                f"{text}",
+                allowed_mentions=discord.AllowedMentions(
+                    everyone=False, users=True, roles=True, replied_user=True)
+            )
         except discord.Forbidden:
             await itx.response.send_message(
-                "Forbidden: I'm not allowed to send a message in this channel.",
+                "Forbidden: I'm not allowed to send a message in "
+                "this channel.",
                 ephemeral=True)
             return
-        # No longer necessary: this gets caught by the on_app_command_error() event in the main file.
+        # No longer necessary: this gets caught by the
+        #  on_app_command_error() event in the main file.
         await itx.response.send_message("Successfully sent!", ephemeral=True)
 
     @app_commands.check(is_staff_check)
     @module_enabled_check(ModuleKeys.selfies_channel_deletion)
-    @app_commands.command(name="delete_week_selfies", description="Remove selfies and messages older than 7 days")
+    @app_commands.command(
+        name="delete_week_selfies",
+        description="Remove selfies and messages older than 7 days"
+    )
     async def delete_week_selfies(self, itx: discord.Interaction[Bot]):
-        # This function largely copies the built-in channel.purge() function with a check, but is more fancy by
-        # offering a sort of progress update every 50-100 messages :D
+        # This function largely copies the built-in channel.purge()
+        #  function with a check, but is more fancy by offering a
+        #  sort of progress update every 50-100 messages :D
         selfies_channel = itx.client.get_guild_attribute(
             itx.guild, AttributeKeys.selfies_channel)
         if selfies_channel is None:
@@ -65,76 +94,108 @@ async def delete_week_selfies(self, itx: discord.Interaction[Bot]):
 
         await itx.response.send_message(output + "...", ephemeral=True)
 
-        await log_to_guild(itx.client, itx.guild,
-                           f"{itx.user} ({itx.user.id}) deleted messages older than 7 days, in "
-                           f"{itx.channel.mention} ({itx.channel.id}).")
+        await log_to_guild(
+            itx.client,
+            itx.guild,
+            f"{itx.user} ({itx.user.id}) deleted messages older "
+            f"than 7 days, in {itx.channel.mention} ({itx.channel.id})."
+        )
         message_delete_count: int = 0
         queued_message_deletions: list[discord.Message] = []
-        # current ephemeral message's count content (status of deleting messages)
+        # current ephemeral message's count content
+        #  (status of deleting messages)
         feedback_output_count_status: int = 0
-        async for message in itx.channel.history(limit=None,
-                                                 before=(datetime.now().astimezone() -
-                                                         timedelta(days=6, hours=23, minutes=30)),
-                                                 oldest_first=True):
+        async for message in itx.channel.history(
+                limit=None,
+                before=(datetime.now().astimezone()
+                        - timedelta(days=6, hours=23, minutes=30)),
+                oldest_first=True
+        ):
             message_date = int(message.created_at.timestamp())
-            if "[info]" in message.content.lower() and is_staff(itx, message.author):
+            if ("[info]" in message.content.lower()
+                    and is_staff(itx, message.author)):
                 continue
             if time_now - message_date > 14 * 86400:
                 # 14 days, too old to remove by bulk
                 message_delete_count += 1
                 await message.delete()
             elif time_now - message_date > 7 * 86400:
-                # 7 days ; technically redundant due to loop's "before" kwarg, but better safe than sorry
+                # 7 days ; technically redundant due to loop's
+                #  "before" kwarg, but better safe than sorry
                 queued_message_deletions.append(message)
                 if message_delete_count - feedback_output_count_status >= 50:
-                    feedback_output_count_status = message_delete_count - message_delete_count % 10  # round to 10s
+                    feedback_output_count_status = (
+                        message_delete_count - message_delete_count % 10)
+                    # ^ round to 10s
                     try:
                         await itx.edit_original_response(
-                            content=output + f"\nRemoved {message_delete_count} messages older than 7 days "
-                                             f"in {itx.channel.mention} so far...")
+                            content=output
+                            + f"\nRemoved {message_delete_count} "
+                              f"messages older than 7 days in "
+                              f"{itx.channel.mention} so far..."
+                        )
                     except discord.errors.HTTPException:
                         pass  # ephemeral message timed out or something..
 
             if len(queued_message_deletions) >= 100:
                 # can only bulk delete up to 100 msgs
                 message_delete_count += len(queued_message_deletions[:100])
-                await itx.channel.delete_messages(queued_message_deletions[:100],
-                                                  reason="Delete selfies older than 7 days")
+                await itx.channel.delete_messages(
+                    queued_message_deletions[:100],
+                    reason="Delete selfies older than 7 days"
+                )
                 queued_message_deletions = queued_message_deletions[100:]
 
         if queued_message_deletions:
-            message_delete_count += len(queued_message_deletions)  # count remaining messages
-            await itx.channel.delete_messages(queued_message_deletions,
-                                              reason="Delete selfies older than 7 days")  # delete last few messages
+            # count remaining messages
+            message_delete_count += len(queued_message_deletions)
+            # delete last few messages
+            await itx.channel.delete_messages(
+                queued_message_deletions,
+                reason="Delete selfies older than 7 days"
+            )
 
         try:
-            await itx.channel.send(f"Removed {message_delete_count} messages older than 7 days!")
+            await itx.channel.send(
+                f"Removed {message_delete_count} messages older"
+                f" than 7 days!"
+            )
         except discord.Forbidden:
-            await itx.followup.send(f"Removed {message_delete_count} messages older than 7 days!", ephemeral=False)
+            await itx.followup.send(
+                f"Removed {message_delete_count} messages older"
+                f" than 7 days!",
+                ephemeral=False
+            )
 
     @app_commands.command(name="version", description="Get bot version")
     async def get_bot_version(self, itx: discord.Interaction[Bot]):
         # get most recently pushed bot version
+        # noinspection LongLine
         latest_rina = requests.get("https://raw.githubusercontent.com/TransPlace-Devs/uncute-rina/main/main.py").text
-        latest_version = latest_rina.split("BOT_VERSION = \"", 1)[1].split("\"", 1)[0]
+        latest_version = (latest_rina
+                          .split("BOT_VERSION = \"", 1)[1]
+                          .split("\"", 1)[0])
         unix = int(itx.client.startup_time.timestamp())
         for i in range(len(latest_version.split("."))):
-            if int(latest_version.split(".")[i]) > int(itx.client.version.split(".")[i]):
+            if (int(latest_version.split(".")[i])
+                    > int(itx.client.version.split(".")[i])):
                 await itx.response.send_message(
-                    f"Bot is currently running on v{itx.client.version} (latest: v{latest_version})\n"
+                    f"Bot is currently running on v{itx.client.version} "
+                    f"(latest: v{latest_version})\n"
                     f"(started  at )",
                     ephemeral=False)
                 return
         else:
             await itx.response.send_message(
-                f"Bot is currently running on v{itx.client.version} (latest)\n(started  at )",
+                f"Bot is currently running on v{itx.client.version} (latest)\n"
+                f"(started  at )",
                 ephemeral=False)
 
     @app_commands.check(is_staff_check)
     @app_commands.command(name="update", description="Update slash-commands")
-    async def update_command_tree(self, itx: discord.Interaction):
-        itx.response: discord.InteractionResponse  # noqa
-        itx.followup: discord.Webhook  # noqa
+    async def update_command_tree(self, itx: discord.Interaction[Bot]):
+        itx.response: discord.InteractionResponse[Bot]  # type: ignore
+        itx.followup: discord.Webhook  # type: ignore
         await itx.response.defer(ephemeral=True)
         await itx.client.tree.sync()
         itx.client.commandList = await itx.client.tree.fetch_commands()
diff --git a/extensions/starboard/cogs/starboard.py b/extensions/starboard/cogs/starboard.py
index ea808c6..eae5e1e 100644
--- a/extensions/starboard/cogs/starboard.py
+++ b/extensions/starboard/cogs/starboard.py
@@ -5,7 +5,8 @@
 from resources.checks import MissingAttributesCheckFailure
 from resources.customs import Bot
 from resources.utils.discord_utils import get_or_fetch_channel
-from resources.utils.utils import log_to_guild  # to log starboard addition/removal
+from resources.utils.utils import log_to_guild
+# ^ to log starboard addition/removal
 
 from extensions.starboard.local_starboard import (
     add_to_local_starboard,
@@ -21,29 +22,39 @@
 
 
 async def _fetch_starboard_original_message(
-        client: Bot, starboard_message: discord.Message, starboard_emoji: discord.Emoji
+        client: Bot,
+        starboard_message: discord.Message,
+        starboard_emoji: discord.Emoji
 ) -> discord.Message | None:
     """
-    Uses the 'jump to original' link in a starboard message to fetch its original author's message.
+    Uses the 'jump to original' link in a starboard message to fetch
+    its original author's message.
 
     :param client: The bot, to get the correct logging channel.
-    :param starboard_message: The starboard message to get the original message of.
-    :param starboard_emoji: The starboard upvote emoji, used in logging messages.
+    :param starboard_message: The starboard message to get the original
+     message of.
+    :param starboard_emoji: The starboard upvote emoji, used in
+     logging messages.
 
-    :return: The original author's message, or None if it was not found (``Forbidden`` or ``NotFound``).
+    :return: The original author's message, or None if it was not
+     found (``Forbidden`` or ``NotFound``).
 
     .. note::
 
-        If the original message was deleted (``NotFound``), it deletes its starboard message too.
+        If the original message was deleted (``NotFound``), it deletes
+        its starboard message too.
     """
     # find original message
     try:
         guild_id, channel_id, message_id = \
             parse_starboard_message(starboard_message)
     except (ValueError, IndexError) as ex:
-        await log_to_guild(client, starboard_message.guild,
-                           f"{starboard_emoji} :x: Starboard message {starboard_message.id} was ignored "
-                           f"({ex})")
+        await log_to_guild(
+            client,
+            starboard_message.guild,
+            f"{starboard_emoji} :x: Starboard message {starboard_message.id} "
+            f"was ignored ({ex})"
+        )
         return None
     ch = client.get_channel(channel_id)
 
@@ -51,8 +62,10 @@ async def _fetch_starboard_original_message(
         await log_to_guild(
             client,
             starboard_message.guild,
-            f":warning: Couldn't find starboard channel from starboard message!\n"
-            f"starboard message: {starboard_message.channel.id}/{starboard_message.id}\n"
+            f":warning: Couldn't find starboard channel from starboard "
+            f"message!\n"
+            f"starboard message: "
+            f"{starboard_message.channel.id}/{starboard_message.id}\n"
             f"recovered channel id: {channel_id}"
         )
         return None
@@ -93,6 +106,7 @@ async def _send_starboard_message(
         description=f"{message.content}",
         timestamp=message.created_at  # this, or datetime.now()
     )
+    # noinspection LongLine
     msg_link = f"https://discord.com/channels/{message.guild.id}/{message.channel.id}/{message.id}"
     embed.add_field(name="Source", value=f"[Jump!]({msg_link})")
     embed.set_footer(text=f"{message.id}")
@@ -109,12 +123,14 @@ async def _send_starboard_message(
     embed_list = []
     for attachment in message.attachments:
         try:
-            if attachment.content_type.split("/")[0] == "image":  # is image or GIF
+            if attachment.content_type.split("/")[0] == "image":
+                # attachment is an image or GIF
                 if len(embed_list) == 0:
                     embed.set_image(url=attachment.url)
                     embed_list = [embed]
                 else:
-                    # can only set one image per embed... But you can add multiple embeds :]
+                    # You can only set one image per embed... But you
+                    #  can add multiple embeds :]
                     embed = discord.Embed(
                         color=discord.Colour.from_rgb(r=255, g=172, b=51),
                     )
@@ -125,26 +141,34 @@ async def _send_starboard_message(
                     embed.set_field_at(
                         0,
                         name=embed.fields[0].name,
-                        value=embed.fields[0].value + f"\n\n(⚠️ +1 Unknown attachment "
-                                                      f"({attachment.content_type}))")
+                        value=embed.fields[0].value
+                        + f"\n\n(⚠️ +1 Unknown attachment "
+                          f"({attachment.content_type}))"
+                    )
                 else:
                     embed_list[0].set_field_at(
                         0,
                         name=embed_list[0].fields[0].name,
-                        value=embed_list[0].fields[0].value + f"\n\n(⚠️ +1 Unknown attachment "
-                                                              f"({attachment.content_type}))")
+                        value=embed_list[0].fields[0].value
+                        + f"\n\n(⚠️ +1 Unknown attachment "
+                          f"({attachment.content_type}))")
         except AttributeError:
-            # if it is neither an image, video, application, or recognised file type:
+            # if it is neither an image, video, application, nor
+            #  recognised file type:
             if len(embed_list) == 0:
                 embed.set_field_at(
                     0,
                     name=embed.fields[0].name,
-                    value=embed.fields[0].value + "\n\n(💔 +1 Unrecognized attachment type)")
+                    value=embed.fields[0].value
+                    + "\n\n(💔 +1 Unrecognized attachment type)"
+                )
             else:
                 embed_list[0].set_field_at(
                     0,
                     name=embed_list[0].fields[0].name,
-                    value=embed_list[0].fields[0].value + "\n\n(💔 +1 Unrecognized attachment type)")
+                    value=embed_list[0].fields[0].value
+                    + "\n\n(💔 +1 Unrecognized attachment type)"
+                )
     if len(embed_list) == 0:
         embed_list.append(embed)
 
@@ -154,12 +178,15 @@ async def _send_starboard_message(
         embeds=embed_list,
         allowed_mentions=discord.AllowedMentions.none(),
     )
+    attachment_urls = ','.join(att.url for att in message.attachments)
     await log_to_guild(client, starboard_channel.guild,
-                       f"{reaction.emoji} Starboard message {msg.jump_url} was "
-                       f"created from {message.jump_url}. "
+                       f"{reaction.emoji} Starboard message {msg.jump_url} "
+                       f"was created from {message.jump_url}. "
                        f"Content: \"\"\"{message.content[:1000]}\"\"\" and "
-                       f"attachments: {[att.url for att in message.attachments]}")
-    # add star reaction to original message to prevent message from being re-added to the starboard
+                       f"attachments: {attachment_urls}"
+                       )
+    # Add star reaction to original message to prevent message from
+    #  being re-added to the starboard.
     await msg.add_reaction(reaction.emoji)
     await msg.add_reaction("❌")
     await add_to_local_starboard(client.async_rina_db, msg, message)
@@ -234,7 +261,8 @@ async def _update_starboard_message_score(
             reaction_total = star_stat + reaction.count - reaction.me
             star_stat -= reaction.count - reaction.me
 
-    # if more x'es than stars, and more than [15] reactions, remove message
+    # if more x'es than stars, and more than [15] reactions, remove
+    #  the message.
     if star_stat < 0 and reaction_total >= downvote_init_value:
         await _delete_starboard_message(
             client, star_msg,
@@ -261,7 +289,9 @@ async def _update_starboard_message_score(
     try:
         await star_msg.edit(content=new_content, embeds=embeds)
     except discord.HTTPException as ex:
-        if ex.code == 429:  # too many requests; can't edit messages older than 1 hour more than x times an hour.
+        if ex.code == 429:
+            # Too many requests; can't edit messages older than 1 hour
+            #  more than x times an hour.
             return
         raise
 
@@ -401,8 +431,8 @@ def _get_starboard_message_data(
 async def _delete_starboard_message(
         client: Bot, starboard_message: discord.Message, reason: str) -> None:
     """
-    Handles custom starboard message deletion messages and preventing double logging messages when
-    the bot removes a starboard message.
+    Handles custom starboard message deletion messages and preventing
+    double logging messages when the bot removes a starboard message.
 
     :param client: The bot, to get the correct logging channel.
     :param starboard_message: The starboard message to delete.
@@ -411,7 +441,11 @@ async def _delete_starboard_message(
     await log_to_guild(client, starboard_message.guild, reason)
     starboard_message_ids_marked_for_deletion.append(starboard_message.id)
     await starboard_message.delete()
-    await delete_from_local_starboard(client.async_rina_db, starboard_message.guild.id, starboard_message.id)
+    await delete_from_local_starboard(
+        client.async_rina_db,
+        starboard_message.guild.id,
+        starboard_message.id
+    )
 
 
 async def _handle_starboard_create_or_update(
@@ -456,8 +490,9 @@ async def _handle_starboard_create_or_update(
             #  message to prevent duplicate entries in starboard.
             await message.add_reaction(starboard_emoji)
         except discord.errors.Forbidden:
-            # If "Reaction blocked", then maybe message author blocked Rina.
-            # Thus, I can't track if Rina added it to starboard already or not.
+            # If "Reaction blocked", then maybe message author blocked
+            #  Rina. Thus, I can't track if Rina added it to starboard
+            #  already or not.
             await log_to_guild(
                 client,
                 message.guild.id,
@@ -467,7 +502,12 @@ async def _handle_starboard_create_or_update(
             )
             return
 
-        await _send_starboard_message(client, message, starboard_channel, reaction)
+        await _send_starboard_message(
+            client,
+            message,
+            starboard_channel,
+            reaction
+        )
 
 
 class Starboard(commands.Cog):
@@ -503,22 +543,29 @@ async def on_raw_reaction_add(
         if None in (star_channel, star_minimum, channel_blacklist,
                     starboard_emoji, downvote_init_value):
             missing = [key for key, value in {
-                AttributeKeys.starboard_channel: star_channel,
-                AttributeKeys.starboard_minimum_upvote_count: star_minimum,
-                AttributeKeys.starboard_blacklisted_channels: channel_blacklist,
-                AttributeKeys.starboard_upvote_emoji: starboard_emoji,
+                AttributeKeys.starboard_channel:
+                    star_channel,
+                AttributeKeys.starboard_minimum_upvote_count:
+                    star_minimum,
+                AttributeKeys.starboard_blacklisted_channels:
+                    channel_blacklist,
+                AttributeKeys.starboard_upvote_emoji:
+                    starboard_emoji,
                 AttributeKeys.starboard_minimum_vote_count_for_downvote_delete:
-                    downvote_init_value}.items()
+                    downvote_init_value
+            }.items()
                 if value is None]
             raise MissingAttributesCheckFailure(ModuleKeys.starboard, missing)
 
         if self.client.is_me(payload.member) or \
                 (getattr(payload.emoji, "id", None) != starboard_emoji.id and
                  getattr(payload.emoji, "name", None) != "❌"):
-            # only run starboard code if the reactions tracked are actually starboard emojis (or the downvote emoji)
+            # Only run starboard code if the reactions tracked are actually
+            #  starboard emojis (or the downvote emoji).
             return
 
-        # get the message id from payload.message_id through the channel (with payload.channel_id) (oof lengthy process)
+        # get the message id from payload.message_id through the channel
+        #  (with payload.channel_id) (oof lengthy process)
         ch = self.client.get_channel(payload.channel_id)
         if ch is None:
             # channel not in cache?
@@ -526,25 +573,34 @@ async def on_raw_reaction_add(
         try:
             message = await ch.fetch_message(payload.message_id)
         except discord.errors.NotFound:
-            # likely caused by someone removing a PluralKit message by reacting with the :x: emoji.
+            # likely caused by someone removing a PluralKit message by
+            #  reacting with the :x: emoji.
 
             if payload.emoji.name == "❌":
                 return
 
+            # noinspection LongLine
             broken_link = f"https://discord.com/channels/{payload.guild_id}/{payload.channel_id}/{payload.message_id}"
             await log_to_guild(
-                self.client, self.client.get_guild(payload.guild_id),
-                f'**:warning: Warning: **Couldn\'t find channel {payload.channel_id} (<#{payload.channel_id}>) o'
-                f'r message {payload.message_id}!\n'
+                self.client,
+                self.client.get_guild(payload.guild_id),
+                f'**:warning: Warning: **Couldn\'t find channel '
+                f'{payload.channel_id} (<#{payload.channel_id}>) or '
+                f'message {payload.message_id}!\n'
                 f'Potentially broken link: {broken_link}\n'
-                f'This is likely caused by someone removing a PluralKit message by reacting with the :x: emoji.\n'
+                f'This is likely caused by someone removing a PluralKit '
+                f'message by reacting with the :x: emoji.\n'
                 f'\n'
-                f"In this case, the user reacted with a '{repr(payload.emoji)}' emoji")
+                f"In this case, the user reacted with a "
+                f"'{repr(payload.emoji)}' emoji"
+            )
             return
 
         if message.channel.id == star_channel.id:
             if not self.client.is_me(message.author):
-                return  # only needs to update the message if it's a rina starboard message of course...
+                # only needs to update the message if it's a rina
+                #  starboard message of course...
+                return
 
             # fetch original message, so we can get the original author.
             starboard_original_message: discord.Message | None = \
@@ -557,8 +613,9 @@ async def on_raw_reaction_add(
                 await _delete_starboard_message(
                     self.client,
                     message,
-                    f"{starboard_emoji} :x: Starboard message {message.id} was removed "
-                    f"(from {starboard_original_message.id}) (original author downvoted the starboard message!)"
+                    f"{starboard_emoji} :x: Starboard message {message.id} "
+                    f"was removed (from {starboard_original_message.id}) "
+                    f"(original author downvoted the starboard message!)"
                 )
                 return
 
@@ -581,7 +638,10 @@ async def on_raw_reaction_add(
                 )
 
     @commands.Cog.listener()
-    async def on_raw_reaction_remove(self, payload: discord.RawReactionActionEvent):
+    async def on_raw_reaction_remove(
+            self,
+            payload: discord.RawReactionActionEvent
+    ):
         if payload.guild_id is None:
             return
         if not self.client.is_module_enabled(payload.guild_id,
@@ -638,7 +698,10 @@ async def on_raw_reaction_remove(self, payload: discord.RawReactionActionEvent):
             )
 
     @commands.Cog.listener()
-    async def on_raw_message_delete(self, message_payload: discord.RawMessageDeleteEvent):
+    async def on_raw_message_delete(
+            self,
+            message_payload: discord.RawMessageDeleteEvent
+    ):
         # can raise discord.NotFound and discord.Forbidden.
 
         if not self.client.is_module_enabled(message_payload.guild_id,
@@ -660,16 +723,24 @@ async def on_raw_message_delete(self, message_payload: discord.RawMessageDeleteE
                 if value is None]
             raise MissingAttributesCheckFailure(ModuleKeys.starboard, missing)
 
-        if message_payload.message_id in starboard_message_ids_marked_for_deletion:  # global variable
-            # this prevents having two 'message deleted' logs for manual deletion of starboard message
-            starboard_message_ids_marked_for_deletion.remove(message_payload.message_id)
+        # noinspection LongLine
+        if message_payload.message_id in starboard_message_ids_marked_for_deletion:
+            # marked messages is a global variable
+            # this prevents having two 'message deleted' logs for
+            # manual deletion of starboard message
+            starboard_message_ids_marked_for_deletion.remove(
+                message_payload.message_id)
             return
         if message_payload.channel_id == star_channel.id:
-            # check if the deleted message is a starboard message; if so, log it at starboard message deletion
-            await log_to_guild(self.client, star_channel.guild,
-                               f"{starboard_emoji} :x: Starboard message was removed "
-                               f"(from {message_payload.message_id}) "
-                               f"(Starboard message was deleted manually).")
+            # check if the deleted message is a starboard message;
+            #  if so, log it at starboard message deletion.
+            await log_to_guild(
+                self.client,
+                star_channel.guild,
+                f"{starboard_emoji} :x: Starboard message was removed "
+                f"(from {message_payload.message_id}) "
+                f"(Starboard message was deleted manually)."
+            )
             await delete_from_local_starboard(
                 self.client.async_rina_db,
                 message_payload.guild_id,
diff --git a/extensions/starboard/local_starboard.py b/extensions/starboard/local_starboard.py
index 95cb37d..167eb93 100644
--- a/extensions/starboard/local_starboard.py
+++ b/extensions/starboard/local_starboard.py
@@ -14,7 +14,8 @@
 OriginalMessageData = tuple[OriginalChannelId, OriginalMessageId]
 DatabaseData = tuple[StarboardMessageId, OriginalChannelId, OriginalMessageId]
 
-local_starboard_index: dict[GuildId, dict[StarboardMessageId, OriginalMessageData]] = {}
+local_starboard_index: dict[
+    GuildId, dict[StarboardMessageId, OriginalMessageData]] = {}
 starboard_loaded = False
 
 
@@ -43,7 +44,8 @@ async def import_starboard_messages(
         if not client.is_me(starboard_msg.author):
             continue
         try:
-            guild_id, channel_id, message_id = parse_starboard_message(starboard_msg)
+            guild_id, channel_id, message_id \
+                = parse_starboard_message(starboard_msg)
         except (ValueError, IndexError):
             continue
         orig_data = (channel_id, message_id)
diff --git a/extensions/tags/cogs/tags.py b/extensions/tags/cogs/tags.py
index 08b89dc..41f187d 100644
--- a/extensions/tags/cogs/tags.py
+++ b/extensions/tags/cogs/tags.py
@@ -23,7 +23,8 @@
 )
 
 
-# to prevent excessive spamming when multiple people mention staff. A sorta cooldown
+# To prevent excessive spamming when multiple people mention staff.
+#  A sort of cooldown
 report_message_reminder = datetime.min
 
 
diff --git a/extensions/tags/tags.py b/extensions/tags/tags.py
index 1d1ff7b..6d1b0a8 100644
--- a/extensions/tags/tags.py
+++ b/extensions/tags/tags.py
@@ -5,9 +5,11 @@
 from extensions.settings.objects import AttributeKeys, ModuleKeys
 from resources.checks import MissingAttributesCheckFailure
 from resources.customs import Bot
-from resources.utils.utils import get_mod_ticket_channel  # for ticket channel id in Report tag
+from resources.utils.utils import get_mod_ticket_channel
+# ^ for ticket channel id in Report tag
 from resources.utils.utils import log_to_guild
-# ^ for logging when people send tags anonymously (in case someone abuses the anonymity)
+# ^ for logging when people send tags anonymously (in case someone
+#  abuses the anonymity)
 
 from extensions.tags.views import SendPubliclyTagView
 
diff --git a/extensions/testing_commands/cogs/testingcommands.py b/extensions/testing_commands/cogs/testingcommands.py
index 390cbe6..40c18cc 100644
--- a/extensions/testing_commands/cogs/testingcommands.py
+++ b/extensions/testing_commands/cogs/testingcommands.py
@@ -80,10 +80,12 @@ async def _make_vclog_embed(
 
 class TestingCog(commands.GroupCog, name="testing"):
     def __init__(self):
-        # todo: try to implement tests for commands instead of doing roundabout ways like these.
+        # todo: try to implement tests for commands instead of doing
+        #  roundabout ways like these.
         pass
 
-    @app_commands.command(name="send_fake_watchlist_modlog", description="make a fake user modlog report")
+    @app_commands.command(name="send_fake_watchlist_modlog",
+                          description="make a fake user modlog report")
     @app_commands.describe(
         target="User to add",
         reason="Reason for adding",
@@ -93,8 +95,13 @@ def __init__(self):
     )
     @app_commands.check(is_staff_check)
     async def send_fake_watchlist_mod_log(
-            self, itx: discord.Interaction[Bot], target: discord.User, reason: str = "",
-            rule: str = None, private_notes: str = "", role_changes: str = ""
+            self,
+            itx: discord.Interaction[Bot],
+            target: discord.User,
+            reason: str = "",
+            rule: str = None,
+            private_notes: str = "",
+            role_changes: str = ""
     ):
         staff_logs_category = itx.client.get_guild_attribute(
             itx.guild, AttributeKeys.staff_logs_category
@@ -104,30 +111,57 @@ async def send_fake_watchlist_mod_log(
                 "testing", [AttributeKeys.staff_logs_category])
 
         embed = discord.Embed(title="did a log thing for x", color=16705372)
-        embed.add_field(name="User", value=f"{target.mention} (`{target.id}`)", inline=True)
-        embed.add_field(name="Moderator", value=f"{itx.user.mention}", inline=True)
-        embed.add_field(name="\u200b", value="\u200b", inline=False)
-        embed.add_field(name="Rule", value=f">>> {rule}", inline=True)
-        embed.add_field(name="\u200b", value="\u200b", inline=False)
-        embed.add_field(name="Reason", value=f">>> {reason}")
-        embed.add_field(name="\u200b", value="\u200b", inline=False)
-        embed.add_field(name="Private Notes", value=f">>> {private_notes}")
-        embed.add_field(name="\u200b", value="\u200b", inline=False)
-        embed.add_field(name="Role Changes", value=role_changes.replace("[[\\n]]", "\n"))
+        embed.add_field(name="User",
+                        value=f"{target.mention} (`{target.id}`)",
+                        inline=True)
+        embed.add_field(name="Moderator",
+                        value=f"{itx.user.mention}",
+                        inline=True)
+        embed.add_field(name="\u200b",
+                        value="\u200b",
+                        inline=False)
+        embed.add_field(name="Rule",
+                        value=f">>> {rule}",
+                        inline=True)
+        embed.add_field(name="\u200b",
+                        value="\u200b",
+                        inline=False)
+        embed.add_field(name="Reason",
+                        value=f">>> {reason}")
+        embed.add_field(name="\u200b",
+                        value="\u200b",
+                        inline=False)
+        embed.add_field(name="Private Notes",
+                        value=f">>> {private_notes}")
+        embed.add_field(name="\u200b",
+                        value="\u200b",
+                        inline=False)
+        embed.add_field(name="Role Changes",
+                        value=role_changes.replace("[[\\n]]", "\n"))
         # any channel in AttributeKeys.staff_logs_category should work.
         await staff_logs_category.send(embed=embed)
 
-    @app_commands.command(name="send_pageview_test", description="Send a test embed with page buttons")
+    @app_commands.command(name="send_pageview_test",
+                          description="Send a test embed with page buttons")
     @app_commands.describe(page_count="The amount of pages to send/test")
     @app_commands.check(is_staff_check)
     async def send_pageview_test_embed(
-            self, itx: discord.Interaction[Bot], page_count: app_commands.Range[int, 1, 10000] = 40
+            self,
+            itx: discord.Interaction[Bot],
+            page_count: app_commands.Range[int, 1, 10000] = 40
     ):
         def get_chars(length: int):
-            letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/   "
-            return (''.join(random.choice(letters) for _ in range(length))).strip()
+            letters = ("abcdefghijklmnopqrstuvwxyz"
+                       "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                       "0123456789+/   ")
+            return (''.join(random.choice(letters)
+                            for _ in range(length))
+                    ).strip()
 
-        async def update_test_page(itx1: discord.Interaction[Bot], view1: PageView):
+        async def update_test_page(
+                itx1: discord.Interaction[Bot],
+                view1: PageView
+        ):
             embed = view1.pages[view1.page]
             await itx1.response.edit_message(
                 content="updated a" + str(view1.page),
@@ -153,17 +187,33 @@ async def update_test_page(itx1: discord.Interaction[Bot], view1: PageView):
 
         async def go_to_page_button_callback(itx1: discord.Interaction):
             # view: PageView = view
-            await itx1.response.send_message(f"This embed has {view.max_page_index + 1} pages!")
+            await itx1.response.send_message(
+                f"This embed has {view.max_page_index + 1} pages!")
 
-        go_to_page_button = create_simple_button("🔢", discord.ButtonStyle.blurple, go_to_page_button_callback,
-                                                 label_is_emoji=True)
-        view = PageView(0, len(pages), update_test_page, appended_buttons=[go_to_page_button])
-        await itx.response.send_message("Sending this cool embed...", embed=pages[0], view=view)
+        go_to_page_button = create_simple_button(
+            "🔢",
+            discord.ButtonStyle.blurple,
+            go_to_page_button_callback,
+            label_is_emoji=True
+        )
+        view = PageView(
+            0,
+            len(pages),
+            update_test_page,
+            appended_buttons=[go_to_page_button]
+        )
+        await itx.response.send_message(
+            "Sending this cool embed...", embed=pages[0], view=view)
 
-    @app_commands.command(name="send_srmod_appeal_test", description="Send a test embed of a ban appeal")
+    @app_commands.command(name="send_srmod_appeal_test",
+                          description="Send a test embed of a ban appeal")
     @app_commands.describe(username="The username you want to fill in")
     @app_commands.check(is_staff_check)
-    async def send_srmod_appeal_test(self, itx: discord.Interaction[Bot], username: str):
+    async def send_srmod_appeal_test(
+            self,
+            itx: discord.Interaction[Bot],
+            username: str
+    ):
         embed: discord.Embed = discord.Embed(title="New Ban Appeal")
         embed.add_field(name="Which of the following are you appealing?",
                         value="Discord Ban")
@@ -195,7 +245,7 @@ async def send_vc_log_test(
             from_channel: discord.VoiceChannel | discord.StageChannel = None,
             to_channel: discord.VoiceChannel | discord.StageChannel = None,
     ):
-        itx.response: discord.InteractionResponse  # noqa
+        itx.response: discord.InteractionResponse  # type: ignore
         # jeez the log is inconsistent lol
         user = itx.user
 
diff --git a/extensions/todolist/cogs/todolist.py b/extensions/todolist/cogs/todolist.py
index 6e8e133..373a148 100644
--- a/extensions/todolist/cogs/todolist.py
+++ b/extensions/todolist/cogs/todolist.py
@@ -10,25 +10,42 @@ def __init__(self):
         pass
 
     @app_commands.command(name="todo", description="Add or remove a to-do!")
-    @app_commands.describe(mode="Do you want to add, remove a to-do item, or check your current items?",
-                           todo="Add/remove a todo-list item. Does nothing if u wanna check it")
+    @app_commands.describe(mode="Do you want to add, remove a to-do item, "
+                                "or check your current items?",
+                           todo="Add/remove a todo-list item. Does nothing "
+                                "if u wanna check it")
     @app_commands.choices(mode=[
-        discord.app_commands.Choice(name='Add something to your to-do list', value=1),
-        discord.app_commands.Choice(name='Remove to-do', value=2),
-        discord.app_commands.Choice(name='Check', value=3)
+        discord.app_commands.Choice(
+            name='Add something to your to-do list', value=1),
+        discord.app_commands.Choice(
+            name='Remove to-do', value=2),
+        discord.app_commands.Choice(
+            name='Check', value=3)
     ])
-    async def todo(self, itx: discord.Interaction[Bot], mode: int, todo: str = None):
+    async def todo(
+            self,
+            itx: discord.Interaction[Bot],
+            mode: int,
+            todo: str = None
+    ):
+        # todo: use Enum for mode
         if mode == 1:  # Add item to to-do list
             if todo is None:
                 cmd_mention = itx.client.get_command_mention("todo")
-                await itx.response.send_message(f"This command lets you add items to your to-do list!\n"
-                                                f"Type whatever you still plan to do in the `todo: ` argument, "
-                                                f"and then you can see your current to-do list with {cmd_mention} "
-                                                f"`mode:Check`!", ephemeral=True)
+                await itx.response.send_message(
+                    f"This command lets you add items to your to-do list!\n"
+                    f"Type whatever you still plan to do in the `todo: `"
+                    f" argument, and then you can see your current to-do list "
+                    f"with {cmd_mention} `mode:Check`!",
+                    ephemeral=True,
+                )
                 return
             if len(todo) > 500:
-                itx.response.send_message("I.. don't think having such a big to-do message is gonna be very helpful..",
-                                          ephemeral=True)
+                itx.response.send_message(
+                    "I.. don't think having such a big to-do message is gonna "
+                    "be very helpful..",
+                    ephemeral=True,
+                )
                 return
             collection = itx.client.rina_db["todoList"]
             query = {"user": itx.user.id}
@@ -38,35 +55,50 @@ async def todo(self, itx: discord.Interaction[Bot], mode: int, todo: str = None)
             else:
                 todo_list = search["list"]
             todo_list.append(todo)
-            collection.update_one(query, {"$set": {"list": todo_list}}, upsert=True)
+            collection.update_one(
+                query,
+                {"$set": {"list": todo_list}},
+                upsert=True
+            )
             await itx.response.send_message(
                 f"Successfully added an item to your to-do list! "
-                f"({len(todo_list)} item{'s' * (len(todo_list) != 1)} in your to-do list now)",
-                ephemeral=True)
+                f"({len(todo_list)} item{'s' * (len(todo_list) != 1)} in "
+                f"your to-do list now)",
+                ephemeral=True,
+            )
 
         elif mode == 2:  # Remove item from to-do list
             if todo is None:
                 cmd_mention = itx.client.get_command_mention("todo")
                 await itx.response.send_message(
-                    f"Removing todo's with this command is done with IDs. You can see your current list "
-                    f"of todo's using {cmd_mention} `mode:Check`. \n"
-                    f"This list will start every todo-list item with a number. This is the ID you're "
-                    f"looking for. This number can be filled into the `todo: ` argument to remove it.", ephemeral=True)
+                    f"Removing todo's with this command is done with IDs. "
+                    f"You can see your current list of todo's using "
+                    f"{cmd_mention} `mode:Check`.\n"
+                    f"This list will start every todo-list item with a "
+                    f"number. This is the ID you're looking for. This number "
+                    f"can be filled into the `todo: ` argument to remove it.",
+                    ephemeral=True,
+                )
                 return
             try:
                 todo = int(todo)
             except ValueError:
                 await itx.response.send_message(
-                    "To remove an item from your to-do list, you must give the id of the item you want to remove. "
-                    "This should be a number... You didn't give a number...",
-                    ephemeral=True)
+                    "To remove an item from your to-do list, you must "
+                    "give the id of the item you want to remove. This "
+                    "should be a number... You didn't give a number...",
+                    ephemeral=True,
+                )
                 return
             collection = itx.client.rina_db["todoList"]
             query = {"user": itx.user.id}
             search = collection.find_one(query)
             if search is None:
                 await itx.response.send_message(
-                    "There are no items on your to-do list, so you can't remove any either...", ephemeral=True)
+                    "There are no items on your to-do list, so you can't "
+                    "remove any either...",
+                    ephemeral=True,
+                )
                 return
             todo_list = search["list"]
 
@@ -75,22 +107,34 @@ async def todo(self, itx: discord.Interaction[Bot], mode: int, todo: str = None)
             except IndexError:
                 cmd_mention = itx.client.get_command_mention("todo")
                 await itx.response.send_message(
-                    f"Couldn't delete that ID, because there isn't any item on your list with that ID. "
-                    f"Use {cmd_mention} `mode:Check` to see the IDs assigned to each item on your list",
-                    ephemeral=True)
+                    f"Couldn't delete that ID, because there isn't any item "
+                    f"on your list with that ID. Use {cmd_mention} "
+                    f"`mode:Check` to see the IDs assigned to each item on "
+                    f"your list",
+                    ephemeral=True,
+                )
                 return
-            collection.update_one(query, {"$set": {"list": todo_list}}, upsert=True)
+            collection.update_one(
+                query,
+                {"$set": {"list": todo_list}},
+                upsert=True
+            )
             await itx.response.send_message(
                 f"Successfully removed '{todo}' from your to-do list. "
-                f"Your list now contains {len(todo_list)} item{'s' * (len(todo_list) != 1)}.",
-                ephemeral=True)
+                f"Your list now contains {len(todo_list)} "
+                f"item{'s' * (len(todo_list) != 1)}.",
+                ephemeral=True,
+            )
         elif mode == 3:
             collection = itx.client.rina_db["todoList"]
             query = {"user": itx.user.id}
             search = collection.find_one(query)
             if search is None:
                 await itx.response.send_message(
-                    "There are no items on your to-do list, so.. Good job! nothing to list here....", ephemeral=True)
+                    "There are no items on your to-do list, so.. Good job! "
+                    "nothing to list here....",
+                    ephemeral=True,
+                )
                 return
             todo_list = search["list"]
             length = len(todo_list)
@@ -99,4 +143,7 @@ async def todo(self, itx: discord.Interaction[Bot], mode: int, todo: str = None)
             for item_id in range(length):
                 ans.append(f"`{item_id}`: {todo_list[item_id]}")
             ans = '\n'.join(ans)
-            await itx.response.send_message(f"Found {length} to-do item{'s' * (length != 1)}:\n{ans}", ephemeral=True)
+            await itx.response.send_message(
+                f"Found {length} to-do item{'s' * (length != 1)}:\n{ans}",
+                ephemeral=True
+            )
diff --git a/extensions/toneindicator/cogs/toneindicator.py b/extensions/toneindicator/cogs/toneindicator.py
index d2144b0..dd3b08e 100644
--- a/extensions/toneindicator/cogs/toneindicator.py
+++ b/extensions/toneindicator/cogs/toneindicator.py
@@ -312,17 +312,28 @@ def _handle_acronym(string, exact) -> tuple[str, bool]:
 
 
 class ToneIndicator(commands.Cog):
-    @app_commands.command(name="toneindicator", description="Look for the definition of a tone indicator")
-    @app_commands.describe(mode="Choose a search method, eg. /p -> platonic; or vice versa",
-                           string="This is your search query. What do you want to look for?",
-                           public="Do you want to share the search results with the rest of the channel? (True=yes)")
+    @app_commands.command(
+        name="toneindicator",
+        description="Look for the definition of a tone indicator"
+    )
+    @app_commands.describe(
+        mode="Choose a search method, eg. /p -> platonic; or vice versa",
+        string="This is your search query. What do you want to look for?",
+        public="Do you want to share the search results with the rest of "
+               "the channel? (True=yes)"
+    )
     @app_commands.choices(mode=[
-        discord.app_commands.Choice(name='Definition', value=SearchMode.definition.value),
-        discord.app_commands.Choice(name='Exact acronym', value=SearchMode.exact_acronym.value),
-        discord.app_commands.Choice(name='Rough acronym', value=SearchMode.rough_acronym.value),
+        discord.app_commands.Choice(name='Definition',
+                                    value=SearchMode.definition.value),
+        discord.app_commands.Choice(name='Exact acronym',
+                                    value=SearchMode.exact_acronym.value),
+        discord.app_commands.Choice(name='Rough acronym',
+                                    value=SearchMode.rough_acronym.value),
     ])
-    @app_commands.allowed_installs(guilds=True, users=True)
-    @app_commands.allowed_contexts(guilds=True, private_channels=True, dms=True)
+    @app_commands.allowed_installs(
+        guilds=True, users=True)
+    @app_commands.allowed_contexts(
+        guilds=True, private_channels=True, dms=True)
     async def toneindicator(
             self,
             itx: discord.Interaction[Bot],
@@ -348,7 +359,8 @@ async def toneindicator(
                 f"definition of a \"/j\", you should set `mode: ` to"
                 f"\"exact acronym\" or \"rough acronym\".\n"
                 f"If you believe this tone tag should be added to the "
-                f"dictionary, message @mysticmia on discord (bot developer)")
+                f"dictionary, message @mysticmia on discord (bot developer)"
+            )
 
         elif len(result_str.split("\n")) > 6 and public:
             public = False
diff --git a/extensions/vclogreader/cogs/vclogreader.py b/extensions/vclogreader/cogs/vclogreader.py
index fe5abf9..4c85a0b 100644
--- a/extensions/vclogreader/cogs/vclogreader.py
+++ b/extensions/vclogreader/cogs/vclogreader.py
@@ -22,7 +22,10 @@
 channel_separator_table = str.maketrans({"<": "", "#": "", ">": ""})
 
 
-def extract_id_and_name(embed: discord.Embed, field_number: int) -> tuple[str, str]:
+def extract_id_and_name(
+        embed: discord.Embed,
+        field_number: int,
+) -> tuple[str, str]:
     """
     A helper function to extract id and name from Logger Bot or Anna's
     voice channel logs.
@@ -52,17 +55,27 @@ async def _get_vc_activity(
         min_time: float,
         max_time: float,
         msg_limit: int
-) -> list[tuple[float, tuple[int, str], tuple[int, str] | None, tuple[int, str] | None]]:
+) -> list[tuple[
+    float,
+    tuple[int, str],
+    tuple[int, str] | None,
+    tuple[int, str] | None
+]]:
     """
-    Retrieve the most recent voice channel activity from the logger channel and convert into neat string.
-
-    :param voice_log_channel: Channel from which you want to get the voice channel logs / information.
-    :param min_time: A unix epoch timestamp for the earliest logs to fetch (up to how long ago).
-    :param max_time: A unix epoch timestamp for the latest logs to fetch (up to how recent).
+    Retrieve the most recent voice channel activity from the logger
+    channel and convert into neat string.
+
+    :param voice_log_channel: Channel from which you want to get the
+     voice channel logs / information.
+    :param min_time: A unix epoch timestamp for the earliest logs to
+     fetch (up to how long ago).
+    :param max_time: A unix epoch timestamp for the latest logs to
+     fetch (up to how recent).
     :param msg_limit: How many log messages to look through.
 
-    :return: A list of (timestamp, user, previous_channel?, new_channel?). Typically, at least one of
-     previous_channel or new_channel has a value.
+    :return: A list of (timestamp, user, previous_channel?,
+     new_channel?). Typically, at least one of previous_channel or
+     new_channel has a value.
     """
     # list of [(username, user_id), (joined_channel_id), (left_channel_id)]
     output: list[tuple[
@@ -78,12 +91,15 @@ async def _get_vc_activity(
             limit=msg_limit,
             oldest_first=True
     ):
-        # oldest_first is by default true, since "after" != None, oldest_first will be true anyway.
+        # oldest_first is by default true, since "after" != None,
+        #  oldest_first will be true anyway.
         # Might as well make it definitive.
 
-        # For context: the embed message sent by the Logger bot looks like this:
-        #   author / image of user that sent message (author.name = username + descriminator
-        #     (#0 in new discord update))
+        # For context: the embed message sent by the Logger bot
+        #  looks like this:
+        #
+        #   author / image of user that sent message (author.name
+        #    = username + descriminator (#0 in new discord update))
         #  case 1: the user joins/leaves a voice channel
         #   description: **username#0** joined/left voice channel vc_name
         #   fields[0].name: Channel
@@ -91,20 +107,25 @@ async def _get_vc_activity(
         #   fields[1].name: ID
         #   fields[1].value: "```ini\nUser = 123456789\nChannel = 123456```"
         #  case 2: the user moves from one channel to another
-        #   description: **username#0** moved from <#123456> (name1) to <#234567> (name2)
+        #   description: **username#0** moved from <#123456>
+        #    (name1) to <#234567> (name2)
         #   fields[0].name: Current channel they are in    << New >>
         #   fields[0].value: <#234567> (name1)
         #   fields[0].name: Previously occupied channel    << Old >>
         #   fields[0].value: <#123456> (name2)
         #   fields[1].name: ID
-        #   fields[1].value: "```ini\nUser = 123456789\nNew = 234567\nOld = 123456```"
+        #   fields[1].value: "```ini\nUser = 123456789\nNew = 234567\n
+        #    Old = 123456```"
 
         for embed in message.embeds:
             username = embed.description.split("**", 2)[1].split("#", 1)[0]
-            # split **mysticmia#0** to mysticmia (taking discord usernames can't contain hashtags (cuz they can't))
+            # split **mysticmia#0** to mysticmia
+            #  (taking discord usernames can't contain hashtags (cuz
+            #  they can't))
             # print("Username = ", username)
             try:
-                # remove bold name and everything after the first word, to only select 'moved', 'joined', or 'left'.
+                # remove bold name and everything after the first word,
+                #  to only select 'moved', 'joined', or 'left'.
                 event_type = (
                     embed
                     .description
@@ -130,7 +151,8 @@ async def _get_vc_activity(
                 #  user "moved" in this case. There isn't any fool-proof
                 #  way to test otherwise.
                 event_type = "moved"
-                # Will get an assertion error if it's not, anyway, so I'll leave it as it is for now.
+                # Will get an assertion error if it's not, anyway, so
+                #  I'll leave it as it is for now.
 
             # print("Type = ", type)
             # print("Embed field count = ", len(embed.fields))
@@ -140,46 +162,69 @@ async def _get_vc_activity(
 
             try:
                 if embed.fields[0].name == "Action":
-                    # actions like server deafening or muting someone also get logged, but are
-                    # irrelevant for this diagram/command.
+                    # actions like server deafening or muting someone
+                    #  also get logged, but are irrelevant for this
+                    #  diagram/command.
                     continue
-                # Could be done more efficiently but oh well. Not like this is suitable for any
-                # other bot either anyway. And I'm limited by discord API anyway.
+                # Could be done more efficiently but oh well. Not like
+                #  this is suitable for any other bot either anyway. And
+                #  I'm limited by discord API anyway.
 
-                if len(embed.fields) == 3:  # user moved channels (3 fields: previous/current channel, and IDs)
+                if len(embed.fields) == 3:
+                    # user moved channels
+                    #  (3 fields: previous/current channel, and IDs)
                     if event_type != "moved":
-                        raise AssertionError(f"type '{event_type}' is not a valid type (should be 'moved')")
-                    current_id, current_name = extract_id_and_name(embed, 0)
+                        raise AssertionError(
+                            f"type '{event_type}' is not a valid type "
+                            f"(should be 'moved')"
+                        )
+
+                    current_id, current_name = extract_id_and_name(
+                        embed, 0)
                     current_channel_data.append(current_id)
                     current_channel_data.append(current_name)
-                    previous_id, previous_name = extract_id_and_name(embed, 1)
+                    previous_id, previous_name = extract_id_and_name(
+                        embed, 1)
                     previous_channel_data.append(previous_id)
                     previous_channel_data.append(previous_name)
                 elif len(embed.fields) == 2:
                     if event_type == "joined":
-                        current_id, current_name = extract_id_and_name(embed, 0)
+                        current_id, current_name = extract_id_and_name(
+                            embed, 0)
                         current_channel_data.append(current_id)
                         current_channel_data.append(current_name)
                     elif event_type == "left":
-                        previous_id, previous_name = extract_id_and_name(embed, 0)
+                        previous_id, previous_name = extract_id_and_name(
+                            embed, 0)
                         previous_channel_data.append(previous_id)
                         previous_channel_data.append(previous_name)
                     else:
                         raise AssertionError(
-                            f"type '{event_type}' is not a valid type (should be 'joined' or 'left')")
+                            f"type '{event_type}' is not a valid type "
+                            f"(should be 'joined' or 'left')"
+                        )
                 else:
                     raise AssertionError(
-                        f"Embed fields count was expected to be 3 or 2. Instead, it was '{len(embed.fields)}'")
+                        f"Embed fields count was expected to be 3 or 2. "
+                        f"Instead, it was '{len(embed.fields)}'"
+                    )
             except IndexError:
-                # TODO: try to figure out why it crashed that one time. Now with more details
-                # edit: Some actions, such as server-deafening another user, give a different log message.
+                # TODO: try to figure out why it crashed that one time.
+                #  Now with more details
+                # edit: Some actions, such as server-deafening another
+                #  user, give a different log message.
                 if len(embed.fields) == 0:
                     raise Exception("Embed has no fields!")
                 else:
                     if len(embed.fields[0].value.split("#", 1)) < 2:
                         raise Exception(
-                            f"First embed field '{embed.fields[0].value}' does not have hashtags for its ID!")
-                    raise Exception(f"Embed field '{embed.fields[0].value}' has some other error or something D:")
+                            f"First embed field '{embed.fields[0].value}' "
+                            f"does not have hashtags for its ID!"
+                        )
+                    raise Exception(
+                        f"Embed field '{embed.fields[0].value}' has some "
+                        f"other error or something D:"
+                    )
 
             # remove the ```ini\n  ...   ``` from the embed field
             id_data = (embed.fields[-1]
@@ -213,8 +258,6 @@ async def _get_vc_activity(
             event_timestamp = embed.timestamp.timestamp()
 
             try:
-                "A list of (timestamp, user, previous_channel?, new_channel?). Typically, at least one of"
-                "previous_channel or new_channel has a value."
                 event_user = (int(user_data[0]), user_data[1])
 
                 if len(previous_channel_data) == 0:
@@ -229,9 +272,14 @@ async def _get_vc_activity(
                     current_channel = (int(current_channel_data[0]),
                                        current_channel_data[1])
             except ValueError:
-                raise AssertionError(f"IDs were not numeric!\nFull error:\n{traceback.format_exc()}")
+                raise AssertionError(
+                    f"IDs were not numeric!\n"
+                    f"Full error:\n"
+                    f"{traceback.format_exc()}"
+                )
 
-            data = (event_timestamp, event_user, previous_channel, current_channel)
+            data = (event_timestamp, event_user,
+                    previous_channel, current_channel)
             output.append(data)
 
     return output
@@ -401,23 +449,30 @@ async def get_voice_channel_data(
         if user_ids is not None:
             select_user_ids: list[str] = user_ids.replace(" ", "").split(",")
         # update typing (if channel mention)
-        requested_channel: discord.app_commands.AppCommandChannel | str = requested_channel
+        requested_channel: discord.app_commands.AppCommandChannel | str \
+            = requested_channel
         warning = ""
         if type(requested_channel) is discord.app_commands.AppCommandChannel:
             voice_channel = itx.client.get_channel(requested_channel.id)
         else:
             if not requested_channel.isdecimal():
-                await itx.response.send_message("You need to give a numerical ID!", ephemeral=True)
+                await itx.response.send_message(
+                    "You need to give a numerical ID!",
+                    ephemeral=True
+                )
                 return
             voice_channel = itx.client.get_channel(int(requested_channel))
 
         if voice_channel is None:
             # make custom vc if the voice channel we're trying to get
             #  logs does not exist anymore.
-            voice_channel = CustomVoiceChannel(channel_id=int(requested_channel),
-                                               name="Unknown Channel",
-                                               members=[])
-            warning = "Warning: This channel is not a voice channel, or has been deleted!\n\n"
+            voice_channel = CustomVoiceChannel(
+                channel_id=int(requested_channel),
+                name="Unknown Channel",
+                members=[]
+            )
+            warning = ("Warning: This channel is not a voice channel, "
+                       "or has been deleted!\n\n")
 
         vc_activity_logs_channel: discord.abc.Messageable | None
         vc_activity_logs_channel = itx.client.get_guild_attribute(
@@ -434,16 +489,25 @@ async def get_voice_channel_data(
             lower_bound = float(lower_bound)
             upper_bound = float(upper_bound)
             if lower_bound <= 0:
-                await itx.response.send_message("Your period (data in the past [x] minutes) has to be above 0!",
-                                                ephemeral=True)
+                await itx.response.send_message(
+                    "Your period (data in the past [x] minutes) has to "
+                    "be above 0!",
+                    ephemeral=True,
+                )
                 return
             if upper_bound > lower_bound:
                 await itx.response.send_message(
-                    "Your upper bound can't be bigger (-> longer ago) than the lower bound!", ephemeral=True)
+                    "Your upper bound can't be bigger (-> longer ago) than "
+                    "the lower bound!",
+                    ephemeral=True,
+                )
                 return
         except ValueError:
             await itx.response.send_message(
-                "Your bounding period has to be a number for the amount of minutes that have passed", ephemeral=True)
+                "Your bounding period has to be a number for the amount of "
+                "minutes that have passed",
+                ephemeral=True,
+            )
             return
 
         lower_bound *= 60  # minutes to seconds
@@ -465,8 +529,12 @@ async def get_voice_channel_data(
 
         await itx.response.defer(ephemeral=True)
 
-        events = await _get_vc_activity(vc_activity_logs_channel, min_time,
-                                        max_time, msg_log_limit)
+        events = await _get_vc_activity(
+            vc_activity_logs_channel,
+            min_time,
+            max_time,
+            msg_log_limit,
+        )
 
         if upper_bound == 0:
             # If looking until the current time/date, add fake "leave"
@@ -475,9 +543,12 @@ async def get_voice_channel_data(
             #  joined or left during the given time frame] will still be
             #  plotted on the graph.
             for member in voice_channel.members:
-                events.append((current_time,
-                               (member.id, member.name),
-                               (voice_channel.id, voice_channel.name), None))
+                events.append((
+                    current_time,
+                    (member.id, member.name),
+                    (voice_channel.id, voice_channel.name),
+                    None
+                ))
 
         data, sorted_usernames = await _format_data_for_graph(
             events, max_time, min_time, select_user_ids, voice_channel)
diff --git a/extensions/vclogreader/customvoicechannel.py b/extensions/vclogreader/customvoicechannel.py
index e9b8d59..a25e9fb 100644
--- a/extensions/vclogreader/customvoicechannel.py
+++ b/extensions/vclogreader/customvoicechannel.py
@@ -2,13 +2,20 @@
 
 
 class CustomVoiceChannel:
-    def __init__(self, channel_id: int, name: str, members: list[discord.Member]):
+    def __init__(
+            self,
+            channel_id: int,
+            name: str,
+            members: list[discord.Member]
+    ):
         """
-        Create a custom :py:type:`~discord.VoiceChannel` to reuse similar code.
+        Create a custom :py:type:`~discord.VoiceChannel` to reuse
+        similar code.
 
         :param channel_id: The id of the channel.
         :param name: The name of the channel.
-        :param members: A list of members currently connected to the voice channel.
+        :param members: A list of members currently connected to
+         the voice channel.
 
         :return: A string representation of the channel as mention.
         """
diff --git a/extensions/watchlist/cogs/watchlist.py b/extensions/watchlist/cogs/watchlist.py
index 01f17e8..4dff8a8 100644
--- a/extensions/watchlist/cogs/watchlist.py
+++ b/extensions/watchlist/cogs/watchlist.py
@@ -1,4 +1,5 @@
-from datetime import datetime  # to get embed send time for embed because cool (serves no real purpose)
+from datetime import datetime
+# ^ to get embed send time for embed because cool (serves no real purpose)
 
 import discord
 import discord.app_commands as app_commands
@@ -7,30 +8,41 @@
 
 from extensions.settings.objects import ModuleKeys, AttributeKeys
 from resources.customs import Bot
-from resources.checks.permissions import is_staff  # to check role in _add_to_watchlist, as backup
+from resources.checks.permissions import is_staff
+# ^ to check role in _add_to_watchlist, as backup
 from resources.checks import (
     is_staff_check, module_enabled_check, MissingAttributesCheckFailure
 )  # the cog is pretty much only intended for staff use
 
-from extensions.watchlist.local_watchlist import create_watchlist, get_watchlist, remove_watchlist, \
-    get_user_id_from_watchlist, WatchlistNotLoadedException
+from extensions.watchlist.local_watchlist import (
+    create_watchlist,
+    get_watchlist,
+    remove_watchlist,
+    get_user_id_from_watchlist,
+    WatchlistNotLoadedException,
+)
 from extensions.watchlist.modals import WatchlistReasonModal
 
 
-def _parse_watchlist_string_message_id(message_id: str | None) -> tuple[int | None, bool]:
+def _parse_watchlist_string_message_id(
+        message_id: str | None
+) -> tuple[int | None, bool]:
     """
     Parse a given message_id from a /watchlist command
 
     :param message_id: The message id to parse to an int (or ``None``)
-    :return: A tuple of the parsed message id (or ``None`` if ``None`` given), and whether the message_id ended with
-     " | overwrite", to allow the user to link someone else's message with the watchlist report.
+    :return: A tuple of the parsed message id (or ``None`` if ``None``
+     given), and whether the message_id ended with " | overwrite", to
+     allow the user to link someone else's message with the watchlist
+     report.
     """
     allow_different_report_author = False
     if message_id is None:
         return message_id, allow_different_report_author
 
     if message_id.isdecimal():
-        # required cuz discord client doesn't let you fill in message IDs as ints; too large
+        # required cuz discord client doesn't let you fill in message
+        # IDs as ints; too large
         message_id = int(message_id)
     else:
         if message_id.endswith(" | overwrite"):
@@ -80,7 +92,8 @@ async def _create_uncool_watchlist_thread(
         cmd_mention_settings = client.get_command_mention("settings")
         await joiner_msg.edit(
             content=f"No role has been set up to be pinged when a watchlist "
-                    f"is created. Use {cmd_mention_settings} to add one.")
+                    f"is created. Use {cmd_mention_settings} to add one."
+        )
     else:
         await joiner_msg.edit(content=f"<@&{active_staff_role.id}>")
         await joiner_msg.delete()
@@ -118,20 +131,23 @@ async def _add_to_watchlist(
 ):
     if not is_staff(itx, itx.user):
         await itx.response.send_message(
-            "You don't have the right permissions to do this.", ephemeral=True)
+            "You don't have the right permissions to do this.",
+            ephemeral=True,
+        )
         return
     if itx.client.is_me(user):
         await itx.response.send_message(
             "You just tried to add a watchlist to Uncute Rina. Are you "
             "sure about that.......?\n"
             "If so, please send a message to Mia so she can remove this "
-            "extra if-statement.", ephemeral=True
+            "extra if-statement.",
+            ephemeral=True,
         )
         return
 
-    # different report author = different message author than the reported user,
-    #   used in case you want to report someone but want to use someone else's
-    #   message as evidence or context.
+    # different report author = different message author than the
+    #  reported user, used in case you want to report someone but want
+    #  to use someone else's message as evidence or context.
     message_id, allow_different_report_author = \
         _parse_watchlist_string_message_id(message_id)
 
@@ -142,12 +158,13 @@ async def _add_to_watchlist(
             "Your watchlist reason won't fit! Please make your reason "
             "shorter. You can also expand your reason / add details in "
             "the thread.",
-            ephemeral=True)
+            ephemeral=True,
+        )
         # because I'm nice
         await itx.followup.send(
             "Given reason (you can also copy paste the command):\n"  # 52 chars
             + reason[:2000 - 52 - 3] + "...",
-            ephemeral=True
+            ephemeral=True,
         )
         return
 
@@ -172,21 +189,25 @@ async def _add_to_watchlist(
         try:
             reported_message = await itx.channel.fetch_message(message_id)
         except discord.Forbidden:
-            await itx.followup.send("Forbidden: I do not have permission to "
-                                    "see that message.", ephemeral=True)
+            await itx.followup.send(
+                "Forbidden: I do not have permission to see that message.",
+                ephemeral=True,
+            )
             return
         except discord.NotFound:
             await itx.followup.send(
                 "NotFound: I could not find that message. Make sure you ran "
                 "this command in the same channel as the one where the "
                 "message id came from.",
-                ephemeral=True
+                ephemeral=True,
             )
             return
         except discord.HTTPException:
-            await itx.followup.send("HTTPException: Something went wrong "
-                                    "while trying to fetch the message.",
-                                    ephemeral=True)
+            await itx.followup.send(
+                "HTTPException: Something went wrong while trying to fetch "
+                "the message.",
+                ephemeral=True,
+            )
             raise
 
         if (reported_message.author.id != user.id
@@ -216,7 +237,10 @@ async def _add_to_watchlist(
         )
 
         if reported_message.attachments:
-            reported_message_info += f"(:newspaper: Contains {len(reported_message.attachments)} attachments)\n"
+            reported_message_info += (
+                f"(:newspaper: Contains "
+                f"{len(reported_message.attachments)} attachments)\n"
+            )
 
     watchlist_thread_id = get_watchlist(watch_channel.guild.id, user.id)
     already_on_watchlist = watchlist_thread_id is not None
@@ -224,7 +248,8 @@ async def _add_to_watchlist(
     if already_on_watchlist:
         # fetch thread, in case the thread was archived (not in cache)
         thread = await watch_channel.guild.fetch_channel(watchlist_thread_id)
-        # fetch message the thread is attached to (fetch, in case msg is not in cache)
+        # fetch message the thread is attached to (fetch, in case msg
+        #  is not in cache)
         msg = await watch_channel.fetch_message(watchlist_thread_id)
         # link back to that original message with the existing thread.
         await msg.reply(
@@ -233,7 +258,7 @@ async def _add_to_watchlist(
                     f"Since they were already on this list, here's a reply "
                     f"to the original thread.\n"
                     f"May this serve as a warning for this user.",
-            allowed_mentions=discord.AllowedMentions.none()
+            allowed_mentions=discord.AllowedMentions.none(),
         )
     else:
         msg, thread = await _create_uncool_watchlist_thread(
@@ -253,14 +278,16 @@ async def _add_to_watchlist(
     if allow_different_report_author:
         different_author_warning = " (mentioned message author below)"
     copyable_version = await thread.send(
-        f"Reported user: {user.mention} (`{user.id}`)" + different_author_warning,
+        f"Reported user: {user.mention} (`{user.id}`)"
+        + different_author_warning,
         allowed_mentions=discord.AllowedMentions.none())
 
     if message_id is not None:
         reported_message_data_message = await thread.send(
             f"Reported message: {reported_message.author.mention}"
             f"(`{reported_message.author.id}`) - {reported_message.jump_url}",
-            allowed_mentions=discord.AllowedMentions.none())
+            allowed_mentions=discord.AllowedMentions.none(),
+        )
         await thread.send(f">>> {reported_message.content}",
                           allowed_mentions=discord.AllowedMentions.none())
 
@@ -289,21 +316,33 @@ async def _add_to_watchlist(
             ephemeral=True
         )
     else:
-        await _update_uncool_watchlist_embed(copyable_version.jump_url, reported_message_info, msg, reason, user)
+        await _update_uncool_watchlist_embed(
+            copyable_version.jump_url,
+            reported_message_info,
+            msg,
+            reason,
+            user
+        )
         await itx.followup.send(
             warning
             + ":white_check_mark: Successfully added user to watchlist.",
-            ephemeral=True
+            ephemeral=True,
         )
 
 
 @app_commands.check(is_staff_check)
 @module_enabled_check(ModuleKeys.watchlist)
 @app_commands.context_menu(name="Add user to watchlist")
-async def watchlist_ctx_user(itx: discord.Interaction[Bot], user: discord.User):
+async def watchlist_ctx_user(
+        itx: discord.Interaction[Bot],
+        user: discord.User
+):
     watchlist_reason_modal = WatchlistReasonModal(
-        _add_to_watchlist, "Add user to watchlist",
-        user, None, 300
+        _add_to_watchlist,
+        title="Add user to watchlist",
+        reported_user=user,
+        message=None,
+        timeout=300,
     )
     await itx.response.send_modal(watchlist_reason_modal)
 
@@ -316,8 +355,11 @@ async def watchlist_ctx_message(
         message: discord.Message
 ):
     watchlist_reason_modal = WatchlistReasonModal(
-        _add_to_watchlist, "Add user to watchlist using message",
-        message.author, message, 300
+        _add_to_watchlist,
+        title="Add user to watchlist using message",
+        reported_user=message.author,
+        message=message,
+        timeout=300,
     )
     await itx.response.send_modal(watchlist_reason_modal)
 
@@ -348,10 +390,14 @@ async def watchlist(
                           .transform(itx, user))
             warning = ""
         except app_commands.errors.TransformerError:
-            warning = ("This user is not in this server! Either they left or got banned, or you executed this in "
-                       "a server they're not in.\n"
-                       "If they got banned, there's no reason to look out for them anymore ;)\n"
-                       "It's also easier to mention them if you run it in the main server. Anyway,\n\n")
+            warning = (
+                "This user is not in this server! Either they left or got "
+                "banned, or you executed this in a server they're not in.\n"
+                "If they got banned, there's no reason to look out for them "
+                "anymore ;)\n"
+                "It's also easier to mention them if you run it in the main "
+                "server. Anyway,\n\n"
+            )
         await _add_to_watchlist(itx, user, reason, message_id, warning=warning)
 
     @app_commands.command(name="check_watchlist",
@@ -359,7 +405,11 @@ async def watchlist(
     @app_commands.describe(user="User to check")
     @app_commands.check(is_staff_check)
     @module_enabled_check(ModuleKeys.watchlist)
-    async def check_watchlist(self, itx: discord.Interaction[Bot], user: discord.User):
+    async def check_watchlist(
+            self,
+            itx: discord.Interaction[Bot],
+            user: discord.User
+    ):
         if not is_staff(itx, itx.user):
             await itx.response.send_message(
                 "You don't have the right permissions to do this.",
@@ -382,14 +432,14 @@ async def check_watchlist(self, itx: discord.Interaction[Bot], user: discord.Use
                 f"🔵 This user ({user.mention} `{user.id}`) is already "
                 f"on the watchlist.",
                 ephemeral=True,
-                allowed_mentions=discord.AllowedMentions.none()
+                allowed_mentions=discord.AllowedMentions.none(),
             )
         else:
             await itx.followup.send(
                 f"🟡 This user ({user.mention} `{user.id}`) is not yet "
                 f"on the watchlist.",
                 ephemeral=True,
-                allowed_mentions=discord.AllowedMentions.none()
+                allowed_mentions=discord.AllowedMentions.none(),
             )
 
     @commands.Cog.listener()
@@ -457,8 +507,11 @@ async def on_message(self, message: discord.Message):
         on_watchlist: bool = watchlist_thread_id is not None
 
         if on_watchlist:
-            thread: discord.Thread = await watchlist_channel.guild.fetch_channel(
-                watchlist_thread_id)  # fetch, to retrieve (archived) thread.
+            thread: discord.Thread = \
+                await watchlist_channel.guild.fetch_channel(
+                    watchlist_thread_id
+                )
+            # ^ fetch, to retrieve (archived) thread.
             await message.forward(thread)
 
     @commands.Cog.listener()
diff --git a/extensions/watchlist/modals/watchlistreasonmodal.py b/extensions/watchlist/modals/watchlistreasonmodal.py
index 6a00c25..6b79df7 100644
--- a/extensions/watchlist/modals/watchlistreasonmodal.py
+++ b/extensions/watchlist/modals/watchlistreasonmodal.py
@@ -4,13 +4,16 @@
 
 class WatchlistReasonModal(discord.ui.Modal):
     """
-    A modal allowing the user to add a user to the watchlist with a reason.
+    A modal allowing the user to add a user to the watchlist with
+    a reason.
 
     :ivar add_to_watchlist_func: An async function from
      ``WatchList.add_to_watchlist(itx, user, reason, message_id,
      [warnings]) -> None`` to run with on_submit.
-    :ivar message: The message that was reported / marked for the watchlist.
-    :ivar reason_text: The reason provided by the staff member to add the user to the watchlist.
+    :ivar message: The message that was reported / marked for
+     the watchlist.
+    :ivar reason_text: The reason provided by the staff member to add
+     the user to the watchlist.
     :ivar timeout: The timeout before the modal closes itself.
     :ivar title: The title of the embed.
     :ivar user: The user that is being added to the watchlist.
@@ -36,14 +39,21 @@ def __init__(
         self.message = message
         self.add_to_watchlist_func = add_to_watchlist_func
 
-        self.reason_text = discord.ui.TextInput(label=f'Reason for reporting {reported_user}'[:45],
-                                                placeholder="not required but recommended",
-                                                style=discord.TextStyle.paragraph,
-                                                required=False)
+        self.reason_text = discord.ui.TextInput(
+            label=f'Reason for reporting {reported_user}'[:45],
+            placeholder="not required but recommended",
+            style=discord.TextStyle.paragraph,
+            required=False,
+        )
         self.add_item(self.reason_text)
 
     async def on_submit(self, itx: discord.Interaction):
         self.value = 1
-        await self.add_to_watchlist_func(itx, self.user, self.reason_text.value,
-                                         str(getattr(self.message, "id", "")) or None, "")
+        await self.add_to_watchlist_func(
+            itx,
+            self.user,
+            self.reason_text.value,
+            str(getattr(self.message, "id", "")) or None,
+            ""
+        )
         self.stop()
diff --git a/main.py b/main.py
index 28567c9..250e0ce 100644
--- a/main.py
+++ b/main.py
@@ -71,11 +71,13 @@
 #       read channel history (locate previous starboard message, for example)
 #       move users between voice channels (custom vc)
 #       manage roles (for removing NPA and NVA roles)
-#       manage channels (Global: You need this to be able to set the position of CustomVCs in a category, apparently)
+#       manage channels (Global: You need this to be able to set the
+#        position of CustomVCs in a category, apparently)
 #           NEEDS TO BE GLOBAL?
 #           Create and Delete voice channels
 #       use embeds (for starboard)
-#       use (external) emojis (for starboard, if you have external starboard reaction...?)
+#       use (external) emojis (for starboard, if you have external
+#        starboard reaction...?)
 
 
 def get_token_data() -> tuple[
@@ -85,14 +87,17 @@ def get_token_data() -> tuple[
     motorcore.AgnosticDatabase
 ]:
     """
-    Ensures the api_keys.json file contains all the bot's required keys, and
-    uses these keys to start a link to the MongoDB.
+    Ensures the api_keys.json file contains all the bot's required
+    keys, and uses these keys to start a link to the MongoDB.
 
-    :return: Tuple of discord bot token, other api tokens, and a sync and async database client cluster connection.
+    :return: Tuple of discord bot token, other api tokens, and a sync
+    and async database client cluster connection.
 
     :raise FileNotFoundError: If the api_keys.json file does not exist.
-    :raise json.decoder.JSONDecodeError: If the api_keys.json file is not in correct JSON format.
-    :raise KeyError: If the api_keys.json file is missing the api key for an api used in the program.
+    :raise json.decoder.JSONDecodeError: If the api_keys.json file is
+     not in correct JSON format.
+    :raise KeyError: If the api_keys.json file is missing the api key
+     for an api used in the program.
     """
     load_progress.progress("Loading api keys...")
     try:
@@ -102,7 +107,8 @@ def get_token_data() -> tuple[
         bot_token: str = api_keys['Discord']
         missing_tokens: list[str] = []
         for key in ApiTokenDict.__annotations__:
-            # copy every other key to new dictionary to check if every key is in the file.
+            # copy every other key to new dictionary to check if every
+            #  key is in the file.
             if key not in api_keys:
                 missing_tokens.append(key)
                 continue
@@ -112,14 +118,18 @@ def get_token_data() -> tuple[
         raise
     except json.decoder.JSONDecodeError as ex:
         raise json.decoder.JSONDecodeError(
-            "Invalid JSON file. Please ensure it has correct formatting.", ex.doc, ex.pos).with_traceback(None)
+            "Invalid JSON file. Please ensure it has correct formatting.",
+            ex.doc,
+            ex.pos
+        ).with_traceback(None)
     if missing_tokens:
         raise KeyError("Missing API key for: " + ', '.join(missing_tokens))
 
     load_progress.progress("Loading database clusters...")
     cluster: MongoClient = MongoClient(tokens['MongoDB'])
     rina_db: PyMongoDatabase = cluster["Rina"]
-    cluster: motorcore.AgnosticClient = motorasync.AsyncIOMotorClient(tokens['MongoDB'])
+    cluster: motorcore.AgnosticClient = motorasync.AsyncIOMotorClient(
+        tokens['MongoDB'])
     async_rina_db: motorcore.AgnosticDatabase  # = cluster["Rina"]
     async_rina_db = cluster.get_database("Rina", codec_options=codec_options)
     load_progress.step("Loaded database clusters", newline=False)
@@ -128,8 +138,10 @@ def get_token_data() -> tuple[
 
 def get_version() -> str:
     """
-    Dumb code for cool version updates. Reads version file and matches with current version string. Updates file if
-    string is newer, and adds another ".%d" for how often the bot has been started in this version.
+    Dumb code for cool version updates. Reads version file and matches
+    with current version string. Updates file if string is newer, and
+    adds another ".%d" for how often the bot has been started in
+    this version.
 
     :return: Current version/instance of the bot.
     """
@@ -165,10 +177,14 @@ def create_client(
     load_progress.progress("Creating bot")
 
     intents = discord.Intents.default()
-    intents.members = True  # apparently this needs to be defined because it's not included in Intents.default()?
-    intents.message_content = True  # to send 1984, and to otherwise read message content.
-    # setup default discord bot client settings, permissions, slash commands, and file paths
-
+    intents.members = True
+    # ^ apparently this needs to be defined because it's not included
+    #  in Intents.default()?
+    intents.message_content = True
+    # to send 1984, and to otherwise read message content.
+
+    # setup default discord bot client settings, permissions,
+    #  slash commands, and file paths
     discord.VoiceClient.warn_nacl = False
     bot: Bot = Bot(
         api_tokens=tokens,
@@ -178,7 +194,8 @@ def create_client(
 
         intents=intents,
         command_prefix="/!\"@:\\#",
-        #  unnecessary, but needs to be set so... uh... yeah. Unnecessary terminal warnings avoided.
+        # Unnecessary, but needs to be set so... uh... yeah. Unnecessary
+        #  terminal warnings avoided.
         case_insensitive=True,
         activity=discord.Game(name="with slash (/) commands!"),
         allowed_mentions=discord.AllowedMentions(everyone=False),
@@ -204,7 +221,8 @@ async def on_ready():
         except OverflowError:
             debug(text, color="green")
 
-        await client.log_channel.send(f":white_check_mark: **Started Rina** in version {version}")
+        await client.log_channel.send(
+            f":white_check_mark: **Started Rina** in version {version}")
 
         post_startup_progress = ProgressBar(4)
 
@@ -234,26 +252,34 @@ async def setup_hook():
         client.sched = AsyncIOScheduler(logger=logger)
         client.sched.start()
 
-        # Cache server settings into client, to prevent having to load settings for every extension
+        # Cache server settings into client, to prevent having to load
+        #  settings for every extension.
         # Activate the extensions/programs/code for slash commands
 
         extension_loading_start_time = datetime.now().astimezone()
         extension_load_progress = ProgressBar(len(EXTENSIONS))
         for extID in range(len(EXTENSIONS)):
             extension_load_progress.progress(f"Loading {EXTENSIONS[extID]}")
-            await client.load_extension("extensions." + EXTENSIONS[extID] + ".module")
-        start_progress.step(f"Loaded extensions successfully "
-                            f"(in {datetime.now().astimezone() - extension_loading_start_time})")
+            await client.load_extension(
+                "extensions." + EXTENSIONS[extID] + ".module")
+        start_progress.step(
+            f"Loaded extensions successfully (in "
+            f"{datetime.now().astimezone() - extension_loading_start_time})"
+        )
         start_progress.progress("Loading server settings...")
         try:
-            client.log_channel = await client.fetch_channel(988118678962860032)
+            client.log_channel = \
+                await client.fetch_channel(988118678962860032)
         except discord.errors.Forbidden:
-            # client.log_channel = await client.fetch_channel(986304081234624554)
-            client.log_channel = await client.fetch_channel(1062396920187863111)
+            # client.log_channel = \
+            #     await client.fetch_channel(986304081234624554)
+            client.log_channel = \
+                await client.fetch_channel(1062396920187863111)
         client.bot_owner = await client.fetch_user(262913789375021056)
-        # client.bot_owner = (await client.application_info()).owner  # or client.owner / client.owner_id :P
-        # can't use the commented out code because Rina is owned by someone else in the main server than
-        # the dev server (=not me).
+        # client.bot_owner = (await client.application_info()).owner
+        # ^ or client.owner / client.owner_id :P
+        # can't use the commented out code because Rina is owned by
+        # someone else in the main server than the dev server (=not me).
         start_progress.step("Loaded server settings")
         start_progress.progress("Restarting ongoing reminders...")
         await relaunch_ongoing_reminders(client)
@@ -279,7 +305,7 @@ async def setup_hook():
 # - Translator
 # - (Unisex) compliment quotes
 # - Add error catch for when dictionaryapi.com is down
-# - make more three-in-one commands have optional arguments, explaining what to do if you don't
-#       fill in the optional argument
+# - make more three-in-one commands have optional arguments, explaining
+#   what to do if you don't fill in the optional argument.
 
 # endregion
diff --git a/resources/checks/command_checks.py b/resources/checks/command_checks.py
index defe36e..ecdb044 100644
--- a/resources/checks/command_checks.py
+++ b/resources/checks/command_checks.py
@@ -4,7 +4,10 @@
 import discord
 import discord.app_commands as app_commands
 
-from resources.checks.errors import ModuleNotEnabledCheckFailure, CommandDoesNotSupportDMsCheckFailure
+from resources.checks.errors import (
+    ModuleNotEnabledCheckFailure,
+    CommandDoesNotSupportDMsCheckFailure
+)
 
 if TYPE_CHECKING:
     from resources.customs import Bot
@@ -15,16 +18,21 @@ def module_enabled_check(module_key):
     A check to check if a module is enabled.
 
     :param module_key: The key of the module to check.
-    :return: A decorator that checks if the given module is enabled in the interaction's guild.
+    :return: A decorator that checks if the given module is enabled in
+     the interaction's guild.
     """
     async def decor_check(itx: discord.Interaction[Bot]):
         """
-        A decorator that checks if the interaction's guild has a module enabled.
-        :param itx: The interaction with the guild to check (and the itx.client to check it with).
+        A decorator that checks if the interaction's guild has a
+        module enabled.
+
+        :param itx: The interaction with the guild to check (and the
+         itx.client to check it with).
         :return: ``True`` if the module is enabled, else an exception.
-        :raise ModuleNotEnabledCheckFailure: If the module is not enabled.
-        :raise CommandDoesNotSupportDMsCheckFailure: If the command was run outside a server
-         (-> no modules = no attributes).
+        :raise ModuleNotEnabledCheckFailure: If the module is
+         not enabled.
+        :raise CommandDoesNotSupportDMsCheckFailure: If the command was
+         run outside a server (-> no modules = no attributes).
         """
         if itx.guild is None:
             raise CommandDoesNotSupportDMsCheckFailure()
@@ -37,9 +45,12 @@ async def decor_check(itx: discord.Interaction[Bot]):
 def not_in_dms_check(itx: discord.Interaction[Bot]) -> bool:
     """
     A check to check if the command/interaction was run in DMs.
+
     :param itx: The interaction to check.
-    :return: ``True`` if the interaction was not in a DM, else an exception.
-    :raise CommandDoesNotSupportDMsCheckFailure: If the command was run outside a server.
+    :return: ``True`` if the interaction was not in a DM, else an
+     exception.
+    :raise CommandDoesNotSupportDMsCheckFailure: If the command was run
+     outside a server.
     """
     if is_in_dms(itx.guild):
         raise CommandDoesNotSupportDMsCheckFailure()
@@ -49,7 +60,8 @@ def not_in_dms_check(itx: discord.Interaction[Bot]) -> bool:
 def is_in_dms(guild: discord.Guild | int | None) -> bool:
     """
     A simple function to check if a command was run in a DM.
-    :param guild: The guild or guild id to check...... This function is just a one-liner...
+    :param guild: The guild or guild id to check...... This function is
+     just a one-liner...
     :return: Whether the command was run in DMs.
     """
     return guild is None
@@ -60,25 +72,20 @@ def module_not_disabled_check(module_key: str):
     A check to check if a module is not disabled.
 
     :param module_key: The key of the module to check.
-    :return: A decorator that checks if the given module is disabled in the interaction's guild.
+    :return: A decorator that checks if the given module is disabled
+     in the interaction's guild.
     """
     async def decor_check(itx: discord.Interaction[Bot]) -> bool:
         """
-        A check to check if a command is allowed to be used in the current channel. This will be allowed in DMs, but
-        not in servers that don't explicitly enabled the module.
+        A check to check if a command is allowed to be used in the
+        current channel. This will be allowed in DMs, but not in servers
+        that don't explicitly enabled the module.
 
-        :param itx: The interaction with the guild and :py:func:`Bot.is_module_enabled` function.
+        :param itx: The interaction with the guild
+         and :py:func:`Bot.is_module_enabled` function.
         :return: ``True`` if the check passed.
-        :raise ModuleNotEnabledCheckFailure: If the command was run in a server, and the server did not have the
-         module enabled.
-        """
-        """
-        A decorator that checks if the interaction's guild has a module enabled.
-        :param itx: The interaction with the guild to check (and the itx.client to check it with).
-        :return: ``True`` if the module is enabled, else an exception.
-        :raise ModuleNotEnabledCheckFailure: If the module is not enabled.
-        :raise CommandDoesNotSupportDMsCheckFailure: If the command was run outside a server
-         (-> no modules = no attributes).
+        :raise ModuleNotEnabledCheckFailure: If the command was run in
+         a server, and the server did not have the module enabled.
         """
         if is_in_dms(itx.guild):
             return True
diff --git a/resources/checks/permission_checks.py b/resources/checks/permission_checks.py
index 38c6dff..f2b023d 100644
--- a/resources/checks/permission_checks.py
+++ b/resources/checks/permission_checks.py
@@ -1,7 +1,10 @@
 import discord
 
 from resources.checks.permissions import is_staff, is_admin
-from .errors import CommandDoesNotSupportDMsCheckFailure, InsufficientPermissionsCheckFailure
+from .errors import (
+    CommandDoesNotSupportDMsCheckFailure,
+    InsufficientPermissionsCheckFailure
+)
 from .command_checks import is_in_dms
 
 
@@ -9,8 +12,10 @@ def is_staff_check(itx: discord.Interaction):
     """
     A check to check if the command executor has a staff role.
     :param itx: The interaction to check.
-    :return: ``True`` if the executor has a staff role, else an exception.
-    :raise InsufficientPermissionsCheckFailure: If the user does not have a staff role.
+    :return: ``True`` if the executor has a staff role, else
+     an exception.
+    :raise InsufficientPermissionsCheckFailure: If the user does not
+     have a staff role.
     """
     if is_in_dms(itx.guild):
         return CommandDoesNotSupportDMsCheckFailure()
@@ -23,8 +28,10 @@ def is_admin_check(itx: discord.Interaction):
     """
     A check to check if the command executor has an admin role.
     :param itx: The interaction to check.
-    :return: ``True`` if the executor has an admin role, else an exception.
-    :raise InsufficientPermissionsCheckFailure: If the user does not have an admin role.
+    :return: ``True`` if the executor has an admin role, else an
+     exception.
+    :raise InsufficientPermissionsCheckFailure: If the user does not
+     have an admin role.
     """
     if is_in_dms(itx.guild):
         return CommandDoesNotSupportDMsCheckFailure()
diff --git a/resources/checks/permissions.py b/resources/checks/permissions.py
index 0f9c140..cccd706 100644
--- a/resources/checks/permissions.py
+++ b/resources/checks/permissions.py
@@ -9,11 +9,15 @@
     from resources.customs import Bot
 
 
-def is_staff(itx: discord.Interaction[Bot], member: discord.Member | discord.User) -> bool:
+def is_staff(
+        itx: discord.Interaction[Bot],
+        member: discord.Member | discord.User
+) -> bool:
     """
     Check if someone is staff.
 
-    :param itx: The interaction with ``itx.client.server_settings`` and ``itx.guild``.
+    :param itx: The interaction with ``itx.client.server_settings``
+     and ``itx.guild``.
     :param member: A discord user with or without roles attribute.
 
     :return: Whether the user has a staff role.
@@ -27,11 +31,15 @@ def is_staff(itx: discord.Interaction[Bot], member: discord.Member | discord.Use
     )
 
 
-def is_admin(itx: discord.Interaction[Bot], member: discord.Member | discord.User) -> bool:
+def is_admin(
+        itx: discord.Interaction[Bot],
+        member: discord.Member | discord.User
+) -> bool:
     """
     Check if someone is an admin.
 
-    :param itx: The interaction with ``itx.client.server_settings`` and ``itx.guild``.
+    :param itx: The interaction with ``itx.client.server_settings``
+     and ``itx.guild``.
     :param member: A discord user with or without roles attribute.
 
     :return: Whether the user has an admin role.
diff --git a/resources/customs/api_token_dict.py b/resources/customs/api_token_dict.py
index 730fc23..c523e33 100644
--- a/resources/customs/api_token_dict.py
+++ b/resources/customs/api_token_dict.py
@@ -3,5 +3,12 @@
 
 # this format is necessary to keep the spaces in "Open Exchange Rates" etc.
 # I could rename the key to use snake_case, but oh well.
-ApiTokenDict = TypedDict('ApiTokenDict',
-                         {'MongoDB': str, 'Open Exchange Rates': str, 'Wolfram Alpha': str, 'Equaldex': str})
+ApiTokenDict = TypedDict(
+    'ApiTokenDict',
+    {
+        'MongoDB': str,
+        'Open Exchange Rates': str,
+        'Wolfram Alpha': str,
+        'Equaldex': str
+    }
+)
diff --git a/resources/customs/bot.py b/resources/customs/bot.py
index 988ca3e..459fa7c 100644
--- a/resources/customs/bot.py
+++ b/resources/customs/bot.py
@@ -1,13 +1,16 @@
 from __future__ import annotations
-from apscheduler.schedulers.asyncio import AsyncIOScheduler  # for scheduling Reminders
-from datetime import datetime  # for startup and crash logging, and Reminders
+from apscheduler.schedulers.asyncio import AsyncIOScheduler
+# ^ for scheduling Reminders
+from datetime import datetime
+# ^ for startup and crash logging, and Reminders
 from typing import TYPE_CHECKING, TypeVar
 
 import discord  # for main discord bot functionality
 import discord.ext.commands as commands
 
 import motor.core as motorcore  # for typing
-from pymongo.database import Database as PyMongoDatabase  # for MongoDB database typing
+from pymongo.database import Database as PyMongoDatabase
+# ^ for MongoDB database typing
 
 from extensions.settings.objects import (
     AttributeKeys, EnabledModules, ServerAttributes)
@@ -22,7 +25,8 @@
 
 
 class Bot(commands.Bot):
-    startup_time = datetime.now().astimezone()  # bot uptime start, used in /version in cmd_staffaddons
+    # bot uptime start, used in /version in cmd_staffaddons
+    startup_time = datetime.now().astimezone()
 
     commandList: list[discord.app_commands.AppCommand]
     log_channel: discord.TextChannel | discord.Thread
@@ -92,16 +96,21 @@ def get_guild_attribute(
         :param args: The attribute(s) to get the values of. Must be keys
          of ServerAttributes.
         :param default: The value to return if attribute was not found.
-        :return: A single or list of values matching the requested attributes,
-         with *default* if attributes are not found.
+        :return: A single or list of values matching the requested
+         attributes, with *default* if attributes are not found.
         """
         if type(guild_id) is discord.Guild:
             guild_id: int = guild_id.id
         if len(args) == 0:
             raise ValueError("You must provide at least one argument!")
 
-        if (self.server_settings is None or  # settings have not been fetched yet.
-                guild_id not in self.server_settings):  # return early
+        if (
+                self.server_settings is None
+                or guild_id not in self.server_settings
+        ):
+            # If settings have not been fetched yet, or if the guild
+            #  doesn't have any settings (perhaps the bot was recently
+            #  added).
             if len(args) > 1:
                 return [default] * len(args)
             return default
@@ -119,7 +128,8 @@ def get_guild_attribute(
         for arg in args:
             if arg not in attributes:
                 assert arg not in ServerAttributes.__annotations__
-                raise ValueError(f"Attribute '{arg}' is not a valid attribute!")
+                raise ValueError(
+                    f"Attribute '{arg}' is not a valid attribute!")
 
             att_value = attributes[arg]  # type:ignore
             if att_value is not None:
@@ -144,14 +154,19 @@ def is_module_enabled(
         :param guild_id: The server to check the module state for.
         :param args: The module key(s) to get the state for.
 
-        :return: The enabled/disabled state of the module as boolean, or a list of booleans matching the list of
-         module keys given.
+        :return: The enabled/disabled state of the module as boolean, or
+         a list of booleans matching the list of module keys given.
         """
         if type(guild_id) is discord.Guild:
             guild_id: int = guild_id.id
 
-        if (self.server_settings is None or  # settings have not been fetched yet.
-                guild_id not in self.server_settings):  # return early
+        if (
+                self.server_settings is None
+                or guild_id not in self.server_settings
+        ):
+            # If settings have not been fetched yet, or if the guild
+            #  doesn't have any settings (perhaps the bot was recently
+            #  added).
             return False
 
         modules = self.server_settings[guild_id].enabled_modules
@@ -176,7 +191,8 @@ def is_me(self, user_id: discord.Member | discord.User | int) -> bool:
         :param user_id: The user or user id to check.
         :return: ``True`` if the given user is the bot, otherwise ``False``.
         """
-        if isinstance(user_id, discord.User) or isinstance(user_id, discord.Member):
+        if (isinstance(user_id, discord.User)
+                or isinstance(user_id, discord.Member)):
             user_id = user_id.id
         # Could also use hasattr(user_id, "id") for a more generic approach...
         #  But this should work fine enough.
diff --git a/resources/customs/enabledservers.py b/resources/customs/enabledservers.py
index 596b191..d485f22 100644
--- a/resources/customs/enabledservers.py
+++ b/resources/customs/enabledservers.py
@@ -1,3 +1,5 @@
+# todo: delete file
+
 class EnabledServers:
     @classmethod
     def no_server_ids(cls):
@@ -13,13 +15,17 @@ def dev_server_ids(cls):
     @classmethod
     def transplace_etc_ids(cls):
         """
-        Returns a list of TransPlace dev servers, TransPlace, and the staff server ids.
+        Returns a list of TransPlace dev servers, TransPlace, and the
+        staff server ids.
         """
-        return [959551566388547676, 981730502987898960] + EnabledServers.dev_server_ids()  # transplace, staff
+        return ([959551566388547676, 981730502987898960]
+                + EnabledServers.dev_server_ids())  # transplace, staff
 
     @classmethod
     def all_server_ids(cls):
         """
         Returns a list of all TransPlace dev.
         """
-        return [1087014898199969873, 638480381552754730] + EnabledServers.transplace_etc_ids()  # EnbyPlace, Transonance
+        # EnbyPlace, Transonance
+        return ([1087014898199969873, 638480381552754730]
+                + EnabledServers.transplace_etc_ids())
diff --git a/resources/customs/progressbar.py b/resources/customs/progressbar.py
index a66387e..407cb39 100644
--- a/resources/customs/progressbar.py
+++ b/resources/customs/progressbar.py
@@ -2,7 +2,14 @@
 
 
 class ProgressBar:
-    def __init__(self, max_steps: int, *, fill_char='#', progress_char='+', empty_char=' '):
+    def __init__(
+            self,
+            max_steps: int,
+            *,
+            fill_char: str = '#',
+            progress_char: str = '+',
+            empty_char: str = ' '
+    ):
         self._max_steps = max_steps
         self._step = 0
         self._fill_char = fill_char
@@ -15,9 +22,13 @@ def __init__(self, max_steps: int, *, fill_char='#', progress_char='+', empty_ch
 
     def _get_progess_bar(self, *, busy) -> str:
         """
-        A helper function to construct a progress bar based on current progress.
-        :param busy: Whether to add a :py:attr:`_progress_char` in the bar.
-        :return: A progress bar string of the current progress, with opening and closing parentheses: "[   ]:".
+        A helper function to construct a progress bar based on
+        current progress.
+
+        :param busy: Whether to add a :py:attr:`_progress_char` in
+         the bar.
+        :return: A progress bar string of the current progress, with
+         opening and closing parentheses: "[   ]:".
         """
         out = "["
         out += self._fill_char * self._step
@@ -33,9 +44,12 @@ def _get_progess_bar(self, *, busy) -> str:
 
     def _get_line_clear_padding(self, string) -> str:
         """
-        A helper function to get the amount of spaces necessary to overwrite the previous progress message.
+        A helper function to get the amount of spaces necessary to
+        overwrite the previous progress message.
+
         :param string: The text of the upcoming progress message.
-        :return: A string with spaces to overwrite the previous progress message.
+        :return: A string with spaces to overwrite the previous
+         progress message.
         """
         length = len(string)
         length = max(self._previous_message_length - length, 0)
@@ -43,13 +57,17 @@ def _get_line_clear_padding(self, string) -> str:
 
     def progress(self, text, *, newline=False) -> None:
         """
-        Print a debug log, incrementing the progress bar with the :py:attr:`_progress_char`.
+        Print a debug log, incrementing the progress bar with
+        the :py:attr:`_progress_char`.
+
         :param text: The text to place after the progress bar.
-        :param newline: Whether to place a newline after the progress bar, or reset the caret to overwrite this line
-         with the next progress bar step.
+        :param newline: Whether to place a newline after the progress
+         bar, or reset the caret to overwrite this line with the next
+         progress bar step.
         """
         end = '\n' if newline else '\r'
-        if self._just_progressed:  # to prevent two progress chars in a row: "[#++ ]:"
+        if self._just_progressed:
+            # to prevent two progress chars in a row: "[#++ ]:"
             self._step += 1
         progress_bar = self._get_progess_bar(busy=True)
         padding = self._get_line_clear_padding(text)
@@ -59,10 +77,13 @@ def progress(self, text, *, newline=False) -> None:
 
     def step(self, text, *, newline=True):
         """
-        Print a debug log, incrementing the progress bar with the :py:attr:`_fill_char`.
+        Print a debug log, incrementing the progress bar with
+        the :py:attr:`_fill_char`.
+
         :param text: The text to place after the progress bar.
-        :param newline: Whether to place a newline after the progress bar, or reset the caret to overwrite this line
-         with the next progress bar step.
+        :param newline: Whether to place a newline after the progress
+         bar, or reset the caret to overwrite this line with the next
+         progress bar step.
         """
         end = '\n' if newline else '\r'
         self._step += 1
diff --git a/resources/modals/generics.py b/resources/modals/generics.py
index 9f39711..8e71be9 100644
--- a/resources/modals/generics.py
+++ b/resources/modals/generics.py
@@ -4,10 +4,11 @@
 class SingleLineModal(discord.ui.Modal):
     def __init__(self, title: str, label: str, placeholder: str = ""):
         super().__init__(title=title)
-        self.question_text = discord.ui.TextInput(label=label,
-                                                  placeholder=placeholder,
-                                                  # style=discord.TextStyle.short, required=True
-                                                  )
+        self.question_text = discord.ui.TextInput(
+            label=label,
+            placeholder=placeholder,
+            # style=discord.TextStyle.short, required=True
+        )
         self.add_item(self.question_text)
         self.itx = None
 
diff --git a/resources/utils/__init__.py b/resources/utils/__init__.py
index 0b6aebb..03f4f47 100644
--- a/resources/utils/__init__.py
+++ b/resources/utils/__init__.py
@@ -10,12 +10,11 @@
     'debug',
     'get_mod_ticket_channel',
     'log_to_guild',
-    'executed_in_dms',
     'codec_options',
 ]
 
 from resources.checks.permissions import is_staff, is_admin
 from .stringhelper import replace_string_command_mentions
 from .timeparser import TimeParser, MissingQuantityException, MissingUnitException, TIMETERMS
-from .utils import DebugColor, debug, get_mod_ticket_channel, log_to_guild, executed_in_dms
+from .utils import DebugColor, debug, get_mod_ticket_channel, log_to_guild
 from .database import codec_options
diff --git a/resources/utils/stringhelper.py b/resources/utils/stringhelper.py
index 7f9bf06..fcab2a5 100644
--- a/resources/utils/stringhelper.py
+++ b/resources/utils/stringhelper.py
@@ -3,16 +3,20 @@
 
 def replace_string_command_mentions(text: str, client: Bot) -> str:
     """
-    Converts strings with "%%command%%" into a command mention ().
+    Converts strings with "%%command%%" into a command mention
+    ().
 
     :param text: The text in which to look for command mentions.
-    :param client: The client with which to convert the command into a command mention.
+    :param client: The client with which to convert the command into
+     a command mention.
 
-    :return: The input text, with every command instance replaced with its matching command mention.
+    :return: The input text, with every command instance replaced with
+     its matching command mention.
 
     .. note::
 
-        If the command does not exist, it will fill the mention with "/command" instead of "".
+        If the command does not exist, it will fill the mention with
+        "/command" instead of "".
     """
     while "%%" in text:
         command_start_index = text.index("%%")
diff --git a/resources/utils/timeparser.py b/resources/utils/timeparser.py
index 0d5f191..65c3bf7 100644
--- a/resources/utils/timeparser.py
+++ b/resources/utils/timeparser.py
@@ -32,28 +32,36 @@ def parse_time_string(input_string: str) -> DistanceComponents:
 
         :param input_string: The string to split. Example: "4days6seconds"
 
-        :return: A list of easily-comprehensible time components: [(4, "days"), (6, "seconds")].
-
-        :raise ValueError: If the user fills in an invalid digit ("0..3" or "0.30.4" for example)
-        :raise MissingQuantityException: If the user starts their input with a non-numeric character (should
-         always start with a number :P)
-        :raise MissingUnitException: If the user doesn't have a complete parse (typically because it ends with
-         a numeric character).
+        :return: A list of easily-comprehensible time components:
+         [(4, "days"), (6, "seconds")].
+
+        :raise ValueError: If the user fills in an invalid digit
+         ("0..3" or "0.30.4" for example)
+        :raise MissingQuantityException: If the user starts their input
+         with a non-numeric character (should always start with a
+         number :P)
+        :raise MissingUnitException: If the user doesn't have a complete
+         parse (typically because it ends with a numeric character).
         """
-        # This function was partially written by AI, to convert "2d4sec" to [(2, "d"), (4,"sec")]
+        # This function was partially written by AI, to convert "2d4sec"
+        #  to [(2, "d"), (4,"sec")]
 
         if input_string[0] not in "0123456789.":
-            raise MissingQuantityException("Time strings should begin with a number: \"10min\"")
-        # This regex captures one or more digits followed by one or more alphabetic characters.
-        pattern = r"([.\d]+)([a-zA-Z]+)"  # :o i see two capture groups
+            raise MissingQuantityException(
+                "Time strings should begin with a number: \"10min\"")
+        # This regex captures one or more digits followed by one or more
+        #  alphabetic characters.
+        pattern = r"([.\d]+)([a-zA-Z]+)"  # :o I see two capture groups
 
         # Find all matches in the input string
         matches = re.findall(pattern, input_string)
 
         # Convert the matches to the desired format: (number, string)
         result = []
-        # Not using list comprehension, to send more-detailed error message.
-        # result = [(int(num), unit) for num, unit in matches] # :o you can split the capture groups
+        # Not using list comprehension, to send more-detailed
+        #  error message:
+        #     result = [(int(num), unit) for num, unit in matches]
+        # ^ :o you can split the capture groups
         output = ""
 
         for (num, unit) in matches:
@@ -62,14 +70,15 @@ def parse_time_string(input_string: str) -> DistanceComponents:
                 result.append((float(num), unit))
             except ValueError:  # re-raise
                 raise ValueError(
-                    f"Invalid number '{num}' in '{input_string}'! You can use decimals, but "
-                    f"the number should be valid."
+                    f"Invalid number '{num}' in '{input_string}'! You can "
+                    f"use decimals, but the number should be valid."
                 )
 
         if input_string != output:
             # incomplete parse
             raise MissingUnitException(
-                f"Incomplete parse. The input string did not have the correct parsing format:\n"
+                f"Incomplete parse. The input string did not have the "
+                f"correct parsing format:\n"
                 f"- Input:  `{input_string}`\n"
                 f"- Parsed: `{output}`" if output else "- Parsed: _(Empty)_"
             )
@@ -77,43 +86,54 @@ def parse_time_string(input_string: str) -> DistanceComponents:
         return result
 
     @staticmethod
-    def shrink_time_terms(time_units: DistanceComponents) -> DistanceComponents:
+    def shrink_time_terms(
+            time_units: DistanceComponents
+    ) -> DistanceComponents:
         """
         Helper function to convert time strings from "year" to "y", etc.
 
-        :param time_units: An output from parse_time_string() containing a list of (4, "days") tuples.
-
+        :param time_units: An output from parse_time_string() containing
+         a list of (4, "days") tuples.
         :return: A list of tuples with shrunk time strings: (4, "d").
-
         :raise ValueError: Input contains unrecognised datetime unit(s).
         """
         for unit_index in range(len(time_units)):
-            # use index instead of iterating tuples in list because of the tuple component reassignment 3 lines down.
+            # use index instead of iterating tuples in list because of
+            #  the tuple component reassignment 3 lines down.
             for timeterm in TIMETERMS:
                 if time_units[unit_index][1] in TIMETERMS[timeterm]:
-                    time_units[unit_index] = (time_units[unit_index][0], timeterm)
-                    # tuple does not support item assignment like (1,2)[0]=2
-                    # Also assigning to tuples doesn't save in list, so need list ref too.
+                    time_units[unit_index] = (time_units[unit_index][0],
+                                              timeterm)
+                    # tuple does not support item assignment
+                    #  like `(1,2)[0]=2`.
+                    # Also assigning to tuples doesn't save in list, so
+                    #  need a list reference too.
                     break
             else:
-                raise ValueError(f"Datetime unit '{time_units[unit_index]}' not recognised!")
+                raise ValueError(f"Datetime unit '{time_units[unit_index]}'"
+                                 f" not recognised!")
         return time_units
 
     @staticmethod
-    def parse_date(time_string: str, start_date: datetime = datetime.now().astimezone()) -> datetime:
+    def parse_date(
+            time_string: str,
+            start_date: datetime = datetime.now().astimezone()
+    ) -> datetime:
         """
-        Helper function to turn strings like "3d5h10min4seconds" to a datetime in the future.
+        Helper function to turn strings like "3d5h10min4seconds" to a
+        datetime in the future.
 
 
-        :param time_string: A string of a date format like "3d10min". Should not contain spaces (but it
-         probably doesn't matter).
+        :param time_string: A string of a date format like "3d10min".
+         Should not contain spaces (but it probably doesn't matter).
         :param start_date: The date to offset from.
 
-        :return: A datetime with an offset in the future (relative to the given datetime input) matching the
-         input string.
+        :return: A datetime with an offset in the future (relative to
+         the given datetime input) matching the input string.
 
-        :raise ValueError: If the input is invalid; if the input contains unrecognised datetime units; or
-         if the "year" unit exceeds 3999 or if the "day" offset exceeds 1500000.
+        :raise ValueError: If the input is invalid; if the input
+         contains unrecognised datetime units; or if the "year" unit
+         exceeds 3999 or if the "day" offset exceeds 1500000.
         """
         # - "next thursday at 3pm"
         # - "tomorrow"
@@ -123,10 +143,15 @@ def parse_date(time_string: str, start_date: datetime = datetime.now().astimezon
         # + "2022y4mo3days"
         # - ""
         if "-" in time_string:
-            raise ValueError("Tried parsing input as timestring when it should be parsed as ISO8601 or Unix instead.")
+            raise ValueError(
+                "Tried parsing input as timestring when it should be parsed "
+                "as ISO8601 or Unix instead."
+            )
+
+        time_units = TimeParser.shrink_time_terms(
+            TimeParser.parse_time_string(time_string)  # can raise ValueError
+        )
 
-        time_units = TimeParser.shrink_time_terms(TimeParser.parse_time_string(time_string))  # can raise ValueError
-        # raises ValueError if invalid input
         timedict = {
             "y": start_date.year,
             "M": start_date.month,
@@ -135,7 +160,8 @@ def parse_date(time_string: str, start_date: datetime = datetime.now().astimezon
             "m": start_date.minute,
             "s": start_date.second,
             "f": start_date.microsecond,
-            # microseconds can only be set with "0.04s", but since start_date will typically be from discord snowflakes,
+            # microseconds can only be set with "0.04s", but since
+            #  start_date will typically be from discord snowflakes,
             #  the microseconds will be 0 by default.
         }
 
@@ -151,8 +177,16 @@ def decimals(time: float) -> float:
             """
             Get the decimal values of a floating point number.
 
-            :param time: The floating point number to get the decimal values of.
-            :return: The floating point section of the given number. Examples: 13.42 -> 0.42, -34.9 -> -0.9.
+            Example::
+
+                >>> decimals(13.42)
+                0.42
+                >>> decimals(-34.9)
+                -0.9.
+
+            :param time: The floating point number to get the decimal
+             values of.
+            :return: The floating point section of the given number.
             """
             return time - int(time)
 
@@ -200,13 +234,22 @@ def is_whole(time: float) -> bool:
         if timedict["y"] >= 3999 or timedict["d"] >= 1500000:
             raise ValueError("I don't think I can remind you in that long!")
 
-        timedict = {i: int(timedict[i]) for i in timedict}  # round everything down to the nearest whole number
-
-        distance = datetime(timedict["y"], timedict["M"], 1,
-                            timedict["h"], timedict["m"], timedict["s"], timedict["f"],
-                            tzinfo=start_date.tzinfo)
-        # cause you cant have >31 days in a month, but if overflow is given, then let
-        # this timedelta calculate the new months/years
-        distance += timedelta(days=timedict["d"] - 1)  # -1 cuz datetime.day has to start at 1 (first day of the month)
+        # round everything down to the nearest whole number
+        timedict = {i: int(timedict[i]) for i in timedict}
+
+        distance = datetime(
+            timedict["y"],
+            timedict["M"],
+            1,  # add days using timedelta(days=d)
+            timedict["h"],
+            timedict["m"],
+            timedict["s"],
+            timedict["f"],
+            tzinfo=start_date.tzinfo
+        )
+        # You cant have >31 days in a month, but if overflow is given,
+        #  then let this timedelta calculate the new months/years
+        distance += timedelta(days=timedict["d"] - 1)
+        # ^ -1 cuz datetime.day has to start at 1 (first day of the month)
 
         return distance
diff --git a/resources/utils/utils.py b/resources/utils/utils.py
index 7c2fa6f..4046834 100644
--- a/resources/utils/utils.py
+++ b/resources/utils/utils.py
@@ -1,5 +1,6 @@
 from __future__ import annotations
-from datetime import datetime, timezone  # for logging, to show log time; and for parsetime
+from datetime import datetime, timezone
+# ^ for logging, to show log time; and for parsetime
 from enum import Enum
 import logging  # for debug (logger.info)
 import warnings  # for debug (if given wrong color)
@@ -51,16 +52,21 @@ def debug(
         end="\n",
         advanced=False
 ) -> None:
-    # todo make all debug calls use DebugColor instead of string for `color`
+    # todo make all debug calls use DebugColor instead of string
+    #  for `color`
     """
     Log a message to the console.
 
     :param text: The message you want to send to the console.
-    :param color: The color you want to give your message ('red' for example).
-    :param add_time: If you want to start the message with a '[2025-03-31T23:59:59.000001Z] [INFO]:'.
-    :param end: What to end the end of the message with (similar to print(end='')).
-    :param advanced: Whether to interpret `text` as advanced text (like minecraft in-chat colors).
-     Replaces "&4" to red, "&l" to bold, etc. and "&&4" to a red background.
+    :param color: The color you want to give your message
+     ('red' for example).
+    :param add_time: If you want to start the message with a
+     '[2025-03-31T23:59:59.000001Z] [INFO]:'.
+    :param end: What to end the end of the message with (similar
+     to print(end='')).
+    :param advanced: Whether to interpret `text` as advanced text
+     (like minecraft in-chat colors). Replaces "&4" to red, "&l" to
+     bold, etc. and "&&4" to a red background.
     """
     if type(text) is not str:
         text = repr(text)
@@ -103,20 +109,39 @@ def debug(
         for _detColor in detail_color:
             while "&" + _detColor in text:
                 _text = text
-                text = text.replace("m&" + _detColor, ";" + detail_color[_detColor] + "m", 1)
+                text = text.replace(
+                    "m&" + _detColor,
+                    ";" + detail_color[_detColor] + "m",
+                    1
+                )
                 if _text == text:
-                    text = text.replace("&" + _detColor, "\033[" + detail_color[_detColor] + "m", 1)
+                    # No previous coloring found to replace, so add a
+                    #  new one instead. (no m&)
+                    text = text.replace(
+                        "&" + _detColor,
+                        "\033[" + detail_color[_detColor] + "m",
+                        1
+                    )
         color = DebugColor.default
     else:
         original_color = color
         if type(color) is str:
-            color = color.replace(" ", "").replace("-", "").replace("_", "")
+            color = (color
+                     .replace(" ", "")
+                     .replace("-", "")
+                     .replace("_", ""))
             color = getattr(DebugColor, color, None)
         if color is None:
-            warnings.warn("Invalid color given for debug function: " + original_color, SyntaxWarning)
+            warnings.warn(
+                "Invalid color given for debug function: " + original_color,
+                SyntaxWarning
+            )
             color = DebugColor.default
     if add_time:
-        time = f"{color.value}[{datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%S.%fZ')}] [INFO]: "
+        formatted_time_string = (datetime
+                                 .now(timezone.utc)
+                                 .strftime('%Y-%m-%dT%H:%M:%S.%fZ'))
+        time = f"{color.value}[{formatted_time_string}] [INFO]: "
     else:
         time = color.value
     logging.basicConfig(level=logging.INFO, format='%(message)s')
@@ -124,7 +149,8 @@ def debug(
     # print = logger.info
     if end.endswith("\n"):
         end = end[:-2]
-    logger.info(f"{time}{text}{DebugColor.default.value}" + end.replace('\r', '\033[F'))
+    logger.info(f"{time}{text}{DebugColor.default.value}"
+                + end.replace('\r', '\033[F'))
 
 
 def get_mod_ticket_channel(
@@ -133,16 +159,21 @@ def get_mod_ticket_channel(
     """
     Fetch the #contact-staff ticket channel for a specific guild.
 
-    :param client: Rina's Bot class to fetch ticket channel IDs (hardcoded).
+    :param client: Rina's Bot class to fetch ticket channel IDs
+     (hardcoded).
     :param guild_id: A class with a guild_id or guild property.
 
     :return: The matching guild's ticket channel id.
-    :raise MissingAttributesCheckFailure: If the guild has no ticket channel defined in its settings.
+    :raise MissingAttributesCheckFailure: If the guild has no ticket
+     channel defined in its settings.
     """
     if type(guild_id) is discord.Interaction:
         guild_id = guild_id.guild_id
-    ticket_channel: discord.abc.Messageable | None = client.get_guild_attribute(
-        guild_id, AttributeKeys.ticket_create_channel)
+    ticket_channel: discord.abc.Messageable | None = \
+        client.get_guild_attribute(
+            guild_id,
+            AttributeKeys.ticket_create_channel
+        )
 
     return ticket_channel
 
@@ -158,18 +189,22 @@ async def log_to_guild(
     """
     Log a message to a guild's logging channel (vcLog)
 
-    :param client: The bot class with :py:func:`Bot.get_guild_info` to find logging channel.
+    :param client: The bot class with :py:func:`Bot.get_guild_info` to
+     find logging channel.
     :param guild: Guild of the logging channel
     :param msg: Message you want to send to this logging channel
-    :param crash_if_not_found: Whether to crash if the guild does not have a logging channel.
-     Useful if this originated from an application command.
+    :param crash_if_not_found: Whether to crash if the guild does not
+     have a logging channel. Useful if this originated from an
+     application command.
     :param ignore_dms: Whether to crash if the command was run in DMs.
 
     :return: ``True`` if a log was sent successfully, else ``False``.
 
-    :raise KeyError: If client.vcLog channel is undefined.
-     Note: It still outputs the given message to console and to the client's default log channel.
-    :raise MissingAttributesCheckFailure: If no logging channel is defined.
+    :raise KeyError: If client.vcLog channel is undefined. Note: It
+     still outputs the given message to console and to the client's
+     default log channel.
+    :raise MissingAttributesCheckFailure: If no logging channel is
+     defined.
     """
     log_channel: discord.abc.Messageable = client.get_guild_attribute(
         guild, AttributeKeys.log_channel)
@@ -201,36 +236,8 @@ async def log_to_guild(
               "    log message: " + msg, color="orange")
         return False
 
-    await log_channel.send(content=msg, allowed_mentions=discord.AllowedMentions.none())
-    return True
-
-
-async def executed_in_dms(
-        *,
-        itx: discord.Interaction = None,
-        message: discord.Message = None,
-        channel: discord.DMChannel | discord.GroupChannel |
-        discord.TextChannel | discord.StageChannel |
-        discord.VoiceChannel | discord.Thread = None
-) -> bool:
-    # make this a check
-    """
-    Make a command guild-only by telling people in DMs that they can't use the command
-
-    :param itx: (used for interactions) The interaction to check if it was used in a server - and to reply to.
-    :param message: (used for events) The message to check if it was used in a server.
-    :param channel: The channel to check if it was used in a server.
-
-    :return: if command was executed in DMs (for 'if ... : continue')
-
-    :raise AssertionError: if the user provided not **exactly one** of [itx, message, channel] parameters.
-    """
-    assert len([i for i in [itx, message, channel] if i is not None]) == 1, ValueError(
-        "Give an itx, message, or channel, not multiple!"
+    await log_channel.send(
+        content=msg,
+        allowed_mentions=discord.AllowedMentions.none()
     )
-    id_object: discord.Message | discord.Interaction | discord.TextChannel = next(
-        i for i in [itx, message, channel] if i is not None)
-    if id_object.server_id is None:
-        await itx.response.send_message("This command is unavailable in DMs", ephemeral=True)
-        return True
-    return False
+    return True
diff --git a/resources/views/generics.py b/resources/views/generics.py
index 0fc6c8e..565998a 100644
--- a/resources/views/generics.py
+++ b/resources/views/generics.py
@@ -21,7 +21,8 @@ def create_simple_button(
     :param style: The button color.
     :param callback: The function to call when the button is clicked.
     :param disabled: Whether the button is clickable by the user.
-    :param label_is_emoji: Whether the given label should be used as button icon instead. Default: False.
+    :param label_is_emoji: Whether the given label should be used as
+     button icon instead. Default: False.
 
     :return: A button with the given properties.
     """
@@ -36,24 +37,32 @@ def create_simple_button(
 class GenericTwoButtonView(discord.ui.View):
     def __init__(
             self,
-            button_true: tuple[str, discord.ButtonStyle] = ("Confirm", discord.ButtonStyle.green),
-            button_false: tuple[str, discord.ButtonStyle] = ("Cancel", discord.ButtonStyle.red),
+            button_true: tuple[str, discord.ButtonStyle] = (
+                "Confirm", discord.ButtonStyle.green),
+            button_false: tuple[str, discord.ButtonStyle] = (
+                "Cancel", discord.ButtonStyle.red),
             timeout: float | None = None
     ):
         """
-        Create a new view with two buttons. Clicking the first button will set :py:attr:`value` to True, and
-        the second button sets it to False. Timing out will leave it at None.
+        Create a new view with two buttons. Clicking the first button
+        will set :py:attr:`value` to True, and the second button sets it
+        to False. Timing out will leave it at None.
 
         :param button_true: The first button's label (text) and color.
         :param button_false: The second button's label (text) and color.
-        :param timeout: How long the user has before the buttons disable and the view.wait() finishes.
+        :param timeout: How long the user has before the buttons disable
+         and the view.wait() finishes.
         """
         super().__init__()
         self.value: bool | None = None
         self.timeout: float | None = timeout
 
-        self.add_item(create_simple_button(button_true[0], button_true[1], self.on_button_true))
-        self.add_item(create_simple_button(button_false[0], button_false[1], self.on_button_false))
+        self.add_item(create_simple_button(
+            button_true[0], button_true[1], self.on_button_true
+        ))
+        self.add_item(create_simple_button(
+            button_false[0], button_false[1], self.on_button_false
+        ))
 
     async def on_button_true(self, _: discord.Interaction):
         self.value = True
@@ -68,15 +77,19 @@ class PageView(discord.ui.View):
     @property
     def page_down_style(self) -> tuple[discord.ButtonStyle, bool]:
         """
-        Gives the button style depending on the current page: If decrementing the page index would
-        make it out of bounds, make it gray; else blurple. If loop_around_pages is disabled, gray
-        buttons will be disabled too.
+        Gives the button style depending on the current page: If
+        decrementing the page index would make it out of bounds, make
+        it gray; else blurple. If loop_around_pages is disabled, the
+        gray buttons will be disabled too.
 
-        :return: A tuple of the button color and whether the button should be disabled.
+        :return: A tuple of the button color and whether the button
+         should be disabled.
         """
-        # set color to gray if clicking the button would make the page out of bounds and thus loop around
+        # set color to gray if clicking the button would make the page
+        #  out of bounds and thus loop around.
         return (
-            discord.ButtonStyle.gray if self.page == 0 else discord.ButtonStyle.blurple,
+            (discord.ButtonStyle.gray if self.page == 0
+             else discord.ButtonStyle.blurple),
             self.page == 0 and not self.loop_around_pages
         )
 
@@ -91,7 +104,8 @@ def page_up_style(self) -> tuple[discord.ButtonStyle, bool]:
         """
         # set color to gray if clicking the button would make the page out of bounds and thus loop around
         return (
-            discord.ButtonStyle.gray if self.page == self.max_page_index else discord.ButtonStyle.blurple,
+            (discord.ButtonStyle.gray if self.page == self.max_page_index
+             else discord.ButtonStyle.blurple),
             self.page == self.max_page_index and not self.loop_around_pages
         )
 
@@ -117,7 +131,8 @@ def __init__(
     ):
         super().__init__(timeout=timeout)
         if prepended_buttons is None:
-            # putting [] as default param makes it mutable, shared across instances -_-
+            # putting [] as default param makes it mutable,
+            #  shared across instances -_-
             prepended_buttons = []
         if appended_buttons is None:
             appended_buttons = []
@@ -133,9 +148,17 @@ def __init__(
         page_up_style: tuple[discord.ButtonStyle, bool] = self.page_up_style
         page_down_style: tuple[discord.ButtonStyle, bool] = self.page_down_style
         self.page_down_button = create_simple_button(
-            "◀️", page_down_style[0], self.on_page_down, disabled=page_down_style[1])
+            "◀️",
+            page_down_style[0],
+            self.on_page_down,
+            disabled=page_down_style[1]
+        )
         self.page_up_button = create_simple_button(
-            "▶️", page_up_style[0], self.on_page_up, disabled=page_up_style[1])
+            "▶️",
+            page_up_style[0],
+            self.on_page_up,
+            disabled=page_up_style[1]
+        )
         self.add_item(self.page_down_button)
         self.add_item(self.page_up_button)
 
@@ -162,11 +185,13 @@ async def on_page_up(self, itx: discord.Interaction):
 
     def update_button_colors(self) -> None:
         """
-        Updates the buttons of the view to match the new page number: gray if
-        it's the first/last page, else blurple. Gray buttons will also be
-        disabled if loop_around_pages is False.
+        Updates the buttons of the view to match the new page number:
+        gray if it's the first/last page, else blurple. Gray buttons
+        will also be disabled if loop_around_pages is False.
 
         To change the buttons in the discord message, run `update_page`.
         """
-        self.page_down_button.style, self.page_down_button.disabled = self.page_down_style
-        self.page_up_button.style, self.page_up_button.disabled = self.page_up_style
+        self.page_down_button.style, self.page_down_button.disabled \
+            = self.page_down_style
+        self.page_up_button.style, self.page_up_button.disabled \
+            = self.page_up_style
diff --git a/unit_tests/extensions/help/test_helppages.py b/unit_tests/extensions/help/test_helppages.py
index fb5568c..f8536a9 100644
--- a/unit_tests/extensions/help/test_helppages.py
+++ b/unit_tests/extensions/help/test_helppages.py
@@ -29,7 +29,8 @@ def test_help_pages_sorted():
     sorted_page_keys = sorted(page_keys)
 
     # Assert
-    assert page_keys == sorted_page_keys, "All help pages should be sorted by default."
+    assert page_keys == sorted_page_keys, \
+        "All help pages should be sorted by default."
 
 
 def test_help_pages_attributes():
@@ -43,7 +44,8 @@ def test_help_pages_attributes():
                 invalid_pages.append((page_number, section_name))
 
     assert invalid_pages == [], \
-        "All pages should only have fields that are one of these attributes: title, description, fields, staff_only"
+        ("All pages should only have fields that are one of these "
+         "attributes: title, description, fields, staff_only")
 
 
 def test_embed_lengths():
@@ -57,12 +59,14 @@ def fake_get_command_mention(cmd: str):
     fake_client.get_command_mention = fake_get_command_mention
 
     for page_number, helppage in help_pages.items():
-        page_embed = generate_help_page_embed(helppage, page_number, fake_client)
+        page_embed = generate_help_page_embed(
+            helppage, page_number, fake_client)
 
         potential_issues, _ = get_embed_issues(page_embed)
 
         if potential_issues:
-            issues = f"Page '{page_number}' embed issues:\n- " + "\n- ".join(potential_issues)
+            issues = (f"Page '{page_number}' embed issues:\n"
+                      f"- " + "\n- ".join(potential_issues))
             pytest.fail(issues)
 
 
diff --git a/unit_tests/extensions/reminders/objects/test_reminderobject.py b/unit_tests/extensions/reminders/objects/test_reminderobject.py
index 593c46d..a894c8d 100644
--- a/unit_tests/extensions/reminders/objects/test_reminderobject.py
+++ b/unit_tests/extensions/reminders/objects/test_reminderobject.py
@@ -15,17 +15,23 @@
 # region Helper functions
 def _get_current_time_formatted():
     start_time = datetime.now().astimezone(timezone.utc)
-    start_time = start_time.replace(microsecond=0)  # itx.created_at has no microseconds
+    # itx.created_at has no microseconds
+    start_time = start_time.replace(microsecond=0)
     return start_time
 
 
 def _get_custom_time1():
-    # Set a custom datetime. This is mainly done to make sure the tests for "%I" and "%H" are correctly different.
-    # These tests check whether hour = "4" vs "04" makes a difference. If hour >= 10, using datetime wouldn't
-    # let these tests show their potential.
-    start_time = datetime(year=2025, month=3, day=1, hour=4, minute=1, second=5, microsecond=9265,
-                          tzinfo=timezone.utc)
-    start_time = start_time.replace(microsecond=0)  # itx.created_at has no microseconds
+    # Set a custom datetime. This is mainly done to make sure the tests
+    #  for "%I" and "%H" are correctly different. These tests check
+    #  whether hour = "4" vs "04" makes a difference. If hour >= 10,
+    #  using datetime wouldn't let these tests show their potential.
+    start_time = datetime(
+        year=2025, month=3, day=1,
+        hour=4, minute=1, second=5, microsecond=9265,
+        tzinfo=timezone.utc
+    )
+    start_time = start_time.replace(microsecond=0)
+    # ^ an itx.created_at has no microseconds
     return start_time
 
 # endregion Helper functions
@@ -70,7 +76,8 @@ def test_relative_offset():
     # Act
     reminder_time, _ = asyncio.run(func)
     current_time = current_time.replace(tzinfo=timezone.utc)
-    expected_time = current_time + timedelta(days=500, hours=1, minutes=1, seconds=1)
+    expected_time = (current_time + timedelta(
+        days=500, hours=1, minutes=1, seconds=1))
 
     # Assert
     assert expected_time == reminder_time
@@ -78,16 +85,21 @@ def test_relative_offset():
 
 def test_offset_overflows():
     # Arrange
-    current_time = datetime(year=2000, month=11, day=30, hour=23, minute=59, second=59,
-                            tzinfo=timezone.utc)
+    current_time = datetime(
+        year=2000, month=11, day=30, hour=23, minute=59, second=59,
+        tzinfo=timezone.utc
+    )
     itx = CustomObject(created_at=current_time)
     func = _parse_reminder_time(itx, "1y 20mo 50w 500d 30h 100m 100s")
 
     # Act
     reminder_time, _ = asyncio.run(func)
-    intermediate_expected_time = datetime(year=2003, month=7, day=30, hour=23, minute=59, second=59,
-                                          tzinfo=timezone.utc)
-    expected_time = intermediate_expected_time + timedelta(days=500 + 50 * 7, hours=30, minutes=100, seconds=100)
+    intermediate_expected_time = datetime(
+        year=2003, month=7, day=30, hour=23, minute=59, second=59,
+        tzinfo=timezone.utc
+    )
+    expected_time = intermediate_expected_time + timedelta(
+        days=500 + 50 * 7, hours=30, minutes=100, seconds=100)
 
     # Assert
     assert expected_time == reminder_time
@@ -114,8 +126,10 @@ def test_discord_timestamp():
 def test_iso_date_timeshort_timezone():
     # Arrange
     current_time = _get_custom_time1()
-    date_string1 = current_time.strftime('%Y-%m-%dT%I:%M:%S+0900')  # 2025-03-01T04:01:05+0100
-    date_string2 = current_time.strftime('%Y-%m-%dT%I:%M:%S+1000')  # 2025-03-01T04:01:05+0100
+    date_string1 = current_time.strftime('%Y-%m-%dT%I:%M:%S+0900')
+    # ^ 2025-03-01T04:01:05+0100
+    date_string2 = current_time.strftime('%Y-%m-%dT%I:%M:%S+1000')
+    # ^ 2025-03-01T04:01:05+0100
 
     assert date_string1.endswith("+0900")
     assert date_string2.endswith("+1000")
@@ -130,14 +144,17 @@ def test_iso_date_timeshort_timezone():
     reminder_time2, _ = asyncio.run(func2)
 
     # Assert
-    assert reminder_time1.astimezone() == reminder_time2.astimezone() + timedelta(hours=1)
+    assert reminder_time1.astimezone() \
+           == reminder_time2.astimezone() + timedelta(hours=1)
 
 
 def test_iso_date_timelong_timezone():
     # Arrange
     current_time = _get_custom_time1()
-    date_string1 = current_time.strftime('%Y-%m-%dT%H:%M:%S+0900')  # 2025-03-01T04:01:05+0100
-    date_string2 = current_time.strftime('%Y-%m-%dT%H:%M:%S+1000')  # 2025-03-01T04:01:05+0100
+    date_string1 = current_time.strftime('%Y-%m-%dT%H:%M:%S+0900')
+    # ^ 2025-03-01T04:01:05+0100
+    date_string2 = current_time.strftime('%Y-%m-%dT%H:%M:%S+1000')
+    # ^ 2025-03-01T04:01:05+0100
 
     itx = CustomObject(created_at=current_time)
     func1 = _parse_reminder_time(itx, date_string1)
@@ -148,8 +165,10 @@ def test_iso_date_timelong_timezone():
     reminder_time2, _ = asyncio.run(func2)
 
     # Assert
-    # same as test_..._timeshort(), but using %H instead of %I. This means the time will be padded to 2 characters.
-    assert reminder_time1.astimezone() == reminder_time2.astimezone() + timedelta(hours=1)
+    # same as test_..._timeshort(), but using %H instead of %I. This
+    #  means the time will be padded to 2 characters.
+    assert reminder_time1.astimezone() \
+           == reminder_time2.astimezone() + timedelta(hours=1)
 
 
 def test_iso_date_matches_unix_timestamp():
@@ -157,7 +176,9 @@ def test_iso_date_matches_unix_timestamp():
     date_string1 = "2025-03-01T04:01:05+0000"
     date_string2 = "1740801665"
 
-    itx = CustomObject(created_at=datetime(year=2025, month=1, day=1, tzinfo=timezone.utc))  # before date_string1
+    itx = CustomObject(created_at=datetime(
+        year=2025, month=1, day=1, tzinfo=timezone.utc))
+    # ^ before date_string1
     func1 = _parse_reminder_time(itx, date_string1)
     func2 = _parse_reminder_time(itx, date_string2)
 
@@ -189,7 +210,7 @@ def test_exception_iso_time_timezone():
 def test_exception_american_format_ymd():
     # Arrange
     current_time = _get_current_time_formatted()
-    datetime_string = current_time.strftime('2025/03/03 10:32:33PM')  # eg. 2025
+    datetime_string = current_time.strftime('%Y/%m/%d %h:%M:%SPM')
     itx = CustomObject(created_at=current_time)
     func = _parse_reminder_time(itx, datetime_string)
 
@@ -201,7 +222,7 @@ def test_exception_american_format_ymd():
 def test_exception_american_format_dmy():
     # Arrange
     current_time = _get_current_time_formatted()
-    datetime_string = current_time.strftime('03/03/2025 10:32:33PM')  # eg. 2025
+    datetime_string = current_time.strftime('%d/%m/%Y %h:%M:%SPM')
     itx = CustomObject(created_at=current_time)
     func = _parse_reminder_time(itx, datetime_string)
 
@@ -213,7 +234,7 @@ def test_exception_american_format_dmy():
 def test_exception_american_format_with_t_ymd():
     # Arrange
     current_time = _get_current_time_formatted()
-    datetime_string = current_time.strftime('2025/03/03T10:32:33PM')  # eg. 2025
+    datetime_string = current_time.strftime('%Y/%m/%dT%h:%M:%SPM')
     itx = CustomObject(created_at=current_time)
     func = _parse_reminder_time(itx, datetime_string)
 
@@ -225,7 +246,7 @@ def test_exception_american_format_with_t_ymd():
 def test_exception_american_format_with_t_dmy():
     # Arrange
     current_time = _get_current_time_formatted()
-    datetime_string = current_time.strftime('03/03/2025T10:32:33PM')  # eg. 2025
+    datetime_string = current_time.strftime('%d/%m/%YT%h:%M:%SPM')
     itx = CustomObject(created_at=current_time)
     func = _parse_reminder_time(itx, datetime_string)
 
@@ -249,7 +270,7 @@ def test_malformed_year():
 def test_malformed_year_month():
     # Arrange
     current_time = _get_current_time_formatted()
-    datetime_string = current_time.strftime('%Y-%M')  # eg. 2025
+    datetime_string = current_time.strftime('%Y-%M')  # eg. 2025-03
     itx = CustomObject(created_at=current_time)
     func = _parse_reminder_time(itx, datetime_string)
 
@@ -261,14 +282,17 @@ def test_malformed_year_month():
 def test_exception_iso_date_timeshort():  # no timezone
     # Arrange
     current_time = _get_custom_time1()
-    datetime_string = current_time.strftime('%Y-%m-%dT%I:%M')  # 2025-03-01T4:01:05
+    datetime_string = current_time.strftime('%Y-%m-%dT%I:%M')
+    # ^ 2025-03-01T4:01:05
     itx = CustomObject(created_at=current_time)
     func = _parse_reminder_time(itx, datetime_string)
 
     # Assert
     with pytest.raises(TimestampParseException):
-        # It should follow the path of YYYY-MM-DD T HH:MM or HH:MM:SS, requiring a timezone +0000.
-        # The timezone isn't provided by the strftime, and as such the code should run into an exception.
+        # It should follow the path of YYYY-MM-DD T HH:MM or HH:MM:SS,
+        #  requiring a timezone +0000. The timezone isn't provided by
+        #  the strftime, and as such the code should run into an
+        #  exception.
         # This exception must be handled by the command in the Cog instead.
         reminder_time, _ = asyncio.run(func)
 
@@ -276,13 +300,15 @@ def test_exception_iso_date_timeshort():  # no timezone
 def test_exception_iso_date_timelong():  # no timezone
     # Arrange
     current_time = _get_custom_time1()
-    datetime_string = current_time.strftime('%Y-%m-%dT%H:%M')  # 2025-03-01T4:01:05
+    datetime_string = current_time.strftime('%Y-%m-%dT%H:%M')
+    # ^ 2025-03-01T4:01:05
     itx = CustomObject(created_at=current_time)
     func = _parse_reminder_time(itx, datetime_string)
 
     # Assert
     with pytest.raises(TimestampParseException):
-        # same as test_..._timeshort(), but using %H instead of %I. This means the time will be padded to 2 characters.
+        # same as test_..._timeshort(), but using %H instead of %I. This
+        #  means the time will be padded to 2 characters.
         reminder_time, _ = asyncio.run(func)
 
 
@@ -295,16 +321,17 @@ def test_exception_iso_date():
 
     # Assert
     with pytest.raises(AttributeError):
-        # It should follow the path of YYYY-MM-DD, asking for a time of day when you want to be reminded.
-        # This would be directly handled using itx.response, which the test doesn't provide, resulting in
-        # an Attribute error.
+        # It should follow the path of YYYY-MM-DD, asking for a time
+        #  of day when you want to be reminded. This would be directly
+        #  handled using itx.response, which the test doesn't provide,
+        #  resulting in an Attribute error.
         reminder_time, _ = asyncio.run(func)
 
 
 def test_exception_12_hour_clock():
     # Arrange
     current_time = _get_current_time_formatted()
-    datetime_string = current_time.strftime('2025-03-17T10:32:33PM')
+    datetime_string = current_time.strftime('%Y-%m-%dT%h:%M:%SPM')
     itx = CustomObject(created_at=current_time)
     func = _parse_reminder_time(itx, datetime_string)
 
@@ -316,7 +343,7 @@ def test_exception_12_hour_clock():
 def test_exception_12_hour_clock_timezone():
     # Arrange
     current_time = _get_current_time_formatted()
-    datetime_string = current_time.strftime('2025-03-17T10:32:33PM+0000')
+    datetime_string = current_time.strftime('%Y-%m-%dT%h:%M:%SPM%z')
     itx = CustomObject(created_at=current_time)
     func = _parse_reminder_time(itx, datetime_string)
 
diff --git a/unit_tests/extensions/settings/test_attributes.py b/unit_tests/extensions/settings/test_attributes.py
index 0597fb3..1de11cc 100644
--- a/unit_tests/extensions/settings/test_attributes.py
+++ b/unit_tests/extensions/settings/test_attributes.py
@@ -2,8 +2,12 @@
 
 import typing
 
-from extensions.settings.objects import ServerAttributes, ServerAttributeIds, EnabledModules, AttributeKeys
-# from extensions.settings.objects.server_settings import convert_old_settings_to_new
+from extensions.settings.objects import (
+    ServerAttributes,
+    ServerAttributeIds,
+    EnabledModules,
+    AttributeKeys,
+)
 
 
 inputs = [
@@ -60,21 +64,6 @@
 ]
 
 
-# def test_migrate_output():
-#     # Honestly, I don't even know what it's supposed to output.
-#     for elem in inputs:
-#         guild_id, attribute_ids = convert_old_settings_to_new(elem)
-#
-#         # Remove unused keys
-#         del elem["_id"]
-#         if "vcNoMic" in elem:
-#             del elem["vcNoMic"]
-#
-#         # Remove extracted key
-#         del elem["guild_id"]
-#         assert len(attribute_ids) == len(elem)
-
-
 def test_matching_keys():
     # Arrange
     at = ServerAttributes.__annotations__.keys()
diff --git a/unit_tests/resources/customs/test_progressbar.py b/unit_tests/resources/customs/test_progressbar.py
index 62b1b5b..7a42ba9 100644
--- a/unit_tests/resources/customs/test_progressbar.py
+++ b/unit_tests/resources/customs/test_progressbar.py
@@ -22,7 +22,8 @@ def starts_with_light_blue(message: str) -> bool:
 
 
 def get_original_message(message: str) -> str:
-    message = message.split("[INFO]: ", 2)[1]  # remove unimportant timestamps and stuff
+    # remove unimportant timestamps and stuff
+    message = message.split("[INFO]: ", 2)[1]
     message = message.split("\x1b[0m", 1)[0]
     return message
 
@@ -38,9 +39,11 @@ def test_progress(caplog):
     message = caplog.messages[0]
 
     # Assert
-    assert starts_with_light_blue(message), f"Message '{message}' was not light blue."
-    assert ends_with_color_reset_and_carriage_return(message), (f"Message '{message}' did not end with a "
-                                                                f"color reset and carriage return.")
+    assert starts_with_light_blue(message), \
+        f"Message '{message}' was not light blue."
+    assert ends_with_color_reset_and_carriage_return(message), \
+        (f"Message '{message}' did not end with a color reset and "
+         f"carriage return.")
 
     # Act
     message = get_original_message(message)
@@ -60,8 +63,10 @@ def test_step(caplog):
     message = caplog.messages[0]
 
     # Assert
-    assert starts_with_green(message), f"Message '{message}' was not green."
-    assert ends_with_color_reset(message), f"Message '{message}' did not end with a color reset."
+    assert starts_with_green(message), \
+        f"Message '{message}' was not green."
+    assert ends_with_color_reset(message), \
+        f"Message '{message}' did not end with a color reset."
 
     # Act
     message = get_original_message(message)
@@ -81,8 +86,10 @@ def test_progress_newline(caplog):
     message = caplog.messages[0]
 
     # Assert
-    assert starts_with_light_blue(message), f"Message '{message}' was not light blue."
-    assert ends_with_color_reset(message), f"Message '{message}' did not end with a color reset."
+    assert starts_with_light_blue(message), \
+        f"Message '{message}' was not light blue."
+    assert ends_with_color_reset(message), \
+        f"Message '{message}' did not end with a color reset."
 
     # Act
     message = get_original_message(message)
@@ -102,9 +109,11 @@ def test_step_no_newline(caplog):
     message = caplog.messages[0]
 
     # Assert
-    assert starts_with_green(message), f"Message '{message}' was not green."
-    assert ends_with_color_reset_and_carriage_return(message), (f"Message '{message}' did not end with a "
-                                                                f"color reset and carriage return.")
+    assert starts_with_green(message), \
+        f"Message '{message}' was not green."
+    assert ends_with_color_reset_and_carriage_return(message), \
+        (f"Message '{message}' did not end with a color reset and "
+         f"carriage return.")
 
     # Act
     message = get_original_message(message)
@@ -125,8 +134,10 @@ def test_step_step(caplog):
     message = caplog.messages[0]
 
     # Assert
-    assert starts_with_green(message), f"Message '{message}' was not green."
-    assert ends_with_color_reset(message), f"Message '{message}' did not end with a color reset."
+    assert starts_with_green(message), \
+        f"Message '{message}' was not green."
+    assert ends_with_color_reset(message), \
+        f"Message '{message}' did not end with a color reset."
 
     # Act
     message = get_original_message(message)
@@ -148,9 +159,11 @@ def test_step_progress(caplog):
     message = caplog.messages[0]
 
     # Assert
-    assert starts_with_light_blue(message), f"Message '{message}' was not green."
-    assert ends_with_color_reset_and_carriage_return(message), (f"Message '{message}' did not end with a "
-                                                                f"color reset and carriage return.")
+    assert starts_with_light_blue(message), \
+        f"Message '{message}' was not green."
+    assert ends_with_color_reset_and_carriage_return(message), \
+        (f"Message '{message}' did not end with a color reset and "
+         f"carriage return.")
 
     # Act
     message = get_original_message(message)
@@ -172,9 +185,11 @@ def test_progress_progress(caplog):
     message = caplog.messages[0]
 
     # Assert
-    assert starts_with_light_blue(message), f"Message '{message}' was not green."
-    assert ends_with_color_reset_and_carriage_return(message), (f"Message '{message}' did not end with a "
-                                                                f"color reset and carriage return.")
+    assert starts_with_light_blue(message), \
+        f"Message '{message}' was not green."
+    assert ends_with_color_reset_and_carriage_return(message), \
+        (f"Message '{message}' did not end with a color reset and "
+         f"carriage return.")
 
     # Act
     message = get_original_message(message)
@@ -195,8 +210,12 @@ def test_progress_step(caplog):
     message = caplog.messages[0]
 
     # Assert
-    assert starts_with_green(message), f"Message '{message}' was not green."
-    assert ends_with_color_reset(message), f"Message '{message}' did not end with a color reset and carriage return."
+    # todo: make a function "assert progress" to test both two \/
+    assert starts_with_green(message), \
+        f"Message '{message}' was not green."
+    assert ends_with_color_reset(message), \
+        (f"Message '{message}' did not end with a color reset and "
+         f"carriage return.")
 
     # Act
     message = get_original_message(message)
diff --git a/unit_tests/resources/utils/timeparser/test_parse_time_string.py b/unit_tests/resources/utils/timeparser/test_parse_time_string.py
index f20bf9a..9f03e2b 100644
--- a/unit_tests/resources/utils/timeparser/test_parse_time_string.py
+++ b/unit_tests/resources/utils/timeparser/test_parse_time_string.py
@@ -1,6 +1,10 @@
 import pytest
 
-from resources.utils.timeparser import TimeParser, MissingQuantityException, MissingUnitException
+from resources.utils.timeparser import (
+    TimeParser,
+    MissingQuantityException,
+    MissingUnitException,
+)
 
 
 # region Correct functionality
@@ -16,7 +20,8 @@ def test_parse_simple():
 def test_parse_longer():
     # Assign
     parse = TimeParser.parse_time_string("2days21hours10minutes4seconds")
-    expected = [(2.0, 'days'), (21.0, 'hours'), (10.0, 'minutes'), (4.0, 'seconds')]
+    expected = [(2.0, 'days'), (21.0, 'hours'),
+                (10.0, 'minutes'), (4.0, 'seconds')]
 
     # Assert
     assert expected == parse
@@ -33,18 +38,26 @@ def test_parse_decimal():
 
 def test_parse_decimals_longer():
     # Assign
-    parse = TimeParser.parse_time_string("2.123days21.53hours10.3242minutes4.432seconds")
-    expected = [(2.123, 'days'), (21.53, 'hours'), (10.3242, 'minutes'), (4.432, 'seconds')]
+    parse = TimeParser.parse_time_string(
+        "2.123days"
+        "21.53hours"
+        "10.3242minutes"
+        "4.432seconds"
+    )
+    expected = [(2.123, 'days'), (21.53, 'hours'),
+                (10.3242, 'minutes'), (4.432, 'seconds')]
 
     # Assert
     assert expected == parse
 
 
 def test_parse_overflow():
-    # this function is not particularly interesting since it should just split them, but oh well.
+    # this function is not particularly interesting since it should
+    #  just split them, but oh well.
     # Assign
     parse = TimeParser.parse_time_string("50days50hours100minutes400seconds")
-    expected = [(50.0, 'days'), (50.0, 'hours'), (100.0, 'minutes'), (400.0, 'seconds')]
+    expected = [(50.0, 'days'), (50.0, 'hours'),
+                (100.0, 'minutes'), (400.0, 'seconds')]
 
     # Assert
     assert expected == parse
diff --git a/unit_tests/resources/utils/timeparser/test_shrink_time_terms.py b/unit_tests/resources/utils/timeparser/test_shrink_time_terms.py
index f8fe16d..6edeee6 100644
--- a/unit_tests/resources/utils/timeparser/test_shrink_time_terms.py
+++ b/unit_tests/resources/utils/timeparser/test_shrink_time_terms.py
@@ -43,8 +43,12 @@ def test_parse_all():
 def test_exception_invalid_time_term():
     # Arrange
     unknown_term = "NONEXISTENT TIME TERM"
-    assert unknown_term not in [val for key in TIMETERMS for val in TIMETERMS[key]]
-    terms = [
+    assert unknown_term not in [
+        val
+        for key in TIMETERMS
+        for val in TIMETERMS[key]
+    ]
+    terms: list[tuple[float, str]] = [
         (1, unknown_term)
     ]
     # Assert
diff --git a/unit_tests/test_main.py b/unit_tests/test_main.py
index dcddb4c..fc57732 100644
--- a/unit_tests/test_main.py
+++ b/unit_tests/test_main.py
@@ -4,7 +4,8 @@
 
 from main import EXTENSIONS
 
-# todo: add more tests. For example for expected crashes, and just overall testing each function.
+# todo: add more tests. For example for expected crashes, and just
+#  overall testing each function.
 
 
 def test_warn_all_modules_enabled():
@@ -27,7 +28,8 @@ def test_warn_all_modules_enabled():
             existing_modules_not_in_active_extensions.append(extension)
 
     if existing_modules_not_in_active_extensions:
-        msg = "Check that all modules are enabled: " + ', '.join(existing_modules_not_in_active_extensions)
+        msg = ("Check that all modules are enabled: "
+               + ', '.join(existing_modules_not_in_active_extensions))
         warn(msg)
 
 
@@ -49,5 +51,7 @@ def test_all_modules_exist():
         else:
             misnamed_extensions.append(extension)
 
-    assert not missing_extensions, "Missing module.py for these extensions!"
-    assert not misnamed_extensions, "Missing extension folder for these extensions!"
+    assert not missing_extensions, \
+        "Missing module.py for these extensions!"
+    assert not misnamed_extensions, \
+        "Missing extension folder for these extensions!"
diff --git a/unit_tests/utils/embeds.py b/unit_tests/utils/embeds.py
index f6b71a9..f2f7f6c 100644
--- a/unit_tests/utils/embeds.py
+++ b/unit_tests/utils/embeds.py
@@ -27,40 +27,57 @@ def get_embed_issues(embed: discord.Embed) -> tuple[list[str], int]:
         if key == "title":
             total_characters += len(value)
             if len(value) > 256:
-                issues.append(f"Title length exceeds 256 characters (is '{len(value)}').")
+                issues.append(
+                    f"Title length exceeds 256 characters (is '{len(value)}')."
+                )
 
         elif key == "description":
             total_characters += len(value)
             if len(value) > 4096:
-                issues.append(f"Description length exceeds 4096 characters (is '{len(value)}').")
+                issues.append(
+                    f"Description length exceeds 4096 characters "
+                    f"(is '{len(value)}')."
+                )
 
         elif key == "fields":
             value: list[d_embed.EmbedField]
             if len(value) > 25:
-                issues.append(f"Embed contains more than 25 fields (is '{len(value)}').")
+                issues.append(
+                    f"Embed contains more than 25 fields (is '{len(value)}')."
+                )
             for field in value:
                 for field_key, field_value in field.items():
                     if field_key == "type":
                         if field_value != "rich":
-                            issues.append(f"Field type '{field_value}' (for field with name '{field['name']}') "
-                                          f"is not supported.")
+                            issues.append(
+                                f"Field type '{field_value}' (for field with "
+                                f"name '{field['name']}') is not supported."
+                            )
                     if field_key == "name":
                         total_characters += len(field_value)
                         if len(field_value) > 256:
-                            issues.append(f"Field name '{field_value}' exceeds 256 characters "
-                                          f"(is '{len(field_value)}').")
+                            issues.append(
+                                f"Field name '{field_value}' exceeds 256 "
+                                f"characters (is '{len(field_value)}')."
+                            )
                     if field_key == "value":
                         total_characters += len(field_value)
                         if len(field_value) > 1024:
-                            issues.append(f"Field value for field with name '{field['name']}' exceeds 1024 characters "
-                                          f"(is '{len(field_value)}').")
+                            issues.append(
+                                f"Field value for field with name "
+                                f"'{field['name']}' exceeds 1024 characters "
+                                f"(is '{len(field_value)}')."
+                            )
 
         elif key == "footer":
             value: d_embed.EmbedFooter
             text = value["text"]
             total_characters += len(text)
             if len(text) > 2048:
-                issues.append(f"Footer length exceeds 2048 characters (is '{len(text)}').")
+                issues.append(
+                    f"Footer length exceeds 2048 characters "
+                    f"(is '{len(text)}')."
+                )
 
         elif key == "author":
             value: d_embed.EmbedAuthor
@@ -68,10 +85,13 @@ def get_embed_issues(embed: discord.Embed) -> tuple[list[str], int]:
                 if author_key == "name":
                     total_characters += len(author_value)
                     if len(author_value) > 256:
-                        issues.append(f"Author name '{author_value}' exceeds 256 characters "
-                                      f"(is '{len(author_value)}').")
+                        issues.append(
+                            f"Author name '{author_value}' exceeds 256 "
+                            f"characters (is '{len(author_value)}')."
+                        )
 
     if total_characters > 6000:
-        issues.append(f"Total character count of embed exceeds 6000 characters (is '{total_characters}').")
+        issues.append(f"Total character count of embed exceeds 6000 "
+                      f"characters (is '{total_characters}').")
 
     return issues, total_characters

From 6c5c66fcce19a74ec1e21220aaaaf4ad917bf533 Mon Sep 17 00:00:00 2001
From: MysticMia <65396931+MysticMia@users.noreply.github.com>
Date: Thu, 1 May 2025 14:19:34 +0200
Subject: [PATCH 05/26] Move generate_roll from cogs/funaddons.py to new
 roll.py file, and add tests for it and its helper functions.

---
 extensions/addons/cogs/funaddons.py           | 196 ++++++------------
 extensions/addons/roll.py                     |  83 ++++++++
 .../funaddons/roll/test_generate_roll.py      |  49 +++++
 .../funaddons/roll/test_parse_dice_roll.py    |  46 ++++
 .../funaddons/roll/test_product_of_list.py    |  20 ++
 .../roll/test_validate_dice_and_faces.py      |  28 +++
 6 files changed, 293 insertions(+), 129 deletions(-)
 create mode 100644 extensions/addons/roll.py
 create mode 100644 unit_tests/extensions/addons/funaddons/roll/test_generate_roll.py
 create mode 100644 unit_tests/extensions/addons/funaddons/roll/test_parse_dice_roll.py
 create mode 100644 unit_tests/extensions/addons/funaddons/roll/test_product_of_list.py
 create mode 100644 unit_tests/extensions/addons/funaddons/roll/test_validate_dice_and_faces.py

diff --git a/extensions/addons/cogs/funaddons.py b/extensions/addons/cogs/funaddons.py
index 8149743..7e53feb 100644
--- a/extensions/addons/cogs/funaddons.py
+++ b/extensions/addons/cogs/funaddons.py
@@ -7,6 +7,7 @@
 import discord.app_commands as app_commands
 import discord.ext.commands as commands
 
+from extensions.addons.roll import generate_roll
 from resources.checks import MissingAttributesCheckFailure
 from resources.customs import Bot
 
@@ -32,83 +33,6 @@ def _product_of_list(mult_list: list[T]) -> T:
     return a
 
 
-def _generate_roll(query: str) -> list[int]:
-    """
-    A helper command to generate a dice roll from a dice string
-    representation "2d6"
-
-    :param query: The string representing the dice roll.
-    :return: A list of outcomes following from the dice roll. 2d6 will return
-     a list with 2 integers, ranging from 1-6.
-    """
-    # print(query)
-    temp: list[str | int] = query.split("d")
-    # 2d4 = ["2","4"]
-    # 2d3d4 = ["2","3","4"] (huh?)
-    # 4 = 4
-    # [] (huh?)
-    if len(temp) > 2:
-        raise ValueError("Can't have more than 1 'd' in the "
-                         "query of your die!")
-    if len(temp) == 1:
-        try:
-            temp[0] = int(temp[0])
-        except ValueError:
-            raise TypeError(f"You can't do operations with '{temp[0]}'")
-        return [temp[0]]
-    if len(temp) < 1:
-        raise ValueError(f"I couldn't understand what you meant with "
-                         f"{query} ({str(temp)})")
-    dice = temp[0]
-    negative = dice.startswith("-")
-    if negative:
-        dice = dice.replace("-", "", 1)
-    faces = ""
-    for x in temp[1]:
-        if x in "0123456789":
-            faces += x
-        else:
-            break
-
-    # see what hasn't been parsed yet
-    remainder = temp[1][len(faces):]
-    if len(remainder) > 0:
-        raise TypeError(
-            f"Idk what happened, but you probably filled something in "
-            f"incorrectly:\n"
-            f"I parsed dice roll '{query}' into a roll of '{dice}' dice "
-            f"with '{faces}' faces, but got left with '{remainder}'. "
-            f"({dice}d{faces} and {remainder})"
-        )
-
-    try:
-        dice = int(dice)
-    except ValueError:
-        raise ValueError(f"You have to roll a numerical number of dice! "
-                         f"(You tried to roll '{dice}' dice)")
-    try:
-        faces = int(faces)
-    except ValueError:
-        raise TypeError(
-            f"You have to roll a die with a numerical number of faces! "
-            f"(You tried to roll {dice} dice with "
-            f"'{faces}' faces)")
-    if dice >= 1000000:
-        raise OverflowError(
-            f"Sorry, if I let you roll `{dice:,}` dice, the universe will "
-            f"implode, and Rina will stop responding to commands. "
-            f"Please stay below 1 million dice...")
-    if faces >= 1000000:
-        raise OverflowError(
-            f"Uh.. At that point, you're basically rolling a sphere. Even "
-            f"earth has less than `{faces:,}` faces. Please bowl with a "
-            f"sphere of fewer than 1 million faces...")
-
-    # turn a boolean 0 or 1 into a 1 or -1
-    negativity: int = (negative * -2 + 1)
-    return [negativity * random.randint(1, faces) for _ in range(dice)]
-
-
 async def _handle_awawa_reaction(
         message: discord.Message, awawawa_emoji: discord.Emoji,
 ) -> bool:
@@ -147,6 +71,61 @@ async def _handle_awawa_reaction(
     return False
 
 
+async def _get_dice_roll_output(dice, faces, mod):
+    """Helper to get the text output for a simple /roll."""
+    rolls = []
+    message_too_long = False
+    for _ in range(dice):
+        rolls.append(random.randint(1, faces))
+    dice_info = (f"I rolled {dice:,} di{'c' * (dice != 1)}e with "
+                 f"{faces:,} face{'s' * (faces > 1)}")
+    modifier_info = ""
+    if mod is None:
+        if dice == 1:
+            out = f": {str(sum(rolls))}"
+        else:
+            out = (f":\n"
+                   f"{' + '.join([str(roll) for roll in rolls])}  =  "
+                   f"{str(sum(rolls))}")
+    else:
+        modifier_info = f" and a modifier of {mod}"
+        out = (f":\n"
+               f"({' + '.join([str(roll) for roll in rolls])}) + {mod}  =  "
+               f"{str(sum(rolls) + mod)}")
+    if len(dice_info) + len(out) > 300:
+        out = (f":\n"
+               f"With this many numbers, I've simplified it a little. "
+               f"You rolled `{sum(rolls) + (mod or 0):,}`.")
+        details = await _simplify_roll_output(rolls)
+        if len(details) > 1500:
+            details = ""
+        elif len(details) > 300:
+            message_too_long = True
+        out += "\n" + details
+    elif len(out) > 300:
+        message_too_long = True
+    output_string = dice_info + modifier_info + out
+    return output_string, message_too_long
+
+
+async def _simplify_roll_output(rolls: list[int]) -> str:
+    """Helper to represent dice rolls into (eyes, count) entries."""
+    roll_db = {}
+    for roll in rolls:
+        try:
+            roll_db[roll] += 1
+        except KeyError:
+            roll_db[roll] = 1
+    # order dict by the eyes rolled: {"eyes":"count",1:4,2:1,3:4,4:1}
+    # x.items() gives a list of tuples [(1,4), (2,1), (3,4), (4,1)] that is then sorted b
+    # the first item in the tuple.
+    roll_db = dict(sorted([x for x in roll_db.items()]))
+    details = "You rolled "
+    for roll in roll_db:
+        details += f"'{roll}'x{roll_db[roll]}, "
+    return details
+
+
 class FunAddons(commands.Cog):
     def __init__(self, client: Bot):
         self.client = client
@@ -260,51 +239,11 @@ async def roll(
             faces: app_commands.Range[int, 1, 999999],
             public: bool = False, mod: int | None = None, advanced: str | None = None
     ):
-        hide = False
         if advanced is None:
             await itx.response.defer(ephemeral=not public)
-            rolls = []
-            for _ in range(dice):
-                rolls.append(random.randint(1, faces))
-
-            if mod is None:
-                if dice == 1:
-                    out = (f"I rolled {dice} die with {faces} face{'s' * (faces > 1)}: "
-                           f"{str(sum(rolls))}")
-                else:
-                    out = (f"I rolled {dice} di{'c' * (dice > 1)}e with {faces} face{'s' * (faces > 1)}:\n"
-                           f"{' + '.join([str(roll) for roll in rolls])}  =  {str(sum(rolls))}")
-            else:
-                out = (f"I rolled {dice} {'die' if dice == 0 else 'dice'} with {faces} face{'s' * (faces > 1)} "
-                       f"and a modifier of {mod}:\n"
-                       f"({' + '.join([str(roll) for roll in rolls])}) + {mod}  =  {str(sum(rolls) + mod)}")
-            if len(out) > 300:
-                out = (f"I rolled {dice:,} {'die' if dice == 0 else 'dice'} with {faces:,} face{'s' * (faces > 1)} "
-                       f"and a modifier of {(mod or 0):,}") * (mod is not None) + \
-                      (f":\n"
-                       f"With this many numbers, I've simplified it a little. You rolled "
-                       f"`{sum(rolls) + (mod or 0):,}`.")
-                roll_db = {}
-                for roll in rolls:
-                    try:
-                        roll_db[roll] += 1
-                    except KeyError:
-                        roll_db[roll] = 1
-                # order dict by the eyes rolled: {"eyes":"count",1:4,2:1,3:4,4:1}
-                # x.items() gives a list of tuples [(1,4), (2,1), (3,4), (4,1)] that is then sorted b
-                # the first item in the tuple.
-                roll_db = dict(sorted([x for x in roll_db.items()]))
-                details = "You rolled "
-                for roll in roll_db:
-                    details += f"'{roll}'x{roll_db[roll]}, "
-                if len(details) > 1500:
-                    details = ""
-                elif len(details) > 300:
-                    hide = True
-                out = out + "\n" + details
-            elif len(out) > 300:
-                hide = True
-            if hide:
+            out, delete_original = await _get_dice_roll_output(
+                dice, faces, mod)
+            if delete_original:
                 await itx.delete_original_response()
             await itx.followup.send(out, ephemeral=not public)
         else:
@@ -331,14 +270,12 @@ async def roll(
                 multiply.append(add_section.split('*'))
             # print("multiply:  ",multiply)
             try:
-                result = [[sum(_generate_roll(query)) for query in mult_section] for mult_section in multiply]
+                result = [[sum(generate_roll(query)) for query in mult_section] for mult_section in multiply]
             except (TypeError, ValueError, OverflowError) as ex:
-                ex = repr(ex).split("(", 1)
-                ex_type = ex[0]
-                ex_message = ex[1][1:-1]
+                ex_type = ex.__class__.__name__
                 if public:
                     await itx.delete_original_response()
-                await itx.followup.send(f"Wasn't able to roll your dice!\n  {ex_type}: {ex_message}", ephemeral=True)
+                await itx.followup.send(f"Wasn't able to roll your dice!\n  {ex_type}: {ex}", ephemeral=True)
                 return
             # print("result:    ",result)
             out = ["Input:  " + advanced]
@@ -349,14 +286,15 @@ async def roll(
             out += [str(sum([_product_of_list(section) for section in result]))]
             output = discord.utils.escape_markdown('\n= '.join(out))
             if len(output) >= 1950:
-                output = ("Your result was too long! I couldn't send it. Try making your rolls a bit smaller, "
-                          "perhaps by splitting it into multiple operations...")
+                output = ("Your result was too long! I couldn't send it. "
+                          "Try making your rolls a bit smaller, perhaps by "
+                          "splitting it into multiple operations...")
             if len(output) >= 500:
-                hide = True
+                public = False
             try:
                 await itx.followup.send(output, ephemeral=not public)
             except discord.errors.NotFound:
-                if hide:
+                if public:
                     await itx.delete_original_response()
                 await itx.user.send(f"Couldn't send you the result of your roll because it took too long "
                                     f"or something. Here you go: \n{output}")
diff --git a/extensions/addons/roll.py b/extensions/addons/roll.py
new file mode 100644
index 0000000..8b000eb
--- /dev/null
+++ b/extensions/addons/roll.py
@@ -0,0 +1,83 @@
+import collections.abc
+import random
+
+
+def _parse_dice_roll(query: str) -> tuple[int, int | None]:
+    """Split the query into dice and faces components."""
+    # print(query)
+    parts: list[str | int] = query.split("d")
+    # 2d4 = ["2","4"]
+    # 2d3d4 = ["2","3","4"] (huh?)
+    # 4 = 4
+    # [] (huh?)
+    if len(parts) > 2:
+        raise ValueError("Can't have more than 1 'd' in the "
+                         "query of your die!")
+    if len(parts) == 1:
+        return _parse_number("dice", parts[0]), None
+    if len(parts) < 1:
+        raise ValueError(f"I couldn't understand what you meant with "
+                         f"{query} ({str(parts)})")
+    return (
+        _parse_number("dice", parts[0]),
+        _parse_number("faces", parts[1])
+    )
+
+
+def _parse_number(source: str, value: str) -> int:
+    """Parse a string to integer with error handling."""
+    try:
+        return int(value)
+    except ValueError:
+        raise ValueError(f"Invalid number format for {source}: {value}")
+
+
+def _validate_dice_and_faces(
+        dice: int, faces: int
+) -> bool:
+    """Confirm the values of the dice are within range"""
+    if faces <= 0:
+        raise ValueError("Faces count must be positive")
+    if dice >= 1000000:
+        raise OverflowError(
+            f"Sorry, if I let you roll `{dice:,}` dice, the universe will "
+            f"implode, and Rina will stop responding to commands. "
+            f"Please stay below 1 million dice..."
+        )
+    if faces >= 1000000:
+        raise OverflowError(
+            f"Uh.. At that point, you're basically rolling a sphere. Even "
+            f"earth has less than `{faces:,}` faces. Please bowl with a "
+            f"sphere of fewer than 1 million faces..."
+        )
+
+    return True
+
+
+def generate_roll(query: str) -> collections.abc.Generator[int]:
+    """
+    A helper command to generate a dice roll from a dice string
+    representation "2d6"
+
+    :param query: The string representing the dice roll.
+    :return: A list of outcomes following from the dice roll. 2d6 will
+     return a list with 2 integers, ranging from 1-6.
+    :raise ValueError: If the given query does not match the format
+     "1d2" where "1" and "2" are numbers; or if "2" is negative.
+    :raise OverflowError: If the number of dice or faces
+     exceeds 1 000 000.
+    """
+    dice, faces = _parse_dice_roll(query)  # raises ValueError
+    if faces is None:
+        # it's a constant, not a dice roll
+        yield dice
+        return
+    negative = dice < 0
+    if negative:
+        dice = -dice
+
+    _validate_dice_and_faces(dice, faces)  # raises ValueError, OverflowError
+
+    negativity_modifier = -1 if negative else 1
+    for _ in range(dice):
+        yield negativity_modifier * random.randint(1, faces)
diff --git a/unit_tests/extensions/addons/funaddons/roll/test_generate_roll.py b/unit_tests/extensions/addons/funaddons/roll/test_generate_roll.py
new file mode 100644
index 0000000..8360442
--- /dev/null
+++ b/unit_tests/extensions/addons/funaddons/roll/test_generate_roll.py
@@ -0,0 +1,49 @@
+import pytest
+from unittest import mock
+from extensions.addons.roll import generate_roll
+import random
+# AI wrote the template for this file
+# (mainly me not yet knowing how best to mock other libraries and
+#  functions. I must say, this is pretty neat.)
+
+
+@pytest.mark.parametrize("query, expected", [
+    ("5", [5]),  # Constant value
+    ("-5", [-5]),  # Negative constant value
+    ("2d6", [1, 1]),  # Default random values
+    ("1d20", [1]),  # Single roll
+    ("-3d6", [-1, -1, -1]),  # Negative dice
+    ("0d6", []),  # Zero dice
+])
+def test_generate_roll_cases(query, expected):
+    with mock.patch.object(random, 'randint', return_value=1):
+        result = list(generate_roll(query))
+        assert result == expected
+
+
+@pytest.mark.parametrize("query, dice_count", [
+    ("2d6", 2),
+    ("1d100", 1),
+    ("-4d6", 4),
+])
+def test_roll_count(query, dice_count):
+    with mock.patch.object(random, 'randint', return_value=1):
+        result = list(generate_roll(query))
+        assert len(result) == dice_count
+
+
+@pytest.mark.parametrize("query, min_val, max_val", [
+    ("2d6", 1, 6),
+    ("1d20", 1, 20),
+    ("-3d6", -6, -1),  # Range adjusted for negative
+])
+def test_roll_range(query, min_val, max_val):
+    with mock.patch.object(random, 'randint', return_value=1):
+        result = list(generate_roll(query))
+        assert all(min_val <= x <= max_val for x in result)
+
+
+def test_negative_dice_negates_values():
+    with mock.patch.object(random, 'randint', return_value=5):
+        result = list(generate_roll("-2d6"))
+        assert result == [-5, -5]
diff --git a/unit_tests/extensions/addons/funaddons/roll/test_parse_dice_roll.py b/unit_tests/extensions/addons/funaddons/roll/test_parse_dice_roll.py
new file mode 100644
index 0000000..f8c857b
--- /dev/null
+++ b/unit_tests/extensions/addons/funaddons/roll/test_parse_dice_roll.py
@@ -0,0 +1,46 @@
+import pytest
+from extensions.addons.roll import _parse_dice_roll
+# AI wrote the template for this file :)
+# (mainly me not yet knowing how to write parameterized tests)
+# (Woah, especially parameterized exceptions. Neat.)
+
+
+@pytest.mark.parametrize("query, expected", [
+    ("2d6", (2, 6)),
+    ("1d20", (1, 20)),
+    ("0d6", (0, 6)),  # Zero dice
+    ("-3d6", (-3, 6)),  # Negative dice
+    ("1d-2", (1, -2)),  # Negative faces
+    ("12345678901234567890", (12345678901234567890, None)),  # big number
+    ("99999999999d88888888888", (99999999999, 88888888888)),  # big dice rolls
+    ("4", (4, None)),  # small constant
+    ("123", (123, None)),  # larger constant
+    ("-456", (-456, None)),  # negative constant
+    ("0", (0, None)),  # zero.
+    ("+789", (789, None)),  # Leading '+' is allowed by int()
+    # ^ dang, AI, that's nasty. But my program doesn't parse
+    #  it that way, haha.
+])
+def test_valid_cases(query, expected):
+    assert _parse_dice_roll(query) == expected
+
+
+@pytest.mark.parametrize("query, expected_exception", [
+    ("AAAA", ValueError),  # Invalid input entirely
+    ("AA123", ValueError),  # ^
+    ("123AA", ValueError),  # ^
+    ("dddd", ValueError),  # Invalid input with multiple d's
+    ("AdB", ValueError),  # Invalid left and right parts.
+    # should raise an exception for whichever first error it encounters.
+    ("Ad99", ValueError),  # Invalid dice
+    ("d99", ValueError),  # Empty dice part
+    ("99dA", ValueError),  # Invalid faces
+    ("99d", ValueError),  # Empty faces part
+    ("99d99d99", ValueError),  # Multiple 'd's in right format
+    ("", ValueError),  # Empty input
+    ("9-9d99", ValueError),  # Invalid characters in dice part
+    ("99d9-9", ValueError),  # Invalid characters in faces part
+])
+def test_invalid_cases(query, expected_exception):
+    with pytest.raises(expected_exception):
+        _parse_dice_roll(query)
diff --git a/unit_tests/extensions/addons/funaddons/roll/test_product_of_list.py b/unit_tests/extensions/addons/funaddons/roll/test_product_of_list.py
new file mode 100644
index 0000000..71b4bfb
--- /dev/null
+++ b/unit_tests/extensions/addons/funaddons/roll/test_product_of_list.py
@@ -0,0 +1,20 @@
+import pytest
+from extensions.addons.cogs.funaddons import _product_of_list
+
+
+@pytest.mark.parametrize("input_list, expected", [
+    ([], 1),  # Empty list
+    ([5], 5),  # Single element
+    ([2, 3, 4], 24),  # Multiple elements
+    ([0, 1, 2], 0),  # A '0' element
+    ([-3, 2], -6),  # Negative element
+    ([12, 2.5], 30),  # Floating point
+    ([0.5, 0.5], 0.25),  # Floating point result
+    ([-0.5, -0.5], 0.25),  # Double negative floating point
+    ([9] * 20, 9**20),  # big number
+    ([1] * 100, 1),  # longer list
+])
+def test_valid_cases(
+        input_list: list[int | float], expected: int | float
+):
+    assert _product_of_list(input_list) == expected
diff --git a/unit_tests/extensions/addons/funaddons/roll/test_validate_dice_and_faces.py b/unit_tests/extensions/addons/funaddons/roll/test_validate_dice_and_faces.py
new file mode 100644
index 0000000..201b126
--- /dev/null
+++ b/unit_tests/extensions/addons/funaddons/roll/test_validate_dice_and_faces.py
@@ -0,0 +1,28 @@
+import pytest
+from extensions.addons.roll import _validate_dice_and_faces
+
+
+@pytest.mark.parametrize("dice, faces, expected", [
+    (2, 6, True),  # simple 2d6 :D
+    (99, 99, True),  # normal number of dice/faces
+    (0, 99, True),  # zero dice
+    (-99, 99, True),  # negative dice
+    (99, 1, True),  # low but non-zero faces
+    # Zero or negative faces won't be accepted, because I don't think
+    #  dice should have negative faces.
+    (999999, 99, True),  # almost a million dice
+    (99, 999999, True),  # almost a million faces
+])
+def test_valid_cases(dice: int, faces: int, expected: bool):
+    assert _validate_dice_and_faces(dice, faces) == expected
+
+
+@pytest.mark.parametrize("dice, faces, expected_exception", [
+    (0, 0, ValueError),  # Zero faces
+    (0, -1, ValueError),  # Negative faces
+    (1000000, 1, OverflowError),  # a million dice
+    (0, 1000000, OverflowError),  # a million faces
+])
+def test_invalid_cases(dice: int, faces: int, expected_exception):
+    with pytest.raises(expected_exception):
+        _validate_dice_and_faces(dice, faces)

From 0f1d5c3a77626bc3ef7adfd025bcfa172848546f Mon Sep 17 00:00:00 2001
From: MysticMia <65396931+MysticMia@users.noreply.github.com>
Date: Thu, 1 May 2025 15:11:38 +0200
Subject: [PATCH 06/26] Update _parse_dice_roll function so roll.py has 100%
 test coverage

---
 extensions/addons/roll.py | 20 ++++++++++++--------
 1 file changed, 12 insertions(+), 8 deletions(-)

diff --git a/extensions/addons/roll.py b/extensions/addons/roll.py
index 8b000eb..5840b10 100644
--- a/extensions/addons/roll.py
+++ b/extensions/addons/roll.py
@@ -13,15 +13,19 @@ def _parse_dice_roll(query: str) -> tuple[int, int | None]:
     if len(parts) > 2:
         raise ValueError("Can't have more than 1 'd' in the "
                          "query of your die!")
-    if len(parts) == 1:
+    elif len(parts) == 2:
+        return (
+            _parse_number("dice", parts[0]),
+            _parse_number("faces", parts[1])
+        )
+    else:
+        assert len(parts) == 1
+        # length of `parts` can't be zero if .split() is provided with a
+        #  delimiter. "".split("d") will return a list with
+        #  1 string: [""]. Only .split() with a whitespace string and
+        #  without split parameters can return an empty list:
+        #  "abcdef".split() -> []
         return _parse_number("dice", parts[0]), None
-    if len(parts) < 1:
-        raise ValueError(f"I couldn't understand what you meant with "
-                         f"{query} ({str(parts)})")
-    return (
-        _parse_number("dice", parts[0]),
-        _parse_number("faces", parts[1])
-    )
 
 
 def _parse_number(source: str, value: str) -> int:

From b3941038d02b0d6608e968ffe5c7d0e81bfa97a9 Mon Sep 17 00:00:00 2001
From: MysticMia <65396931+MysticMia@users.noreply.github.com>
Date: Fri, 2 May 2025 15:29:02 +0200
Subject: [PATCH 07/26] Split dictionary into multiple functions, and begin
 transition to new DictionarySources enum instead of using integers.

---
 .../termdictionary/cogs/termdictionary.py     | 617 ++++++++++--------
 extensions/termdictionary/dictionary_data.py  |   7 +
 .../termdictionary/dictionary_sources.py      |   9 +
 3 files changed, 361 insertions(+), 272 deletions(-)
 create mode 100644 extensions/termdictionary/dictionary_data.py
 create mode 100644 extensions/termdictionary/dictionary_sources.py

diff --git a/extensions/termdictionary/cogs/termdictionary.py b/extensions/termdictionary/cogs/termdictionary.py
index 615113f..576335c 100644
--- a/extensions/termdictionary/cogs/termdictionary.py
+++ b/extensions/termdictionary/cogs/termdictionary.py
@@ -7,8 +7,10 @@
 import discord.app_commands as app_commands
 import discord.ext.commands as commands
 
-from resources.customs import Bot
+from extensions.termdictionary.dictionary_data import DictionaryData
+from extensions.termdictionary.dictionary_sources import DictionarySources
 from resources.checks import is_staff_check  # for staff dictionary commands
+from resources.customs import Bot
 # for logging custom dictionary changes, or when a search query returns nothing or >2000 characters
 from resources.utils.utils import log_to_guild
 
@@ -18,12 +20,16 @@
 del_separators_table = str.maketrans({" ": "", "-": "", "_": ""})
 
 
+def simplify(q: str | list[str]) -> str | list[str]:
+    if type(q) is str:
+        return q.lower().translate(del_separators_table)
+    if type(q) is list:
+        return [text.lower().translate(del_separators_table)
+                for text in q]
+    raise NotImplementedError()
+
+
 async def dictionary_autocomplete(itx: discord.Interaction[Bot], current: str):
-    def simplify(q):
-        if type(q) is str:
-            return q.lower().translate(del_separators_table)
-        if type(q) is list:
-            return [text.lower().translate(del_separators_table) for text in q]
 
     terms = []
     if current == '':
@@ -78,178 +84,348 @@ def simplify(q):
     ]
 
 
+async def _get_custom_dictionary_output(
+        client: Bot, term: str
+):
+    collection = client.async_rina_db["termDictionary"]
+    query = {"synonyms": term.lower()}
+    search = collection.find(query)
+    results: list[tuple[str, str]] = []
+    async for item in search:
+        item: DictionaryData
+        if simplify(term) in simplify(item["synonyms"]):
+            results.append((item["term"], item["definition"]))
+    if not results:
+        cmd_mention_define = client.get_command_mention(
+            "dictionary_staff define")
+        raise KeyError(
+            f"No information found for '{term}' in the custom dictionary.\n"
+            f"If you would like to add a term, message a staff member "
+            f"(to use {cmd_mention_define})"
+        )
+
+    result_str = (f"I found {len(results)} result{'s' * (len(results) > 1)} "
+                  f"for '{term}' in our dictionary:\n")
+    for result_term, result_definition in results:
+        result_str += f"> **{result_term}**: {result_definition}\n"
+
+    long_line = False
+    exceeding_line = False
+    if len(result_str.split("\n")) > 3:
+        long_line = True
+        result_str += "\nDidn't send your message as public cause it would be spammy, having this many results."
+    if len(result_str) > 1999:
+        exceeding_line = True
+        result_str = (
+            f"Your search ({term}) returned too many results (discord has a 2000-character "
+            f"message length D:). (Please ask staff to fix this (synonyms and stuff).)")
+    return result_str, long_line, exceeding_line
+
+
+async def _get_pronouns_page_output(public, result_str,
+                                    source, term):
+    http_safe_term = term.lower().replace("/", " ").replace("%", " ")
+    response_api = requests.get(
+        f'https://en.pronouns.page/api/terms/search/{http_safe_term}'
+    ).text
+    data = json.loads(response_api)
+    if len(data) == 0:
+        if source == 3:
+            source = 5
+        else:
+            result_str = f"I didn't find any results for '{term}' on en.pronouns.page"
+            public = False
+
+    # edit definitions to hide links to other pages:
+    else:
+        search = []
+        for item in data:
+            item_db = item['definition']
+            while item['definition'] == item_db:
+                replacement = re.search("(?<==).+?(?=})",
+                                        item['definition'])
+                if replacement is not None:
+                    item['definition'] = re.sub("{(#.+?=).+?}",
+                                                replacement.group(),
+                                                item['definition'], 1)
+                if item['definition'] == item_db:  # if nothing changed:
+                    break
+                item_db = item['definition']
+            while item['definition'] == item_db:
+                replacement = re.search("(?<={).+?(?=})",
+                                        item['definition'])
+                if replacement is not None:
+                    item['definition'] = re.sub("{.+?}",
+                                                replacement.group(),
+                                                item['definition'], 1)
+                if item['definition'] == item_db:  # if nothing changed:
+                    break
+                item_db = item['definition']
+            search.append(item)
+
+        # if one of the search results matches exactly with the search, give that definition
+        results: list[dict] = []
+        for item in search:
+            if simplify(term) in simplify(item['term'].split('|')):
+                results.append(item)
+        if len(results) > 0:
+            result_str = (
+                f"I found {len(results)} exact result{'s' * (len(results) != 1)} for "
+                f"'{term}' on en.pronouns.page! \n")
+            for item in results:
+                result_str += f"> **{', '.join(item['term'].split('|'))}:** {item['definition']}\n"
+            if (len(search) - len(results)) > 0:
+                result_str += f"{len(search) - len(results)} other non-exact results found."
+            if len(result_str) > 1999:
+                result_str = (
+                    f"Your search ('{term}') returned a too-long "
+                    f"result! (discord has a 2000-character message "
+                    f"length D:). To still let you get better results, "
+                    f"I've rewritten the terms so you might be able "
+                    f"to look for a more specific one:"
+                )
+                for item in results:
+                    result_str += f"> {', '.join(item['term'].split('|'))}\n"
+            raise OverflowError(result_str)
+
+        # if search doesn't exactly match with a result / synonym
+        result_str = (
+            f"I found {len(search)} result{'s' * (len(results) != 1)} for "
+            f"'{term}' on en.pronouns.page! ")
+        if len(search) > 25:
+            result_str += "Here is a list to make your search more specific:\n"
+            results: list[str] = []
+            for item in search:
+                temp = item['term']
+                if "|" in temp:
+                    temp = temp.split("|")[0]
+                results.append(temp)
+            result_str += ', '.join(results)
+            public = False
+        elif len(search) > 2:
+            result_str += "Here is a list to make your search more specific:\n"
+            results: list[str] = []
+            for item in search:
+                if "|" in item['term']:
+                    temp = "- __" + item['term'].split("|")[0] + "__"
+                    temp += " (" + ', '.join(
+                        item['term'].split("|")[1:]) + ")"
+                else:
+                    temp = "- __" + item['term'] + "__"
+                results.append(temp)
+            result_str += '\n'.join(results)
+            public = False
+        elif len(search) > 0:
+            result_str += "\n"
+            for item in search:
+                result_str += f"> **{', '.join(item['term'].split('|'))}:** {item['definition']}\n"
+        else:
+            result_str = f"I didn't find any results for '{term}' on en.pronouns.page!"
+            if source == 4:
+                source = 6
+        msg_length = len(result_str)
+        if msg_length > 1999:
+            public = False
+            result_str = (
+                f"Your search ('{term}') returned too many results ({len(search)} in total!) "
+                f"(discord has a 2000-character message length, and this message "
+                f"was {msg_length} characters D:). Please search more specifically.\n"
+                f"    Here is a link for expanded info on each term: "
+                f"")
+    return public, result_str, source
+
+
+async def _get_urban_dictionary_pages(data):
+    pages = []
+    page = 0
+    for result in data:
+        embed = discord.Embed(color=8481900,
+                              title=f"__{result['word'].capitalize()}__",
+                              description=result['definition'],
+                              url=result['permalink'])
+        post_date = int(
+            datetime.strptime(
+                result['written_on'][:-1],
+                # "2009-03-04T01:16:08.000Z" ([:-1] to remove Z at end)
+                "%Y-%m-%dT%H:%M:%S.%f"
+            ).timestamp()
+        )
+        warning = ""
+        if len(result['example']) > 800:
+            warning = "... (shortened due to size)"
+        embed.add_field(name="Example",
+                        value=f"{result['example'][:800]}{warning}\n\n"
+                              f"{result['thumbs_up']}:thumbsup: :thumbsdown: {result['thumbs_down']}\n"
+                              f"Sent by {result['author']} on  at "
+                              f" ()",
+                        inline=False)
+        pages.append(embed)
+    return page, pages
+
+
+def _get_dictionary_api_pages(data, results):
+    for result in data:
+        meanings = []
+        synonyms = []
+        antonyms = []
+        for meaning in result["meanings"]:
+            meaning_list = [meaning['partOfSpeech']]
+            # **verb**:
+            # meaning one is very useful
+            # meaning two is not as useful
+            for definition in meaning["definitions"]:
+                meaning_list.append("- " + definition['definition'])
+                for synonym in definition['synonyms']:
+                    if synonym not in synonyms:
+                        synonyms.append(synonym)
+                for antonym in definition['antonyms']:
+                    if antonym not in antonyms:
+                        antonyms.append(antonym)
+            for synonym in meaning['synonyms']:
+                if synonym not in synonyms:
+                    synonyms.append(synonym)
+            for antonym in meaning['antonyms']:
+                if antonym not in antonyms:
+                    antonyms.append(antonym)
+            meanings.append(meaning_list)
+
+        results.append([
+            # train
+            result["word"],
+            # [  ["noun", "- Hello there this is 1", "- number two"], ["verb", ...], [...]  ]
+            meanings,
+            ', '.join(synonyms),
+            ', '.join(antonyms),
+            '\n'.join(result["sourceUrls"])
+        ])
+    pages = []
+    pages_detailed = []
+    page = 0
+    for result in results:
+        result_id = 0
+        page_detailed = []
+        embed = discord.Embed(color=8481900,
+                              title=f"__{result[0].capitalize()}__")
+        for meaning_index in range(len(result[1])):
+            _part = result[1][meaning_index][1:]
+            part = []
+            for definition in _part:
+                page_detailed.append(
+                    [result_id, f"__{result[0].capitalize()}__",
+                     result[1][meaning_index][0].capitalize(),
+                     definition])
+                part.append(f"`{result_id}`" + definition)
+                result_id += 1
+            value = '\n'.join(part)
+            if len(value) > 995:  # limit to 1024 chars in Value field
+                value = value[:995] + "... (shortened due to size)"
+            embed.add_field(name=result[1][meaning_index][0].capitalize(),
+                            value=value,
+                            inline=False)
+        if len(result[2]) > 0:
+            embed.add_field(name="Synonyms",
+                            value=f"`{result_id}`" + result[2],
+                            inline=False)
+            page_detailed.append(
+                [result_id, f"__{result[0].capitalize()}__", "Synonyms",
+                 result[2]])
+            result_id += 1
+        if len(result[3]) > 0:
+            embed.add_field(name="Antonyms",
+                            value=f"`{result_id}`" + result[3],
+                            inline=False)
+            page_detailed.append(
+                [result_id, f"__{result[0].capitalize()}__", "Antonyms",
+                 result[3]])
+            result_id += 1
+        if len(result[4]) > 0:
+            embed.add_field(name=f"`{result_id}`-" + "More info:",
+                            value=result[4],
+                            inline=False)
+            page_detailed.append(
+                [result_id, f"__{result[0].capitalize()}__", "More info:",
+                 result[4]])
+            result_id += 1
+        pages.append(embed)
+        pages_detailed.append(page_detailed)
+        # [meaning, [type, definition1, definition2], synonym, antonym, sources]
+    return page, pages, pages_detailed
+
+
 class TermDictionary(commands.Cog):
     def __init__(self):
         pass
 
-    @app_commands.command(name="dictionary", description="Look for the definition of a trans-related term!")
-    @app_commands.describe(term="This is your search query. What do you want to look for?",
-                           source="Where do you want to search? Online? Custom Dictionary? Or just leave it default..",
-                           public="Do you want to share the search results with the rest of the channel? (True=yes)")
+    @app_commands.command(name="dictionary",
+                          description="Look for terms in online dictionaries!")
+    @app_commands.describe(term="This is your search query. What do you want "
+                                "to look for?",
+                           source="Where do you want to search? Online? "
+                                  "Custom Dictionary? Or just leave it "
+                                  "default..",
+                           public="Do you want to share the search results "
+                                  "with the rest of the channel? (True=yes)")
     @app_commands.choices(source=[
-        discord.app_commands.Choice(name='Search from whichever has an answer', value=1),
-        discord.app_commands.Choice(name='Search from custom dictionary', value=2),
-        discord.app_commands.Choice(name='Search from en.pronouns.page', value=4),
-        discord.app_commands.Choice(name='Search from dictionaryapi.dev', value=6),
-        discord.app_commands.Choice(name='Search from urbandictionary.com', value=8),
+        discord.app_commands.Choice(
+            name='Search from whichever has an answer',
+            value=DictionarySources.All.value
+        ),
+        discord.app_commands.Choice(
+            name='Search from custom dictionary',
+            value=DictionarySources.CustomDictionary.value
+        ),
+        discord.app_commands.Choice(
+            name='Search from en.pronouns.page',
+            value=DictionarySources.PronounsPage.value
+        ),
+        discord.app_commands.Choice(
+            name='Search from dictionaryapi.dev',
+            value=DictionarySources.DictionaryApi.value
+        ),
+        discord.app_commands.Choice(
+            name='Search from urbandictionary.com',
+            value=DictionarySources.UrbanDictionary.value
+        ),
     ])
     @app_commands.autocomplete(term=dictionary_autocomplete)
-    async def dictionary(self, itx: discord.Interaction[Bot], term: str, public: bool = False, source: int = 1):
+    async def dictionary(
+            self,
+            itx: discord.Interaction[Bot],
+            term: str,
+            public: bool = False,
+            source: DictionarySources = DictionarySources.All,
+    ):
         # todo: rewrite this whole command.
         #  - Make the sources an enum.
-        #  - Allow going to the next source if you're not satisfied with a dictionary's result.
-        #  - Perhaps make a view with multi-selectable options for which sources you would want to search through
-        #     though this sounds like a bad idea Xd.
-        #  - Should also have an integration without custom dictionary that users can install.
+        #  - Allow going to the next source if you're not satisfied with
+        #    a dictionary's result.
+        #  - Perhaps make a view with multi-selectable options for which
+        #    sources you would want to search through
+        #    though this sounds like a bad idea Xd.
+        #  - Should also have an integration without custom dictionary
+        #    that users can install.
         #  - Make all pageviews into actual PageView views.
-
-        def simplify(q):
-            if type(q) is str:
-                return q.lower().translate(del_separators_table)
-            if type(q) is list:
-                return [text.lower().translate(del_separators_table) for text in q]
+        itx.response: discord.InteractionResponse  # noqa
         # test if mode has been left unset or if mode has been selected: decides whether to move to the
         # online API search or not.
         result_str = ""
         # to make my IDE happy. Will still crash on discord if it actually tries to send it tho: 'Empty message'
-        results: list[any]
-        if source == 1 or source == 2:
-            collection = itx.client.rina_db["termDictionary"]
-            query = {"synonyms": term.lower()}
-            search = collection.find(query)
-
-            result = False
-            results = []
-            result_str = ""
-            for item in search:
-                if simplify(term) in simplify(item["synonyms"]):
-                    results.append([item["term"], item["definition"]])
-                    result = True
-            if result:
-                result_str += (f"I found {len(results)} result{'s' * (len(results) > 1)} for '{term}' "
-                               f"in our dictionary:\n")
-                for x in results:
-                    result_str += f"> **{x[0]}**: {x[1]}\n"
-            else:
-                # if mode has been left unset, it will move to the online API dictionary to look for a definition there.
-                # Otherwise, it will return the 'not found' result of the term, and end the function.
-                if source == 1:
-                    source = 3
-                # public = False
-                else:
-                    cmd_mention = itx.client.get_command_mention("dictionary_staff define")
-                    result_str += (f"No information found for '{term}' in the custom dictionary.\n"
-                                   f"If you would like to add a term, message a staff member (to use {cmd_mention})")
-                    public = False
-            if len(result_str.split("\n")) > 3 and public:
-                public = False
-                result_str += "\nDidn't send your message as public cause it would be spammy, having this many results."
-            if len(result_str) > 1999:
-                result_str = (f"Your search ({term}) returned too many results (discord has a 2000-character "
-                              f"message length D:). (Please ask staff to fix this (synonyms and stuff).)")
-                await log_to_guild(itx.client, itx.guild,
-                                   f":warning: **!! Warning:** {itx.user.name} ({itx.user.id})'s dictionary "
-                                   f"search ('{term}') gave back a result that was larger than 2000 characters!'")
-        if source == 3 or source == 4:
-            http_safe_term = term.lower().replace("/", " ").replace("%", " ")
-            response_api = requests.get(
-                f'https://en.pronouns.page/api/terms/search/{http_safe_term}'
-            ).text
-            data = json.loads(response_api)
-            if len(data) == 0:
-                if source == 3:
-                    source = 5
-                else:
-                    result_str = f"I didn't find any results for '{term}' on en.pronouns.page"
-                    public = False
-
-            # edit definitions to hide links to other pages:
-            else:
-                search = []
-                for item in data:
-                    item_db = item['definition']
-                    while item['definition'] == item_db:
-                        replacement = re.search("(?<==).+?(?=})", item['definition'])
-                        if replacement is not None:
-                            item['definition'] = re.sub("{(#.+?=).+?}", replacement.group(), item['definition'], 1)
-                        if item['definition'] == item_db:  # if nothing changed:
-                            break
-                        item_db = item['definition']
-                    while item['definition'] == item_db:
-                        replacement = re.search("(?<={).+?(?=})", item['definition'])
-                        if replacement is not None:
-                            item['definition'] = re.sub("{.+?}", replacement.group(), item['definition'], 1)
-                        if item['definition'] == item_db:  # if nothing changed:
-                            break
-                        item_db = item['definition']
-                    search.append(item)
-
-                # if one of the search results matches exactly with the search, give that definition
-                results: list[dict] = []
-                for item in search:
-                    if simplify(term) in simplify(item['term'].split('|')):
-                        results.append(item)
-                if len(results) > 0:
-                    result_str = (f"I found {len(results)} exact result{'s' * (len(results) != 1)} for "
-                                  f"'{term}' on en.pronouns.page! \n")
-                    for item in results:
-                        result_str += f"> **{', '.join(item['term'].split('|'))}:** {item['definition']}\n"
-                    if (len(search) - len(results)) > 0:
-                        result_str += f"{len(search) - len(results)} other non-exact results found."
-                    if len(result_str) > 1999:
-                        result_str = (
-                            f"Your search ('{term}') returned a too-long "
-                            f"result! (discord has a 2000-character message "
-                            f"length D:). To still let you get better results, "
-                            f"I've rewritten the terms so you might be able "
-                            f"to look for a more specific one:"
-                        )
-                        for item in results:
-                            result_str += f"> {', '.join(item['term'].split('|'))}\n"
-                    await itx.response.send_message(result_str, ephemeral=not public, suppress_embeds=True)
-                    return
-
-                # if search doesn't exactly match with a result / synonym
-                result_str = (f"I found {len(search)} result{'s' * (len(results) != 1)} for "
-                              f"'{term}' on en.pronouns.page! ")
-                if len(search) > 25:
-                    result_str += "Here is a list to make your search more specific:\n"
-                    results: list[str] = []
-                    for item in search:
-                        temp = item['term']
-                        if "|" in temp:
-                            temp = temp.split("|")[0]
-                        results.append(temp)
-                    result_str += ', '.join(results)
-                    public = False
-                elif len(search) > 2:
-                    result_str += "Here is a list to make your search more specific:\n"
-                    results: list[str] = []
-                    for item in search:
-                        if "|" in item['term']:
-                            temp = "- __" + item['term'].split("|")[0] + "__"
-                            temp += " (" + ', '.join(item['term'].split("|")[1:]) + ")"
-                        else:
-                            temp = "- __" + item['term'] + "__"
-                        results.append(temp)
-                    result_str += '\n'.join(results)
-                    public = False
-                elif len(search) > 0:
-                    result_str += "\n"
-                    for item in search:
-                        result_str += f"> **{', '.join(item['term'].split('|'))}:** {item['definition']}\n"
-                else:
-                    result_str = f"I didn't find any results for '{term}' on en.pronouns.page!"
-                    if source == 4:
-                        source = 6
-                msg_length = len(result_str)
-                if msg_length > 1999:
-                    public = False
-                    result_str = (f"Your search ('{term}') returned too many results ({len(search)} in total!) "
-                                  f"(discord has a 2000-character message length, and this message "
-                                  f"was {msg_length} characters D:). Please search more specifically.\n"
-                                  f"    Here is a link for expanded info on each term: "
-                                  f"")
+        results: list
+        if (source == DictionarySources.All
+                or source == DictionarySources.CustomDictionary):
+            public, result_str, source = await _get_custom_dictionary_output(
+                itx.client, term
+            )
+        if ((source == DictionarySources.All and not result_str)
+                or source == DictionarySources.PronounsPage):
+            try:
+                public, result_str, source = \
+                    await _get_pronouns_page_output(
+                        public, result_str, source, term
+                    )
+            except OverflowError as ex:
+                await itx.reponse.send_message(str(ex), ephemeral=True,
+                                               suppress_embeds=True)
         if source == 5 or source == 6:
             await itx.response.defer(ephemeral=True)
             response_api = requests.get(
@@ -267,84 +443,10 @@ def simplify(q):
                     result_str = f"I didn't find any results for '{term}' on dictionaryapi.dev!"
                     public = False
             else:
-                for result in data:
-                    meanings = []
-                    synonyms = []
-                    antonyms = []
-                    for meaning in result["meanings"]:
-                        meaning_list = [meaning['partOfSpeech']]
-                        # **verb**:
-                        # meaning one is very useful
-                        # meaning two is not as useful
-                        for definition in meaning["definitions"]:
-                            meaning_list.append("- " + definition['definition'])
-                            for synonym in definition['synonyms']:
-                                if synonym not in synonyms:
-                                    synonyms.append(synonym)
-                            for antonym in definition['antonyms']:
-                                if antonym not in antonyms:
-                                    antonyms.append(antonym)
-                        for synonym in meaning['synonyms']:
-                            if synonym not in synonyms:
-                                synonyms.append(synonym)
-                        for antonym in meaning['antonyms']:
-                            if antonym not in antonyms:
-                                antonyms.append(antonym)
-                        meanings.append(meaning_list)
-
-                    results.append([
-                        # train
-                        result["word"],
-                        # [  ["noun", "- Hello there this is 1", "- number two"], ["verb", ...], [...]  ]
-                        meanings,
-                        ', '.join(synonyms),
-                        ', '.join(antonyms),
-                        '\n'.join(result["sourceUrls"])
-                    ])
-
-                pages = []
-                pages_detailed = []
-                page = 0
-                for result in results:
-                    result_id = 0
-                    page_detailed = []
-                    embed = discord.Embed(color=8481900, title=f"__{result[0].capitalize()}__")
-                    for meaning_index in range(len(result[1])):
-                        _part = result[1][meaning_index][1:]
-                        part = []
-                        for definition in _part:
-                            page_detailed.append(
-                                [result_id, f"__{result[0].capitalize()}__", result[1][meaning_index][0].capitalize(),
-                                 definition])
-                            part.append(f"`{result_id}`" + definition)
-                            result_id += 1
-                        value = '\n'.join(part)
-                        if len(value) > 995:  # limit to 1024 chars in Value field
-                            value = value[:995] + "... (shortened due to size)"
-                        embed.add_field(name=result[1][meaning_index][0].capitalize(),
-                                        value=value,
-                                        inline=False)
-                    if len(result[2]) > 0:
-                        embed.add_field(name="Synonyms",
-                                        value=f"`{result_id}`" + result[2],
-                                        inline=False)
-                        page_detailed.append([result_id, f"__{result[0].capitalize()}__", "Synonyms", result[2]])
-                        result_id += 1
-                    if len(result[3]) > 0:
-                        embed.add_field(name="Antonyms",
-                                        value=f"`{result_id}`" + result[3],
-                                        inline=False)
-                        page_detailed.append([result_id, f"__{result[0].capitalize()}__", "Antonyms", result[3]])
-                        result_id += 1
-                    if len(result[4]) > 0:
-                        embed.add_field(name=f"`{result_id}`-" + "More info:",
-                                        value=result[4],
-                                        inline=False)
-                        page_detailed.append([result_id, f"__{result[0].capitalize()}__", "More info:", result[4]])
-                        result_id += 1
-                    pages.append(embed)
-                    pages_detailed.append(page_detailed)
-                    # [meaning, [type, definition1, definition2], synonym, antonym, sources]
+                page, pages, pages_detailed = \
+                    _get_dictionary_api_pages(
+                        data, results
+                    )
 
                 embed = pages[page]
                 embed.set_footer(text="page: " + str(page + 1) + " / " + str(int(len(pages))))
@@ -354,7 +456,6 @@ def simplify(q):
                 await view.wait()
                 if view.value in [None, 1, 2]:
                     await itx.edit_original_response(view=None)
-                return
         if source == 7 or source == 8:
             if not itx.response.is_done():
                 await itx.response.defer(ephemeral=True)
@@ -375,29 +476,7 @@ def simplify(q):
                     result_str = f"I didn't find any results for '{term}' on urban dictionary"
                 public = False
             else:
-                pages = []
-                page = 0
-                for result in data:
-                    embed = discord.Embed(color=8481900,
-                                          title=f"__{result['word'].capitalize()}__",
-                                          description=result['definition'],
-                                          url=result['permalink'])
-                    post_date = int(
-                        datetime.strptime(
-                            result['written_on'][:-1],  # "2009-03-04T01:16:08.000Z" ([:-1] to remove Z at end)
-                            "%Y-%m-%dT%H:%M:%S.%f"
-                        ).timestamp()
-                    )
-                    warning = ""
-                    if len(result['example']) > 800:
-                        warning = "... (shortened due to size)"
-                    embed.add_field(name="Example",
-                                    value=f"{result['example'][:800]}{warning}\n\n"
-                                          f"{result['thumbs_up']}:thumbsup: :thumbsdown: {result['thumbs_down']}\n"
-                                          f"Sent by {result['author']} on  at "
-                                          f" ()",
-                                    inline=False)
-                    pages.append(embed)
+                page, pages = await _get_urban_dictionary_pages(data)
 
                 embed = pages[page]
                 embed.set_footer(text="page: " + str(page + 1) + " / " + str(int(len(pages))))
@@ -424,12 +503,6 @@ def simplify(q):
         definition="Give this term a definition",
         synonyms="Add synonyms (SEPARATE WITH \", \")")
     async def define(self, itx: discord.Interaction[Bot], term: str, definition: str, synonyms: str = ""):
-        def simplify(q):
-            if type(q) is str:
-                return q.lower().translate(del_separators_table)
-            if type(q) is list:
-                return [text.lower().translate(del_separators_table) for text in q]
-
         # Test if this term is already defined in this dictionary.
         collection = itx.client.rina_db["termDictionary"]
         query = {"term": term}
diff --git a/extensions/termdictionary/dictionary_data.py b/extensions/termdictionary/dictionary_data.py
new file mode 100644
index 0000000..660f613
--- /dev/null
+++ b/extensions/termdictionary/dictionary_data.py
@@ -0,0 +1,7 @@
+from typing import TypedDict
+
+
+class DictionaryData(TypedDict):
+    term: str
+    definition: str
+    synonyms: list[str]
diff --git a/extensions/termdictionary/dictionary_sources.py b/extensions/termdictionary/dictionary_sources.py
new file mode 100644
index 0000000..14e9a31
--- /dev/null
+++ b/extensions/termdictionary/dictionary_sources.py
@@ -0,0 +1,9 @@
+from enum import Enum
+
+
+class DictionarySources(Enum):
+    All = 0
+    CustomDictionary = 1
+    PronounsPage = 2
+    DictionaryApi = 3
+    UrbanDictionary = 4

From ff30f5ab002742eaf8ff6cd601a23d8e5a0bd18d Mon Sep 17 00:00:00 2001
From: MysticMia <65396931+MysticMia@users.noreply.github.com>
Date: Tue, 20 May 2025 04:41:47 +0200
Subject: [PATCH 08/26] Add new client function to allow cleaner inline command
 arguments strings.

---
 extensions/addons/cogs/funaddons.py           |   9 +-
 extensions/addons/cogs/otheraddons.py         |   4 +-
 .../addons/views/math_sendpublicbutton.py     |   4 +-
 extensions/compliments/cogs/compliments.py    |  32 ++--
 .../crashhandling/cogs/crashhandling.py       |  51 +++--
 extensions/customvcs/cogs/customvcs.py        |   4 +-
 extensions/customvcs/cogs/vctables.py         | 179 ++++++++++--------
 .../cogs/banappealreaction.py                 |  10 +-
 extensions/qotw/cogs/devrequest.py            |  17 +-
 extensions/reminders/cogs/reminders.py        |  45 ++---
 .../reminders/objects/reminderobject.py       |  21 +-
 extensions/reminders/views/copyreminder.py    |  26 +--
 extensions/settings/cogs/settings.py          |   5 +-
 extensions/tags/cogs/tags.py                  |   9 +-
 extensions/tags/modals/create_tag.py          |   6 +-
 extensions/tags/tags.py                       |  17 +-
 .../termdictionary/cogs/termdictionary.py     |  43 +++--
 extensions/todolist/cogs/todolist.py          |  14 +-
 .../toneindicator/cogs/toneindicator.py       |   9 +-
 extensions/watchlist/cogs/watchlist.py        |  10 +-
 resources/customs/bot.py                      |  11 ++
 21 files changed, 307 insertions(+), 219 deletions(-)

diff --git a/extensions/addons/cogs/funaddons.py b/extensions/addons/cogs/funaddons.py
index 7e53feb..88facbc 100644
--- a/extensions/addons/cogs/funaddons.py
+++ b/extensions/addons/cogs/funaddons.py
@@ -250,10 +250,13 @@ async def roll(
             await itx.response.defer(ephemeral=not public)
             advanced = advanced.replace(" ", "")
             if advanced == "help":
-                cmd_mention = itx.client.get_command_mention("help")
+                cmd_help = itx.client.get_command_mention_with_args(
+                    "help", page="112")
                 await itx.response.send(
-                    f"I don't think I ever added a help command... Ping mysticmia for more information about "
-                    f"this command, or run {cmd_mention} `page:112` for more information.")
+                    f"I don't think I ever added a help command... Ping "
+                    f"mysticmia for more information about this command, or "
+                    f"run {cmd_help} for more information."
+                )
 
             for char in advanced:
                 if char not in "0123456789d+*-":  # kKxXrR": #!!pf≤≥
diff --git a/extensions/addons/cogs/otheraddons.py b/extensions/addons/cogs/otheraddons.py
index 6d6b2f3..41f56be 100644
--- a/extensions/addons/cogs/otheraddons.py
+++ b/extensions/addons/cogs/otheraddons.py
@@ -425,13 +425,13 @@ async def add_poll_reactions(
                 await itx.edit_original_response(
                     content=":warning: Adding emojis failed!"
                 )
-        cmd_mention = itx.client.get_command_mention("add_poll_reactions")
+        cmd_poll = itx.client.get_command_mention("add_poll_reactions")
 
         if not is_in_dms(itx.guild):
             await log_to_guild(
                 itx.client,
                 itx.guild,
-                f"{itx.user.name} ({itx.user.id}) used {cmd_mention} "
+                f"{itx.user.name} ({itx.user.id}) used {cmd_poll} "
                 f"on message {message.jump_url}"
             )
 
diff --git a/extensions/addons/views/math_sendpublicbutton.py b/extensions/addons/views/math_sendpublicbutton.py
index c4b7894..087df02 100644
--- a/extensions/addons/views/math_sendpublicbutton.py
+++ b/extensions/addons/views/math_sendpublicbutton.py
@@ -17,9 +17,9 @@ async def send_publicly(
     ):
         self.value = 1
         await itx.response.edit_message(content="Sent successfully!")
-        cmd_mention = itx.client.get_command_mention("math")
+        cmd_math = itx.client.get_command_mention("math")
         await itx.followup.send(
-            f"**{itx.user.mention} shared a {cmd_mention} output:**\n"
+            f"**{itx.user.mention} shared a {cmd_math} output:**\n"
             + itx.message.content,
             ephemeral=False,
             allowed_mentions=discord.AllowedMentions.none()
diff --git a/extensions/compliments/cogs/compliments.py b/extensions/compliments/cogs/compliments.py
index 5969b2c..ebde5d4 100644
--- a/extensions/compliments/cogs/compliments.py
+++ b/extensions/compliments/cogs/compliments.py
@@ -139,15 +139,19 @@ async def _choose_and_send_compliment(
         )
 
     base = f"{itx.user.mention} complimented {user.mention}!\n"
-    # cmd_mention = client.get_command_mention("developer_request")
-    # cmd_mention1 = client.get_command_mention("complimentblacklist")
+    # cmd_dev_request = itx.client.get_command_mention("developer_request")
+    # cmd_blacklist = itx.client.get_command_mention_with_args(
+    #     "complimentblacklist",
+    #     location="being complimented",
+    #     mode="Add",
+    #     string=" "
+    # )
     suffix = ""  # (
     #     f"\n\nPlease give suggestions for compliments! DM "
-    #     f"<@262913789375021056>, make a staff ticket, or use {cmd_mention} "
-    #     f"to suggest one. Do you dislike this compliment? Use "
-    #     f"{cmd_mention1} `location:being complimented` `mode:Add` "
-    #     f"`string: ` and block specific words (or the letters \"e\" and "
-    #     f"\"o\" to block every compliment"
+    #     f"<@262913789375021056>, make a staff ticket, or use "
+    #     f"{cmd_dev_request} to suggest one. Do you dislike this compliment? "
+    #     f"Use {cmd_blacklist} and block specific words (or the letters "
+    #     f"\"e\" and \"o\" to block every compliment"
     # )
     if itx.response.is_done():  # todo: add "give compliment back" button
         # should happen if user used the modal to select a pronoun role
@@ -383,10 +387,10 @@ async def on_message(self, message: discord.Message):
                     except discord.errors.Forbidden:
                         pass
             else:
-                cmd_mention = self.client.get_command_mention("help")
+                cmd_help = self.client.get_command_mention("help")
                 await message.channel.send(
                     f"I use slash commands! Use /`command`  and see what cool "
-                    f"things might pop up! or try {cmd_mention}\n"
+                    f"things might pop up! or try {cmd_help}\n"
                     f"PS: If you're trying to call me cute: no im not",
                     delete_after=8
                 )
@@ -485,11 +489,11 @@ async def complimentblacklist(
 
         elif mode == 2:  # Remove item from black list
             if string is None:
-                cmd_mention = itx.client.get_command_mention(
-                    "complimentblacklist")
+                cmd_blacklist = itx.client.get_command_mention_with_args(
+                    "complimentblacklist", mode="Check")
                 await itx.response.send_message(
                     f"Type the id of the string you want to remove. To find "
-                    f"the id, type {cmd_mention} `mode:Check`.",
+                    f"the id, type {cmd_blacklist}.",
                     ephemeral=True)
                 return
             try:
@@ -518,11 +522,11 @@ async def complimentblacklist(
             try:
                 del blacklist[string]
             except IndexError:
-                cmd_mention = itx.client.get_command_mention(
+                cmd_blacklist = itx.client.get_command_mention(
                     "complimentblacklist")
                 await itx.response.send_message(
                     f"Couldn't delete that ID, because there isn't any item "
-                    f"on your list with that ID. Use {cmd_mention} "
+                    f"on your list with that ID. Use {cmd_blacklist} "
                     f"`mode:Check` to see the IDs assigned to each item on "
                     f"your list",
                     ephemeral=True)
diff --git a/extensions/crashhandling/cogs/crashhandling.py b/extensions/crashhandling/cogs/crashhandling.py
index 4d76830..f5db0f8 100644
--- a/extensions/crashhandling/cogs/crashhandling.py
+++ b/extensions/crashhandling/cogs/crashhandling.py
@@ -216,15 +216,20 @@ async def on_error(self, event: str, *_args, **_kwargs):
             module_source, missing_attributes = \
                 exception_message.split("; ", 2)
             missing_attributes = missing_attributes.split(", ")
-            cmd_mention = self.client.get_command_mention("settings")
+            cmd_settings = self.client.get_command_mention_with_args(
+                "settings",
+                type="Attribute",
+                setting=" ",
+                mode="Set",
+                value=" "
+            )
             await log_to_guild(
                 self.client,
                 potential_guild,
                 f"Module `{module_source}` ran into an error! This is likely "
                 f"because the module was enabled, but did not have the "
                 f"required attributes configured to execute its features.\n"
-                f"Use {cmd_mention} `type:Attribute` `setting: ` to set "
-                f"these missing attributes:\n"
+                f"Use {cmd_settings} to set these missing attributes:\n"
                 f"> " + ", ".join(missing_attributes)
             )
             return
@@ -268,27 +273,31 @@ async def on_app_command_error(
             return
         elif error_type is ModuleNotEnabledCheckFailure:
             error: ModuleNotEnabledCheckFailure
-            cmd_mention_settings = itx.client.get_command_mention("settings")
             if itx.client.server_settings is None:
+                cmd_settings = itx.client.get_command_mention("settings")
                 await itx.response.send_message(
-                    "This module may or may not be enabled. Unfortunately, "
-                    "no server settings have been loaded. Perhaps "
-                    "the bot is still starting up? Admins can attempt to "
-                    "use the /settings command to add new data.",
+                    f"This module may or may not be enabled. Unfortunately, "
+                    f"no server settings have been loaded. Perhaps "
+                    f"the bot is still starting up? Admins can attempt to "
+                    f"use {cmd_settings} to add new data.",
                     ephemeral=True
                 )
                 return
-
+            cmd_settings = itx.client.get_command_mention_with_args(
+                "settings",
+                type="Module",
+                setting=error.module_key,
+                mode="Enable",
+            )
             if is_admin(itx, itx.user):
-                cmd_mention_help = itx.client.get_command_mention("help")
+                cmd_help = itx.client.get_command_mention("help")
                 await itx.response.send_message(
                     f"This module is not enabled! Enable it using the "
                     f"following command:\n"
-                    f"- {cmd_mention_settings} `type:Module` "
-                    f"`setting:{error.module_key}` `mode:Enable`\n"
+                    f"- {cmd_settings}\n"
                     f"Make sure you also set the required attributes for this "
                     f"module. The required attributes for modules and "
-                    f"commands are explained in {cmd_mention_help}.",
+                    f"commands are explained in {cmd_help}.",
                     ephemeral=True)
                 return
             await itx.response.send_message(
@@ -300,12 +309,18 @@ async def on_app_command_error(
             return
         elif error_type is MissingAttributesCheckFailure:
             error: MissingAttributesCheckFailure
-            cmd_mention_settings = itx.client.get_command_mention("settings")
+            cmd_settings = itx.client.get_command_mention_with_args(
+                "settings",
+                type="Attribute",
+                setting=" ",
+                mode="Set",
+                value=" "
+            )
             await _reply(
                 itx,
                 f"Your command failed to completely execute because it relied "
                 f"on certain server attributes that were not defined! An "
-                f"admin will have to run {cmd_mention_settings} "
+                f"admin will have to run {cmd_settings} "
                 f"`type:Attribute` `setting: ` for the following "
                 f"attribute(s):\n"
                 f"> " + ', '.join(error.attributes)
@@ -332,21 +347,21 @@ async def on_app_command_error(
             )
             return
 
-        cmd_mention_settings = itx.client.get_command_mention("update")
+        cmd_settings = itx.client.get_command_mention("update")
         if isinstance(error, app_commands.errors.CommandNotFound):
             await _reply(
                 itx,
                 f"This command doesn't exist! Perhaps the commands are"
                 f" unsynced. Ask {itx.client.bot_owner} "
                 f"({itx.client.bot_owner.mention}) if she typed "
-                f"{cmd_mention_settings}!"
+                f"{cmd_settings}!"
             )
         elif isinstance(error, app_commands.errors.CommandSignatureMismatch):
             await _reply(
                 itx,
                 f"Error: CommandSignatureMismatch. Either Mia used GroupCog "
                 f"instead of Cog, or this command is out of date "
-                f"(try {cmd_mention_settings})"
+                f"(try {cmd_settings})"
             )
         else:
             if hasattr(error, 'original'):
diff --git a/extensions/customvcs/cogs/customvcs.py b/extensions/customvcs/cogs/customvcs.py
index 6bdc1c1..53b155f 100644
--- a/extensions/customvcs/cogs/customvcs.py
+++ b/extensions/customvcs/cogs/customvcs.py
@@ -100,7 +100,6 @@ async def _create_new_custom_vc(
 
     default_name = "Untitled voice chat"
     warning = ""
-    cmd_mention = client.get_command_mention("editvc")
 
     try:
         vc = await customvc_category.create_voice_channel(
@@ -118,9 +117,10 @@ async def _create_new_custom_vc(
             vc,
             reason="Opened a new voice channel through the vc hub thing."
         )
+        cmd_editvc = client.get_command_mention("editvc")
         await vc.send(
             f"Voice channel <#{vc.id}> ({vc.id}) created by "
-            f"<@{member.id}> ({member.id}). Use {cmd_mention} to edit the "
+            f"<@{member.id}> ({member.id}). Use {cmd_editvc} to edit the "
             f"name/user limit.",
             allowed_mentions=discord.AllowedMentions.none()
         )
diff --git a/extensions/customvcs/cogs/vctables.py b/extensions/customvcs/cogs/vctables.py
index ad75188..3768b7a 100644
--- a/extensions/customvcs/cogs/vctables.py
+++ b/extensions/customvcs/cogs/vctables.py
@@ -186,10 +186,10 @@ async def _get_channel_if_owner(
 
     if not _is_vc_table_owner(channel, itx.user):
         if not from_event:
-            cmd_mention = itx.client.get_command_mention('vctable create')
+            cmd_create = itx.client.get_command_mention('vctable create')
             await itx.response.send_message(
                 f"Invalid permissions: You are not an owner of this VcTable! "
-                f"(Perhaps this isn't a VcTable yet: use {cmd_mention} to "
+                f"(Perhaps this isn't a VcTable yet: use {cmd_create} to "
                 f"make it one!)",
                 ephemeral=True,
             )
@@ -229,7 +229,6 @@ async def vctable_create(
         warning = ""
 
         owners = owners.split(",")
-        cmd_mention = itx.client.get_command_mention("vctable add_owner")
         if itx.user.guild is None:
             await itx.response.send_message(
                 "You don't seem to be in this guild, so I can't give you "
@@ -238,6 +237,8 @@ async def vctable_create(
                 ephemeral=True,
             )
 
+        cmd_owner = itx.client.get_command_mention_with_args(
+            "vctable owner", mode="Add", user=" ")
         # region Parse vctable owners
         added_owners = [itx.user]
         added_owner_ids = []
@@ -249,7 +250,7 @@ async def vctable_create(
                 warning = (
                     f"Note: You didn't give a good list of VcTable owners, "
                     f"so I only added the ones prior. To make more people "
-                    f"owner, use {cmd_mention}.\n"
+                    f"owner, use {cmd_owner}.\n"
                 )
                 break
             mention = mention[2:-1]
@@ -258,7 +259,7 @@ async def vctable_create(
                     warning = (
                         f"Note: You didn't give a good list of VcTable "
                         f"owners, so I only added the ones prior. To make "
-                        f"more people owner, use {cmd_mention}.\n"
+                        f"more people owner, use {cmd_owner}.\n"
                     )
                     break
 
@@ -266,7 +267,7 @@ async def vctable_create(
                 warning = (
                     f"Note: You didn't give a good list of VcTable owners, "
                     f"so I only added the ones prior. To make more people "
-                    f"owner, use {cmd_mention}.\n"
+                    f"owner, use {cmd_owner}.\n"
                 )
                 break
             mention_id = int(mention)
@@ -280,7 +281,7 @@ async def vctable_create(
                         f"Note: The list of owners you provided contained an "
                         f"unknown server member ({owner_id}), so I only added "
                         f"the ones prior. To make more people owner, "
-                        f"use {cmd_mention}."
+                        f"use {cmd_owner}."
                     )
                     break
 
@@ -312,9 +313,9 @@ async def vctable_create(
         for target in user_vc.overwrites:
             if (_is_vc_table_owner(user_vc, target)
                     and target not in user_vc.category.overwrites):
-                cmd_mention = itx.client.get_command_mention("vctable owner")
+                cmd_owner = itx.client.get_command_mention("vctable owner")
                 await itx.followup.send(
-                    f"This channel is already a VcTable! Use {cmd_mention} "
+                    f"This channel is already a VcTable! Use {cmd_owner} "
                     f"`mode:Check owners` to see who the owners of this "
                     f"table are!",
                     ephemeral=True)
@@ -359,10 +360,10 @@ async def vctable_create(
 
         owner_taglist = ', '.join([f'<@{user_id}>'
                                    for user_id in added_owners])
-        cmd_mention = itx.client.get_command_mention("vctable about")
+        cmd_owner = itx.client.get_command_mention("vctable about")
         await user_vc.send(
             f"CustomVC converted to VcTable\n"
-            f"Use {cmd_mention} to learn more!\n"
+            f"Use {cmd_owner} to learn more!\n"
             f"Made {owner_taglist} a VcTable Owner\n"
             f"**:warning: If someone is breaking the rules, TELL A MOD** "
             f"(don't try to moderate a vc yourself)",
@@ -438,7 +439,7 @@ async def vctable_disband(self, itx: discord.Interaction):
         description="Get information about this CustomVC add-on feature"
     )
     @module_enabled_check(ModuleKeys.vc_tables)
-    async def vctable_help(self, itx: discord.Interaction):
+    async def vctable_help(self, itx: discord.Interaction[Bot]):
         embed1 = discord.Embed(
             color=discord.Colour.from_rgb(r=255, g=153, b=204),
             title='Custom VC Tables',
@@ -450,6 +451,22 @@ async def vctable_help(self, itx: discord.Interaction):
                         "over your vc by letting you mute disruptive people "
                         "or make speaking permissions whitelist-only"
         )
+
+        cmd_about = itx.client.get_command_mention('vctable about')
+        cmd_create = itx.client.get_command_mention_with_args(
+            'vctable create', owners=" ")
+        cmd_owner = itx.client.get_command_mention_with_args(
+            'vctable owner', mode=" ", user=" ")
+        cmd_mute = itx.client.get_command_mention_with_args(
+            'vctable mute', mode=" ", user=" ")
+        cmd_authorized = itx.client.get_command_mention(
+            'vctable make_authorized_only')
+        cmd_speaker = itx.client.get_command_mention_with_args(
+            'vctable speaker', mode=" ", user=" ")
+        cmd_lock = itx.client.get_command_mention('vctable lock')
+        cmd_participant = itx.client.get_command_mention_with_args(
+            'vctable participant', mode=" ", user=" ")
+
         embed2 = discord.Embed(
             color=discord.Colour.from_rgb(r=255, g=153, b=204),
             title='Command documentation and explanation',
@@ -458,36 +475,29 @@ async def vctable_help(self, itx: discord.Interaction):
                         f"Most commands are for owners only, like muting, "
                         f"adding/removing permissions. Normal participants "
                         f"can check who's owner, speaker, or muted though.\n"
-                        f"{itx.client.get_command_mention('vctable about')}: "
-                        f"See this help page\n"
-                        f"{itx.client.get_command_mention('vctable create')}"
-                        f" `[owners: ]`: Turns your CustomVC into a VcTable "
+                        f"{cmd_about}: See this help page\n"
+                        f"{cmd_create}: Turns your CustomVC into a VcTable "
                         f"and makes you (and any additional mentioned "
                         f"user(s)) the owner\n"
-                        f"{itx.client.get_command_mention('vctable owner')} "
-                        f"`mode: ` `user: `: Add/Remove an owner to your "
+                        f"{cmd_owner}: Add/Remove an owner to your "
                         f"table. If you want to check the owners, then it "
                         f"doesn't matter what you fill in for 'user'\n"
-                        f"{itx.client.get_command_mention('vctable mute')} "
-                        f"`mode: ` `user: `: Mute/Unmute a user in your "
+                        f"{cmd_mute}: Mute/Unmute a user in your "
                         f"table. If you want to check the muted participants, "
                         f"see ^ (same as for checking owners)\n"
-                        f"{itx.client.get_command_mention(
-                            'vctable make_authorized_only')}: "
-                        f"Toggle the whitelist for speaking\n"
-                        f"{itx.client.get_command_mention('vctable speaker')}"
-                        f" `mode: ` `user: `: Add/Remove a speaker to your "
+                        f"{cmd_authorized}: Toggle the whitelist for "
+                        f"speaking\n"
+                        f"{cmd_speaker}: Add/Remove a speaker to your "
                         f"table. This user gets whitelisted to speak when "
                         f"authorized-only is enabled. Checking speakers works "
                         f"the same as checking owners and muted users\n"
-                        f"{itx.client.get_command_mention('vctable lock')}: "
-                        f"Similar to make-authorized-only, but for viewing "
-                        f"the voice channel and its message history.\n"
-                        f"{itx.client.get_command_mention(
-                            'vctable participant')} `mode: ` `user: `: "
-                        f"Add/Remove a participant to your table. This user "
-                        f"gets whitelisted to view the channel and message "
-                        f"history when the 'lock' is activated.\n"
+                        f"{cmd_lock}: Similar to make-authorized-only, but "
+                        f"for viewing the voice channel and its message "
+                        f"history.\n"
+                        f"{cmd_participant}: Add/Remove a participant to your "
+                        f"table. This user gets whitelisted to view the "
+                        f"channel and message history when the 'lock' is "
+                        f"activated.\n"
         )  # todo: move to use /help page instead
         await itx.response.send_message(
             embeds=[embed1, embed2],
@@ -522,15 +532,15 @@ async def edit_vctable_owners(
                     ephemeral=True,
                 )
             elif mode == 2:
-                cmd_mention = itx.client.get_command_mention(
-                    "vctable owner")
-                cmd_mention1 = itx.client.get_command_mention(
+                cmd_owner = itx.client.get_command_mention_with_args(
+                    "vctable owner", user=" ")
+                cmd_disband = itx.client.get_command_mention(
                     "vctable disband")
                 await itx.response.send_message(
                     "You can't remove your ownership of this VcTable!\n"
-                    f"You can make more people owner with "
-                    f"{cmd_mention} `user: ` though... If you want to delete "
-                    f"the VcTable, you can use {cmd_mention1}",
+                    f"You can make more people owner with {cmd_owner} "
+                    f"though... If you want to delete the VcTable, you can "
+                    f"use {cmd_disband}.",
                     ephemeral=True,
                 )
             return
@@ -581,11 +591,11 @@ async def edit_vctable_owners(
             if channel is None:
                 return
             if user is None:
-                cmd_mention = itx.client.get_command_mention("vctable owner")
+                cmd_owner = itx.client.get_command_mention("vctable owner")
                 await itx.response.send_message(
                     f"Removing owners is usually a bad sign.. Do not hesitate "
                     f"to make a ticket for staff if there's something wrong.\n"
-                    f"Anyway. You can check current owners with {cmd_mention} "
+                    f"Anyway. You can check current owners with {cmd_owner} "
                     f"`mode:Check`. Then mention a user you want to delete in "
                     f"the `user: ` argument.",
                     ephemeral=True,
@@ -656,11 +666,11 @@ async def edit_vctable_speakers(
             if channel is None:
                 return
             if user is None:
-                cmd_mention = itx.client.get_command_mention(
+                cmd_owner = itx.client.get_command_mention(
                     "vctable make_authorized_only")
                 await itx.response.send_message(
                     f"You can add a speaker to your VcTable using this "
-                    f"command. Using {cmd_mention}, you can let only those "
+                    f"command. Using {cmd_owner}, you can let only those "
                     f"you've selected be able to talk in your voice channel. "
                     f"This can be useful if you want an on-topic convo or "
                     f"podcast with a select group of people :)",
@@ -679,10 +689,10 @@ async def edit_vctable_speakers(
                 )
                 return
             if not _is_vctable_authorized(channel, itx.guild):
-                cmd_mention = itx.client.get_command_mention(
+                cmd_owner = itx.client.get_command_mention(
                     "vctable make_authorized_only")
                 warning += (f"\nThis has no purpose until you enable "
-                            f"'authorized-only' using {cmd_mention}.")
+                            f"'authorized-only' using {cmd_owner}.")
             await channel.set_permissions(
                 user,
                 speak=True,
@@ -705,17 +715,16 @@ async def edit_vctable_speakers(
             if channel is None:
                 return
             if user is None:
-                cmd_mention = itx.client.get_command_mention(
-                    "vctable owner")
-                cmd_mention2 = itx.client.get_command_mention(
-                    "vctable speaker")
+                cmd_owner = itx.client.get_command_mention_with_args(
+                    "vctable owner", mode="Remove owner")
+                cmd_speaker = itx.client.get_command_mention_with_args(
+                    "vctable speaker", mode="Check")
                 await itx.response.send_message(
                     f"You can remove speakers with this command. This only "
                     f"works if the user you're trying to remove is not "
                     f"already a VcTable owner (you'll need to use "
-                    f"{cmd_mention} `mode:Remove owner` first).\n"
-                    f"To see current VcTable speakers, use {cmd_mention2} "
-                    f"`mode:Check`. ",
+                    f"{cmd_owner} first).\n"
+                    f"To see current VcTable speakers, use {cmd_speaker}.",
                     ephemeral=True,
                 )
                 return
@@ -735,10 +744,10 @@ async def edit_vctable_speakers(
                 return
             warning = ""
             if not _is_vctable_authorized(channel, itx.guild):
-                cmd_mention = itx.client.get_command_mention(
+                cmd_owner = itx.client.get_command_mention(
                     "vctable make_authorized_only")
                 warning = (f"\nThis has no purpose until you enable "
-                           f"'authorized-only' using {cmd_mention}.")
+                           f"'authorized-only' using {cmd_owner}.")
             await channel.set_permissions(
                 user,
                 speak=None,
@@ -800,10 +809,10 @@ async def edit_vctable_participants(
             if channel is None:
                 return
             if user is None:
-                cmd_mention = itx.client.get_command_mention("vctable lock")
+                cmd_owner = itx.client.get_command_mention("vctable lock")
                 await itx.response.send_message(
                     f"You can add a participant to your VcTable using this "
-                    f"command. Using {cmd_mention}, you can let only those "
+                    f"command. Using {cmd_owner}, you can let only those "
                     f"you've selected be able to see your voice channel and "
                     f"read its messages. This can be useful if you want an "
                     f"on-topic convo or private meeting with a select group "
@@ -820,9 +829,9 @@ async def edit_vctable_participants(
                 return
             warning = ""
             if not _is_vctable_locked(channel, itx.guild):
-                cmd_mention = itx.client.get_command_mention("vctable lock")
+                cmd_owner = itx.client.get_command_mention("vctable lock")
                 warning += (f"\nThis has no purpose until you activate the "
-                            f"'lock' using {cmd_mention}.")
+                            f"'lock' using {cmd_owner}.")
             await channel.set_permissions(
                 user,
                 view_channel=True,
@@ -844,17 +853,17 @@ async def edit_vctable_participants(
             if channel is None:
                 return
             if user is None:
-                cmd_mention = itx.client.get_command_mention(
-                    "vctable owner")
-                cmd_mention2 = itx.client.get_command_mention(
-                    "vctable participant")
+                cmd_owner = itx.client.get_command_mention_with_args(
+                    "vctable owner", mode="Remove owner")
+                cmd_participant = itx.client.get_command_mention_with_args(
+                    "vctable participant", mode="Check")
                 await itx.response.send_message(
                     f"You can remove participants with this command. This "
                     f"only works if the user you're trying to remove is not "
-                    f"already a VcTable owner (you'll need to use "
-                    f"{cmd_mention} `mode:Remove owner` first).\n"
-                    f"To see current VcTable participants, use {cmd_mention2} "
-                    f"`mode:Check`.",
+                    f"already a VcTable owner (you'll need to use {cmd_owner} "
+                    f"first).\n"
+                    f"To see current VcTable participants, use "
+                    f"{cmd_participant}.",
                     ephemeral=True,
                 )
                 return
@@ -886,9 +895,9 @@ async def edit_vctable_participants(
                 return
             warning = ""
             if _is_vctable_locked(channel, itx.guild):
-                cmd_mention = itx.client.get_command_mention("vctable lock")
+                cmd_owner = itx.client.get_command_mention("vctable lock")
                 warning = (f"\nThis has no purpose until you activate the "
-                           f"'lock' using {cmd_mention}.")
+                           f"'lock' using {cmd_owner}.")
             await channel.set_permissions(
                 user,
                 view_channel=None,
@@ -1005,12 +1014,14 @@ async def edit_vctable_muted_participants(
             if channel is None:
                 return
             if user is None:
-                cmd_mention = itx.client.get_command_mention("vctable mute")
+                cmd_check = itx.client.get_command_mention_with_args(
+                    "vctable mute", mode="Check")
+                cmd_mute = itx.client.get_command_mention_with_args(
+                    "vctable mute", mode="Mute", user=" ")
                 await itx.response.send_message(
                     f"This command lets you unmute a previously-muted person. "
-                    f"To see which people are muted, use {cmd_mention} "
-                    f"`mode:Check`\n"
-                    f"Then simply mention this user in the `user: ` argument.",
+                    f"To see which people are muted, use {cmd_check}\n"
+                    f"Then, use {cmd_mute}\n",
                     ephemeral=True,
                 )
                 return
@@ -1058,7 +1069,7 @@ async def edit_vctable_muted_participants(
                           description="Only let users speak if they are "
                                       "whitelisted by the owner")
     @module_enabled_check(ModuleKeys.vc_tables)
-    async def vctable_authorized_only(self, itx: discord.Interaction):
+    async def vctable_authorized_only(self, itx: discord.Interaction[Bot]):
         channel = await _get_channel_if_owner(itx, "enable authorized-only")
         if channel is None:
             return
@@ -1084,12 +1095,13 @@ async def vctable_authorized_only(self, itx: discord.Interaction):
             ("Confirm", discord.ButtonStyle.green),
             ("Cancel", discord.ButtonStyle.red)
         )
-        cmd_mention = itx.client.get_command_mention("vctable speaker")
+        cmd_speaker = itx.client.get_command_mention_with_args(
+            "vctable speaker", mode="Add", user=" ")
         await itx.response.send_message(
             f"Enabling authorized-only (a whitelist) will make only owners "
             f"and speakers (people that have been whitelisted) able to talk.\n"
-            f"Please make sure everyone is aware of this change. "
-            f"To whitelist someone, use {cmd_mention} `mode:Add` `user: `.",
+            f"Please make sure everyone is aware of this change. To whitelist "
+            f"someone, use {cmd_speaker}.",
             ephemeral=True,
             view=view,
         )
@@ -1112,9 +1124,9 @@ async def vctable_authorized_only(self, itx: discord.Interaction):
                         # todo: rename vctable/vc_table to be consistent
                         continue
                 await member.move_to(channel)
-            cmd_mention = itx.client.get_command_mention("vctable speaker")
+            cmd_speaker = itx.client.get_command_mention("vctable speaker")
             await itx.edit_original_response(
-                content=f"Successfully enabled whitelist. Use {cmd_mention} "
+                content=f"Successfully enabled whitelist. Use {cmd_speaker} "
                         f"`user: ` to let more people speak.",
                 view=None)
         else:
@@ -1127,7 +1139,7 @@ async def vctable_authorized_only(self, itx: discord.Interaction):
                     "the owner"
     )
     @module_enabled_check(ModuleKeys.vc_tables)
-    async def vctable_lock(self, itx: discord.Interaction):
+    async def vctable_lock(self, itx: discord.Interaction[Bot]):
         channel = await _get_channel_if_owner(itx, "enable vctable lock")
         if channel is None:
             return
@@ -1157,13 +1169,14 @@ async def vctable_lock(self, itx: discord.Interaction):
             ("Confirm", discord.ButtonStyle.green),
             ("Cancel", discord.ButtonStyle.red)
         )
-        cmd_mention = itx.client.get_command_mention("vctable participant")
+        cmd_participant = itx.client.get_command_mention_with_args(
+            "vctable participant", mode="Add", user=" ")
         await itx.response.send_message(
             f"Enabling the lock (a whitelist) will make only owners and "
             f"participants (people that have been whitelisted) able to see "
             f"this server and message history.\n"
             f"Please make sure everyone is aware of this change. "
-            f"To whitelist someone, use {cmd_mention} `mode:Add` `user: `.",
+            f"To whitelist someone, use {cmd_participant}.",
             ephemeral=True,
             view=view,
         )
@@ -1180,9 +1193,9 @@ async def vctable_lock(self, itx: discord.Interaction):
                 f"viewing the voice channel.",
                 allowed_mentions=discord.AllowedMentions.none()
             )
-            cmd_mention = itx.client.get_command_mention("vctable participant")
+            cmd_participant = itx.client.get_command_mention("vctable participant")
             await itx.edit_original_response(
-                content=f"Successfully enabled whitelist. Use {cmd_mention} "
+                content=f"Successfully enabled whitelist. Use {cmd_participant} "
                         f"`user: ` to let more people speak.",
                 view=None,
             )
diff --git a/extensions/message_reactions/cogs/banappealreaction.py b/extensions/message_reactions/cogs/banappealreaction.py
index a850404..0a6aa9a 100644
--- a/extensions/message_reactions/cogs/banappealreaction.py
+++ b/extensions/message_reactions/cogs/banappealreaction.py
@@ -86,10 +86,16 @@ async def on_message(self, message: discord.Message):
         ban_appeal_role = self.client.get_guild_attribute(
             message.guild, AttributeKeys.ban_appeal_reaction_role)
         if ban_appeal_role is None:
-            cmd_mention_settings = self.client.get_command_mention("settings")
+            cmd_settings = self.client.get_command_mention_with_args(
+                "settings",
+                type="Attribute",
+                setting=AttributeKeys.ban_appeal_reaction_role,
+                mode="Set",
+                value=" "
+            )
             await joiner_msg.edit(
                 content=f"No role has been set up to be pinged after a "
-                        f"ban appeal is created. Use {cmd_mention_settings} "
+                        f"ban appeal is created. Use {cmd_settings} "
                         f"to add one.")
         else:
             await joiner_msg.edit(content=f"<@&{ban_appeal_role.id}>")
diff --git a/extensions/qotw/cogs/devrequest.py b/extensions/qotw/cogs/devrequest.py
index d0aab9a..48692a4 100644
--- a/extensions/qotw/cogs/devrequest.py
+++ b/extensions/qotw/cogs/devrequest.py
@@ -92,11 +92,18 @@ async def developer_request(
         developer_role = itx.client.get_guild_attribute(
             itx.user.guild, AttributeKeys.developer_request_channel)
         if developer_role is None:
-            cmd_mention_settings = itx.client.get_command_mention("settings")
+            cmd_settings = itx.client.get_command_mention_with_args(
+                "settings",
+                type="Attribute",
+                setting=AttributeKeys.developer_request_reaction_role,
+                mode="Set",
+                value=" ",
+            )
             await joiner_msg.edit(
                 content=f"No role has been set up to be pinged after a "
-                        f"developer request is created. Use "
-                        f"{cmd_mention_settings} to add one.")
+                        f"developer request is created. Use {cmd_settings} "
+                        f"to add one."
+            )
         else:
             await joiner_msg.edit(
                 content=f"<@&{developer_role.id}> <@{itx.user.id}>")
@@ -183,11 +190,11 @@ async def ping_open_developer_requests(
             if starter_message.embeds[0].color in [emoji_color_options["🟡"],
                                                    emoji_color_options["🔵"]]:
                 try:
-                    cmd_mention = itx.client.get_command_mention(
+                    cmd_ping = itx.client.get_command_mention(
                         "ping_open_dev_requests")
                     await thread.send(
                         itx.user.mention
-                        + f" poked this thread with {cmd_mention}.\n"
+                        + f" poked this thread with {cmd_ping}.\n"
                           f"This channel got a message because it was "
                           f"archived and the request wasn't marked as "
                           f"completed or rejected.",
diff --git a/extensions/reminders/cogs/reminders.py b/extensions/reminders/cogs/reminders.py
index 5da5fbb..f5c1ca8 100644
--- a/extensions/reminders/cogs/reminders.py
+++ b/extensions/reminders/cogs/reminders.py
@@ -43,7 +43,8 @@ async def remindme(
                 ephemeral=True)
             return
 
-        cmd_mention_help = itx.client.get_command_mention("help")
+        cmd_help = itx.client.get_command_mention_with_args(
+            "help", page="113")
         try:
             await parse_and_create_reminder(itx, reminder_datetime, reminder)
         except OverflowError as ex:
@@ -60,7 +61,7 @@ async def remindme(
                 f"Current time: {int(ex.creation_time.timestamp() + 0.5)} "
                 f"(, "
                 f").\n"
-                f"For more info, use {cmd_mention_help} `page:113`.",
+                f"For more info, use {cmd_help}.",
                 ephemeral=True
             )
             return
@@ -81,7 +82,7 @@ async def remindme(
                 '    hour = h, hour, hours\n'
                 '    minute = m, min, mins, minute, minutes\n'
                 '    second = s, sec, secs, second, seconds\n'
-                f'For more info, use {cmd_mention_help} `page:113`.',
+                f'For more info, use {cmd_help}.',
                 ephemeral=True)
             return
         except TimestampParseException as ex:
@@ -98,7 +99,7 @@ async def remindme(
                 "If you give a time but not a timezone, I don't want you to "
                 "get reminded at the wrong time, "
                 "so I'll say something went wrong.\n"
-                f"For more info, use {cmd_mention_help} `page:113`.",
+                f"For more info, use {cmd_help}.",
                 ephemeral=True
             )
             return
@@ -107,7 +108,7 @@ async def remindme(
                 f"Couldn't make new reminder:\n> {str(ex)}\n\n"
                 f"Be sure you start the reminder time with a number "
                 f"like \"4 days\".\n"
-                f"For more info, use {cmd_mention_help} `page:113`.",
+                f"For more info, use {cmd_help}.",
                 ephemeral=True
             )
             return
@@ -122,7 +123,7 @@ async def remindme(
                 f"providing a unit will give this error. Note: a unix "
                 f"timestamp of 1000000 is 20 Jan 1970 "
                 f"( = )\n"
-                f"For more info, use {cmd_mention_help} `page:113`.",
+                f"For more info, use {cmd_help}.",
                 ephemeral=True
             )
             return
@@ -144,10 +145,10 @@ async def reminders(
         query = {"userID": itx.user.id}
         db_data = collection.find_one(query)
         if db_data is None:
-            cmd_mention = itx.client.get_command_mention("reminder remindme")
+            cmd_reminders = itx.client.get_command_mention("reminder remindme")
             await itx.response.send_message(
                 f"You don't have any reminders running at the moment!\n"
-                f"Use {cmd_mention} to make a reminder!",
+                f"Use {cmd_reminders} to make a reminder!",
                 ephemeral=True)
             return
 
@@ -172,12 +173,12 @@ async def reminders(
                         out.append(f"`{index}` | "
                                    f"")
                         index += 1
-                    cmd_mention = itx.client.get_command_mention(
-                        "reminder reminders")
+                    cmd_reminders = itx.client.get_command_mention_with_args(
+                        "reminder reminders", item="")
                     out_msg = (
                         (f"You have {len(db_data['reminders'])} reminders "
-                         f"(use {cmd_mention} `item: ` to get more info about "
-                         f"a reminder):\n")
+                         f"(use {cmd_reminders} to get more info about a "
+                         f"reminder):\n")
                         + '\n'.join(out)[:1996]
                     )
                 await itx.response.send_message(out_msg, ephemeral=True)
@@ -199,19 +200,19 @@ async def reminders(
                 )
                 return
         except IndexError:
-            cmd_mention = itx.client.get_command_mention("reminder reminders")
+            cmd_reminders = itx.client.get_command_mention("reminder reminders")
             await itx.response.send_message(
                 f"I couldn't find any reminder with that ID!\n"
                 f"Look for the \"ID: `0`\" at the beginning of your reminder "
-                f"on the reminder list ({cmd_mention})",
+                f"on the reminder list ({cmd_reminders})",
                 ephemeral=True,
             )
             return
         except KeyError:
-            cmd_mention = itx.client.get_command_mention("reminder remindme")
+            cmd_reminders = itx.client.get_command_mention("reminder remindme")
             await itx.response.send_message(
                 f"You don't have any reminders running at the moment.\n"
-                f"Use {cmd_mention} to make a reminder!",
+                f"Use {cmd_reminders} to make a reminder!",
                 ephemeral=True,
             )
             return
@@ -227,30 +228,30 @@ async def remove(self, itx: discord.Interaction[Bot], item: int):
         query = {"userID": itx.user.id}
         db_data = collection.find_one(query)
         if db_data is None:
-            cmd_mention = itx.client.get_command_mention("reminder remindme")
+            cmd_remindme = itx.client.get_command_mention("reminder remindme")
             await itx.response.send_message(
                 f"You don't have any reminders running at the moment! (so "
                 f"I can't remove any either..)\n"
-                f"Use {cmd_mention} to make a reminder!",
+                f"Use {cmd_remindme} to make a reminder!",
                 ephemeral=True)
             return
 
         try:
             del db_data['reminders'][item]
         except IndexError:
-            cmd_mention = itx.client.get_command_mention("reminder reminders")
+            cmd_remindme = itx.client.get_command_mention("reminder reminders")
             await itx.response.send_message(
                 f"I couldn't find any reminder with that ID!\n"
                 f"Look for the \"ID: `0`\" at the beginning of your reminder "
-                f"on the reminder list ({cmd_mention})",
+                f"on the reminder list ({cmd_remindme})",
                 ephemeral=True)
             return
         except KeyError:
-            cmd_mention = itx.client.get_command_mention("reminder remindme")
+            cmd_remindme = itx.client.get_command_mention("reminder remindme")
             await itx.response.send_message(
                 f"You don't have any reminders running at the moment. (so "
                 f"I can't remove any either..)\n"
-                f"Use {cmd_mention} to make a reminder!",
+                f"Use {cmd_remindme} to make a reminder!",
                 ephemeral=True)
             return
         query = {"userID": itx.user.id}
diff --git a/extensions/reminders/objects/reminderobject.py b/extensions/reminders/objects/reminderobject.py
index 2a11c66..f674f9c 100644
--- a/extensions/reminders/objects/reminderobject.py
+++ b/extensions/reminders/objects/reminderobject.py
@@ -73,12 +73,12 @@ def __init__(
             # self.remindertime = self.remindertime.astimezone()
             # self.creationtime = self.creationtime.astimezone()
             if self.remindertime < datetime.now().astimezone():
-                cmd_mention = self.client.get_command_mention(
+                cmd_dev_request = self.client.get_command_mention(
                     "developer_request")
                 self.alert = (
                     f"Your reminder was delayed. If this was longer than "
                     f"a day, please report this to the developer using "
-                    f"{cmd_mention}"
+                    f"{cmd_dev_request}."
                     # "Probably because the bot was offline for a while. "
                     # "I hope it didn't cause much of an issue!\n"
                 )
@@ -361,14 +361,14 @@ async def _create_reminder(
         db_data
     )
     _distance = int(distance.timestamp())
-    cmd_mention = itx.client.get_command_mention("reminder reminders")
+    cmd_reminders = itx.client.get_command_mention("reminder reminders")
     view = ShareReminder()
     if from_copy:
         # send message without view.
         await itx.response.send_message(
             f"Successfully created a reminder for you on  "
             f"for \"{reminder}\"!\n"
-            f"Use {cmd_mention} to see your list of reminders",
+            f"Use {cmd_reminders} to see your list of reminders",
             ephemeral=True
         )
         return
@@ -376,7 +376,7 @@ async def _create_reminder(
         await itx.response.send_message(
             f"Successfully created a reminder for you on  "
             f"for \"{reminder}\"!\n"
-            f"Use {cmd_mention} to see your list of reminders",
+            f"Use {cmd_reminders} to see your list of reminders",
             view=view, ephemeral=True
         )
 
@@ -416,12 +416,13 @@ async def parse_and_create_reminder(
     #  (internally chosen limit))
     user_reminders = get_user_reminders(itx.client, itx.user)
     if len(user_reminders) > 50:
-        cmd_mention = itx.client.get_command_mention("reminder reminders")
-        cmd_mention1 = itx.client.get_command_mention("reminder remove")
+        cmd_reminders = itx.client.get_command_mention("reminder reminders")
+        cmd_remove = itx.client.get_command_mention_with_args(
+            "reminder remove", item=" ")
         message = (
-            f"Please don't make more than 50 reminders. Use {cmd_mention} to "
-            f"see your reminders, and use {cmd_mention1} `item: ` to remove "
-            f"a reminder."
+            f"Please don't make more than 50 reminders. Use {cmd_reminders} "
+            f"to see your reminders, and use {cmd_remove} to remove a "
+            f"reminder."
         )
         raise OverflowError(message)
 
diff --git a/extensions/reminders/views/copyreminder.py b/extensions/reminders/views/copyreminder.py
index 8044b35..46c2599 100644
--- a/extensions/reminders/views/copyreminder.py
+++ b/extensions/reminders/views/copyreminder.py
@@ -5,6 +5,7 @@
 import discord
 
 from resources.views.generics import create_simple_button
+from resources.customs import Bot
 
 from extensions.reminders.utils import get_user_reminders
 
@@ -35,26 +36,29 @@ def __init__(
             self.button_callback
         ))
 
-    async def button_callback(self, itx: discord.Interaction):
+    async def button_callback(self, itx: discord.Interaction[Bot]):
         # Check if user has too many reminders
         #  (max 50 allowed (internally chosen limit))
         user_reminders = get_user_reminders(itx.client, itx.user)
         if len(user_reminders) > 50:
-            cmd_mention = itx.client.get_command_mention("reminder reminders")
-            cmd_mention1 = itx.client.get_command_mention("reminder remove")
+            cmd_reminders = itx.client.get_command_mention("reminder reminders")
+            cmd_remove = itx.client.get_command_mention_with_args(
+                "reminder remove", item=" ")
             await itx.response.send_message(
-                f"You already have more than 50 reminders! Use {cmd_mention} "
-                f"to see your reminders, and use {cmd_mention1} `item: ` to "
-                f"remove a reminder",
+                f"You already have more than 50 reminders! Use "
+                f"{cmd_reminders} to see your reminders, and use {cmd_remove} "
+                f"to remove a reminder",
                 ephemeral=True)
             return
         if self.reminder.remindertime < itx.created_at.astimezone():
-            cmd_mention = itx.client.get_command_mention("reminder remindme")
-            cmd_mention1 = itx.client.get_command_mention("help")
+            cmd_remindme = itx.client.get_command_mention_with_args(
+                "reminder remindme", time=" ", reminder=" ")
+            cmd_help = itx.client.get_command_mention_with_args(
+                "help", page="113")
             await itx.response.send_message(
-                f"This reminder has already passed! Use {cmd_mention} to "
-                f"create a new reminder, or use {cmd_mention1} `page:113` "
-                f"for more help about reminders.",
+                f"This reminder has already passed! Use {cmd_remindme} to "
+                f"create a new reminder, or use {cmd_help} for more help "
+                f"about reminders.",
                 ephemeral=True
             )
             return
diff --git a/extensions/settings/cogs/settings.py b/extensions/settings/cogs/settings.py
index ce48def..4342e6f 100644
--- a/extensions/settings/cogs/settings.py
+++ b/extensions/settings/cogs/settings.py
@@ -735,8 +735,9 @@ async def settings(
     ):
         itx.response: discord.InteractionResponse  # noqa
         itx.followup: discord.Webhook  # noqa
-        help_cmd_mention = itx.client.get_command_mention("help")
-        help_str = f"Use {help_cmd_mention} `page:900` for more info."
+        cmd_help = itx.client.get_command_mention_with_args(
+            "help", page="900")
+        help_str = f"Use {cmd_help} for more info."
 
         try:
             modify_mode: ModeAutocomplete | None = None
diff --git a/extensions/tags/cogs/tags.py b/extensions/tags/cogs/tags.py
index 41f187d..b3348c5 100644
--- a/extensions/tags/cogs/tags.py
+++ b/extensions/tags/cogs/tags.py
@@ -189,7 +189,7 @@ async def tag_manage(
             mode: str,
             tag_name: str,
     ):
-        itx.response: discord.InteractionResponse[Bot]  # noqa
+        itx.response: discord.InteractionResponse[Bot]  # type: ignore
         if mode == TagMode.help.value:
             await send_help_menu(itx, 901)
         elif mode == TagMode.create.value:
@@ -220,16 +220,17 @@ async def tag_manage(
             try:
                 color_tuple = await _parse_embed_color_input(color)
             except ValueError as ex:
-                cmd_mention_help = itx.client.get_command_mention('help')
+                cmd_help = itx.client.get_command_mention_with_args(
+                    'help', page="901")
                 await itx.response.send_message(
                     f"Invalid color:\n"
                     f"> {ex}\n"
-                    f"For more help, run {cmd_mention_help} `page:901`",
+                    f"For more help, run {cmd_help}.",
                     ephemeral=True
                 )
                 return
             if report_to_staff.lower() not in ["true", "false"]:
-                await itx.reponse.send_message(
+                await itx.response.send_message(
                     f"Invalid boolean for `report_to_staff`:"
                     f"Expected either `True`, `true`, `False`, or `false`\n"
                     f"but received `{report_to_staff}`.`",
diff --git a/extensions/tags/modals/create_tag.py b/extensions/tags/modals/create_tag.py
index 97673be..01435d0 100644
--- a/extensions/tags/modals/create_tag.py
+++ b/extensions/tags/modals/create_tag.py
@@ -1,5 +1,7 @@
 import discord
 
+from resources.customs import Bot
+
 
 class CreateTagModal(discord.ui.Modal):
     embed_title = discord.ui.TextInput(
@@ -36,7 +38,7 @@ class CreateTagModal(discord.ui.Modal):
     def __init__(self):
         super().__init__(title="Create a custom tag.")
 
-        self.return_interaction = None
+        self.return_interaction: discord.Interaction[Bot] | None = None
 
-    async def on_submit(self, interaction: discord.Interaction):
+    async def on_submit(self, interaction: discord.Interaction[Bot]):
         self.return_interaction = interaction
diff --git a/extensions/tags/tags.py b/extensions/tags/tags.py
index 6d1b0a8..1fcd3ff 100644
--- a/extensions/tags/tags.py
+++ b/extensions/tags/tags.py
@@ -54,13 +54,11 @@ def log_message(self) -> str:
         user_id: str = getattr(self.send_user, "id", "unknown id")
 
         return (
-            f"{username} (`{user_id}`) "
-            f"used {self.command_mention} `tag:{self.id}` "
-            f"anonymously"
-            + (f", in {self.send_channel.mention} (`{self.send_channel.id}`)\n"
+            f"{username} (`{user_id}`) used {self.command_mention} anonymously"
+            + (f", in {self.send_channel.mention} (`{self.send_channel.id}`)"
                if self.send_channel is not None
                else "")
-            + (f"[Jump to the tag message]({self.public_message.jump_url})"
+            + (f"\n[Jump to the tag message]({self.public_message.jump_url})"
                if self.public_message is not None
                else "")
         )
@@ -88,7 +86,8 @@ async def send(
          sensitive tags are sent.
         """
         self.send_user = itx.user
-        self.command_mention = itx.client.get_command_mention("tag")
+        self.command_mention = itx.client.get_command_mention_with_args(
+            "tag", tag=self.id)
 
         if public:
             await self._handle_send_publicly(
@@ -230,9 +229,9 @@ async def send_enabling_embeds_info(
 
 
 async def _send_tag_log_message(itx, tag_name) -> None:
-    cmd_mention = itx.client.get_command_mention("tag")
-    log_msg = (f"{itx.user.name} ({itx.user.id}) "
-               f"used {cmd_mention} `tag:{tag_name}` anonymously")
+    cmd_tag = itx.client.get_command_mention_with_args(
+        "tag", tag=tag_name)
+    log_msg = f"{itx.user.name} ({itx.user.id}) used {cmd_tag} anonymously"
     await log_to_guild(itx.client, itx.guild, log_msg)
 
 
diff --git a/extensions/termdictionary/cogs/termdictionary.py b/extensions/termdictionary/cogs/termdictionary.py
index 576335c..541b993 100644
--- a/extensions/termdictionary/cogs/termdictionary.py
+++ b/extensions/termdictionary/cogs/termdictionary.py
@@ -96,12 +96,11 @@ async def _get_custom_dictionary_output(
         if simplify(term) in simplify(item["synonyms"]):
             results.append((item["term"], item["definition"]))
     if not results:
-        cmd_mention_define = client.get_command_mention(
-            "dictionary_staff define")
+        cmd_define = client.get_command_mention("dictionary_staff define")
         raise KeyError(
             f"No information found for '{term}' in the custom dictionary.\n"
             f"If you would like to add a term, message a staff member "
-            f"(to use {cmd_mention_define})"
+            f"(to use {cmd_define})"
         )
 
     result_str = (f"I found {len(results)} result{'s' * (len(results) > 1)} "
@@ -465,13 +464,18 @@ async def dictionary(
             if len(data) == 0:
                 if source == 7:
                     result_str = f"I didn't find any results for '{term}' online or in our fancy dictionaries"
-                    cmd_mention_dict = itx.client.get_command_mention("dictionary")
-                    cmd_mention_def = itx.client.get_command_mention("dictionary_staff define")
-                    await log_to_guild(itx.client, itx.guild,
-                                       f":warning: **!! Alert:** {itx.user.name} ({itx.user.id}) searched for "
-                                       f"'{term}' in the terminology dictionary and online, but there were "
-                                       f"no results. Maybe we should add this term to "
-                                       f"the {cmd_mention_dict} command ({cmd_mention_def})")
+                    cmd_dictionary = itx.client.get_command_mention(
+                        "dictionary")
+                    cmd_define = itx.client.get_command_mention_with_args(
+                        "dictionary_staff define", term=" ", definition=" ")
+                    await log_to_guild(
+                        itx.client,
+                        itx.guild,
+                        f":warning: **!! Alert:** {itx.user.name} "
+                        f"({itx.user.id}) searched for '{term}' in the "
+                        f"terminology dictionary and online, but there were "
+                        f"no results. Maybe we should add this term to "
+                        f"the {cmd_dictionary} command ({cmd_define})")
                 else:
                     result_str = f"I didn't find any results for '{term}' on urban dictionary"
                 public = False
@@ -508,9 +512,13 @@ async def define(self, itx: discord.Interaction[Bot], term: str, definition: str
         query = {"term": term}
         search = collection.find_one(query)
         if search is not None:
-            cmd_mention = itx.client.get_command_mention("dictionary")
+            cmd_dictionary = itx.client.get_command_mention_with_args(
+                "dictionary", term=term)
             await itx.response.send_message(
-                f"You have already previously defined this term (try to find it with {cmd_mention}).", ephemeral=True)
+                f"You have already previously defined this term (try to find "
+                f"it with {cmd_dictionary}).",
+                ephemeral=True,
+            )
             return
         await itx.response.defer(ephemeral=True)
         # Test if a synonym is already used before
@@ -551,9 +559,11 @@ async def redefine(self, itx: discord.Interaction[Bot], term: str, definition: s
         query = {"term": term}
         search = collection.find_one(query)
         if search is None:
-            cmd_mention = itx.client.get_command_mention("dictionary_staff define")
+            cmd_define = itx.client.get_command_mention_with_args(
+                "dictionary_staff define", term=" ", definition=" ")
             await itx.response.send_message(
-                f"This term hasn't been added to the dictionary yet, and thus cannot be redefined! Use {cmd_mention}.",
+                f"This term hasn't been added to the dictionary yet, and "
+                f"thus cannot be redefined! Use {cmd_define}.",
                 ephemeral=True)
             return
         collection.update_one(query, {"$set": {"definition": definition}})
@@ -597,10 +607,11 @@ async def edit_synonym(self, itx: discord.Interaction[Bot], term: str, mode: int
         query = {"term": term}
         search = collection.find_one(query)
         if search is None:
-            cmd_mention = itx.client.get_command_mention("dictionary_staff define")
+            cmd_define = itx.client.get_command_mention_with_args(
+                "dictionary_staff define", term=" ", definition=" ")
             await itx.response.send_message(
                 f"This term hasn't been added to the dictionary yet, and thus cannot get "
-                f"new synonyms! Use {cmd_mention}.",
+                f"new synonyms! Use {cmd_define}.",
                 ephemeral=True)
             return
 
diff --git a/extensions/todolist/cogs/todolist.py b/extensions/todolist/cogs/todolist.py
index 373a148..6abf2d3 100644
--- a/extensions/todolist/cogs/todolist.py
+++ b/extensions/todolist/cogs/todolist.py
@@ -31,12 +31,13 @@ async def todo(
         # todo: use Enum for mode
         if mode == 1:  # Add item to to-do list
             if todo is None:
-                cmd_mention = itx.client.get_command_mention("todo")
+                cmd_todo = itx.client.get_command_mention_with_args(
+                    "todo", mode="Check")
                 await itx.response.send_message(
                     f"This command lets you add items to your to-do list!\n"
                     f"Type whatever you still plan to do in the `todo: `"
                     f" argument, and then you can see your current to-do list "
-                    f"with {cmd_mention} `mode:Check`!",
+                    f"with {cmd_todo}!",
                     ephemeral=True,
                 )
                 return
@@ -69,11 +70,12 @@ async def todo(
 
         elif mode == 2:  # Remove item from to-do list
             if todo is None:
-                cmd_mention = itx.client.get_command_mention("todo")
+                cmd_todo = itx.client.get_command_mention_with_args(
+                    "todo", mode="Check")
                 await itx.response.send_message(
                     f"Removing todo's with this command is done with IDs. "
                     f"You can see your current list of todo's using "
-                    f"{cmd_mention} `mode:Check`.\n"
+                    f"{cmd_todo}.\n"
                     f"This list will start every todo-list item with a "
                     f"number. This is the ID you're looking for. This number "
                     f"can be filled into the `todo: ` argument to remove it.",
@@ -105,10 +107,10 @@ async def todo(
             try:
                 del todo_list[todo]
             except IndexError:
-                cmd_mention = itx.client.get_command_mention("todo")
+                cmd_todo = itx.client.get_command_mention("todo")
                 await itx.response.send_message(
                     f"Couldn't delete that ID, because there isn't any item "
-                    f"on your list with that ID. Use {cmd_mention} "
+                    f"on your list with that ID. Use {cmd_todo} "
                     f"`mode:Check` to see the IDs assigned to each item on "
                     f"your list",
                     ephemeral=True,
diff --git a/extensions/toneindicator/cogs/toneindicator.py b/extensions/toneindicator/cogs/toneindicator.py
index dd3b08e..d93b819 100644
--- a/extensions/toneindicator/cogs/toneindicator.py
+++ b/extensions/toneindicator/cogs/toneindicator.py
@@ -266,11 +266,12 @@ def _handle_tag_definition(
         # > definition: /tag1 [!], /tag2, /tag3
         result_str += f"- \"{definition}\": {', '.join(acronyms)}\n"
     if any_overlaps:
-        cmd_mention = itx.client.get_command_mention("toneindicator")
+        cmd_toneindicator = itx.client.get_command_mention_with_args(
+            "toneindicator", mode="Exact acronym", tag=" ")
         result_str += (
-            f"Some acronyms have multiple definitions, marked with "
-            f"`[!]`. Use {cmd_mention} `mode:Exact acronym` `tag: `"
-            f"to find the other possible definitions for these tags."
+            f"Some acronyms have multiple definitions, marked with `[!]`. "
+            f"Use {cmd_toneindicator} to find the other possible definitions "
+            f"for these tags."
         )
     return result_str, bool(results)
 
diff --git a/extensions/watchlist/cogs/watchlist.py b/extensions/watchlist/cogs/watchlist.py
index 4dff8a8..f482700 100644
--- a/extensions/watchlist/cogs/watchlist.py
+++ b/extensions/watchlist/cogs/watchlist.py
@@ -89,10 +89,16 @@ async def _create_uncool_watchlist_thread(
     active_staff_role: discord.Guild | None = client.get_guild_attribute(
         watch_channel.guild, AttributeKeys.watchlist_reaction_role)
     if active_staff_role is None:
-        cmd_mention_settings = client.get_command_mention("settings")
+        cmd_settings = client.get_command_mention_with_args(
+            "settings",
+            type="Attribute",
+            setting=AttributeKeys.watchlist_reaction_role,
+            mode="Set",
+            value=" "
+        )
         await joiner_msg.edit(
             content=f"No role has been set up to be pinged when a watchlist "
-                    f"is created. Use {cmd_mention_settings} to add one."
+                    f"is created. Use {cmd_settings} to add one."
         )
     else:
         await joiner_msg.edit(content=f"<@&{active_staff_role.id}>")
diff --git a/resources/customs/bot.py b/resources/customs/bot.py
index 459fa7c..5368b62 100644
--- a/resources/customs/bot.py
+++ b/resources/customs/bot.py
@@ -82,6 +82,17 @@ def get_command_mention(self, command_string: str) -> str:
                                 return subcmdgroup.mention
         return "/" + command_string
 
+    def get_command_mention_with_args(
+            self,
+            command_string: str,
+            **kwargs: str
+    ) -> str:
+        command_mention = self.get_command_mention(command_string)
+        for key, value in kwargs.items():
+            command_mention += f" `{key}:{value}`"
+
+        return command_mention
+
     def get_guild_attribute(
             self,
             guild_id: discord.Guild | int,

From 3eb61dcbd701a4c801ba41206c0135f0719a5133 Mon Sep 17 00:00:00 2001
From: MysticMia <65396931+MysticMia@users.noreply.github.com>
Date: Tue, 20 May 2025 05:12:46 +0200
Subject: [PATCH 09/26] Rename progress bar .step and .progress methods to
 reduce ambiguity.

---
 main.py                                       | 56 +++++++-------
 resources/customs/progressbar.py              | 58 +++++++-------
 .../resources/customs/test_progressbar.py     | 76 +++++++++----------
 3 files changed, 94 insertions(+), 96 deletions(-)

diff --git a/main.py b/main.py
index 250e0ce..bb6719d 100644
--- a/main.py
+++ b/main.py
@@ -99,7 +99,7 @@ def get_token_data() -> tuple[
     :raise KeyError: If the api_keys.json file is missing the api key
      for an api used in the program.
     """
-    load_progress.progress("Loading api keys...")
+    load_progress.begin("Loading api keys...")
     try:
         with open("api_keys.json", "r") as f:
             api_keys = json.loads(f.read())
@@ -125,14 +125,14 @@ def get_token_data() -> tuple[
     if missing_tokens:
         raise KeyError("Missing API key for: " + ', '.join(missing_tokens))
 
-    load_progress.progress("Loading database clusters...")
+    load_progress.begin("Loading database clusters...")
     cluster: MongoClient = MongoClient(tokens['MongoDB'])
     rina_db: PyMongoDatabase = cluster["Rina"]
     cluster: motorcore.AgnosticClient = motorasync.AsyncIOMotorClient(
         tokens['MongoDB'])
     async_rina_db: motorcore.AgnosticDatabase  # = cluster["Rina"]
     async_rina_db = cluster.get_database("Rina", codec_options=codec_options)
-    load_progress.step("Loaded database clusters", newline=False)
+    load_progress.complete("Loaded database clusters", newline=False)
     return bot_token, tokens, rina_db, async_rina_db
 
 
@@ -145,7 +145,7 @@ def get_version() -> str:
 
     :return: Current version/instance of the bot.
     """
-    load_progress.progress("Loading version...")
+    load_progress.begin("Loading version...")
     file_version = BOT_VERSION.split(".")
     try:
         os.makedirs("outputs", exist_ok=True)
@@ -164,7 +164,7 @@ def get_version() -> str:
     rina_version = '.'.join(rina_version)
     with open("outputs/version.txt", "w") as f:
         f.write(f"{rina_version}")
-    load_progress.step("Loaded version", newline=False)
+    load_progress.complete("Loaded version", newline=False)
     return rina_version
 
 
@@ -174,7 +174,7 @@ def create_client(
         async_rina_db: motorcore.AgnosticDatabase,
         version: str
 ) -> Bot:
-    load_progress.progress("Creating bot")
+    load_progress.begin("Creating bot")
 
     intents = discord.Intents.default()
     intents.members = True
@@ -200,7 +200,7 @@ def create_client(
         activity=discord.Game(name="with slash (/) commands!"),
         allowed_mentions=discord.AllowedMentions(everyone=False),
     )
-    start_progress.step("Created Bot")
+    start_progress.complete("Created Bot")
     return bot
 
 
@@ -208,7 +208,7 @@ def start_app():
     (token, tokens, rina_db, async_rina_db) = get_token_data()
     version = get_version()
     client = create_client(tokens, rina_db, async_rina_db, version)
-    start_progress.progress("Starting Bot...")
+    start_progress.begin("Starting Bot...")
 
     # this can probably be done better
     # region Client events
@@ -217,7 +217,7 @@ async def on_ready():
         text = (f"Logged in as {client.user}, in version {version} "
                 f"(in {datetime.now().astimezone() - program_start})")
         try:
-            start_progress.step(text)
+            start_progress.complete(text)
         except OverflowError:
             debug(text, color="green")
 
@@ -226,26 +226,26 @@ async def on_ready():
 
         post_startup_progress = ProgressBar(4)
 
-        post_startup_progress.progress("Loading all server settings...")
+        post_startup_progress.begin("Loading all server settings...")
         client.server_settings = await ServerSettings.fetch_all(client)
-        post_startup_progress.step("Loaded server settings.")
+        post_startup_progress.complete("Loaded server settings.")
 
-        post_startup_progress.progress("Loading all server tags...")
+        post_startup_progress.begin("Loading all server tags...")
         _ = await fetch_all_tags(client.async_rina_db)
-        post_startup_progress.step("Loaded server tags.")
+        post_startup_progress.complete("Loaded server tags.")
 
-        post_startup_progress.progress("Loading all watchlist threads...")
+        post_startup_progress.begin("Loading all watchlist threads...")
         _ = await fetch_all_watchlists(client.async_rina_db)
-        post_startup_progress.step("Loaded watchlist threads.")
+        post_startup_progress.complete("Loaded watchlist threads.")
 
-        post_startup_progress.progress("Loading all starboard messages...")
+        post_startup_progress.begin("Loading all starboard messages...")
         _ = await fetch_all_starboard_messages(client.async_rina_db)
-        post_startup_progress.step("Loaded starboard messages.")
+        post_startup_progress.complete("Loaded starboard messages.")
 
     @client.event
     async def setup_hook():
-        start_progress.step("Started Bot")
-        start_progress.progress("Load extensions and scheduler")
+        start_progress.complete("Started Bot")
+        start_progress.begin("Load extensions and scheduler")
         logger = logging.getLogger("apscheduler")
         logger.setLevel(logging.WARNING)
         # remove annoying 'Scheduler started' message on sched.start()
@@ -259,14 +259,14 @@ async def setup_hook():
         extension_loading_start_time = datetime.now().astimezone()
         extension_load_progress = ProgressBar(len(EXTENSIONS))
         for extID in range(len(EXTENSIONS)):
-            extension_load_progress.progress(f"Loading {EXTENSIONS[extID]}")
+            extension_load_progress.begin(f"Loading {EXTENSIONS[extID]}")
             await client.load_extension(
                 "extensions." + EXTENSIONS[extID] + ".module")
-        start_progress.step(
+        start_progress.complete(
             f"Loaded extensions successfully (in "
             f"{datetime.now().astimezone() - extension_loading_start_time})"
         )
-        start_progress.progress("Loading server settings...")
+        start_progress.begin("Loading server settings...")
         try:
             client.log_channel = \
                 await client.fetch_channel(988118678962860032)
@@ -280,14 +280,14 @@ async def setup_hook():
         # ^ or client.owner / client.owner_id :P
         # can't use the commented out code because Rina is owned by
         # someone else in the main server than the dev server (=not me).
-        start_progress.step("Loaded server settings")
-        start_progress.progress("Restarting ongoing reminders...")
+        start_progress.complete("Loaded server settings")
+        start_progress.begin("Restarting ongoing reminders...")
         await relaunch_ongoing_reminders(client)
-        start_progress.step("Finished setting up reminders")
-        start_progress.progress("Caching bot's command names and their ids")
+        start_progress.complete("Finished setting up reminders")
+        start_progress.begin("Caching bot's command names and their ids")
         client.commandList = await client.tree.fetch_commands()
-        start_progress.step("Cached bot's command names and their ids")
-        start_progress.progress("Starting...")
+        start_progress.complete("Cached bot's command names and their ids")
+        start_progress.begin("Starting...")
 
     # endregion
 
diff --git a/resources/customs/progressbar.py b/resources/customs/progressbar.py
index 407cb39..bfbd394 100644
--- a/resources/customs/progressbar.py
+++ b/resources/customs/progressbar.py
@@ -4,20 +4,20 @@
 class ProgressBar:
     def __init__(
             self,
-            max_steps: int,
+            max_completes: int,
             *,
-            fill_char: str = '#',
-            progress_char: str = '+',
+            complete_char: str = '#',
+            begin_char: str = '+',
             empty_char: str = ' '
     ):
-        self._max_steps = max_steps
-        self._step = 0
-        self._fill_char = fill_char
-        self._progress_char = progress_char
+        self._max_completes = max_completes
+        self._completions = 0
+        self._complete_char = complete_char
+        self._begin_char = begin_char
         self._empty_char = empty_char
-        if not (len(fill_char) == len(progress_char) == len(empty_char)):
+        if not (len(complete_char) == len(begin_char) == len(empty_char)):
             raise ValueError("Progress characters must have the same length!")
-        self._just_progressed = False
+        self._just_began = False
         self._previous_message_length = 0
 
     def _get_progess_bar(self, *, busy) -> str:
@@ -25,17 +25,17 @@ def _get_progess_bar(self, *, busy) -> str:
         A helper function to construct a progress bar based on
         current progress.
 
-        :param busy: Whether to add a :py:attr:`_progress_char` in
+        :param busy: Whether to add a :py:attr:`_begin_char` in
          the bar.
         :return: A progress bar string of the current progress, with
          opening and closing parentheses: "[   ]:".
         """
         out = "["
-        out += self._fill_char * self._step
+        out += self._complete_char * self._completions
         if busy:
-            out += self._progress_char
-        # [###+ = 5 chars. Max step may be 4. Pad 0 characters. [###+]:
-        pad_chars = self._max_steps - len(out) + 1
+            out += self._begin_char
+        # [###+ = 5 chars. Max complete may be 4. Pad 0 characters. [###+]:
+        pad_chars = self._max_completes - len(out) + 1
         if pad_chars < 0:
             raise OverflowError("Progress exceeded size of progress bar!")
         out += self._empty_char * pad_chars
@@ -45,50 +45,50 @@ def _get_progess_bar(self, *, busy) -> str:
     def _get_line_clear_padding(self, string) -> str:
         """
         A helper function to get the amount of spaces necessary to
-        overwrite the previous progress message.
+        overwrite the previous 'begin' message.
 
-        :param string: The text of the upcoming progress message.
+        :param string: The text of the upcoming 'begin' message.
         :return: A string with spaces to overwrite the previous
-         progress message.
+         'begin' message.
         """
         length = len(string)
         length = max(self._previous_message_length - length, 0)
         return " " * length
 
-    def progress(self, text, *, newline=False) -> None:
+    def begin(self, text, *, newline=False) -> None:
         """
         Print a debug log, incrementing the progress bar with
-        the :py:attr:`_progress_char`.
+        the :py:attr:`_begin_char`.
 
         :param text: The text to place after the progress bar.
         :param newline: Whether to place a newline after the progress
          bar, or reset the caret to overwrite this line with the next
-         progress bar step.
+         progress bar completion.
         """
         end = '\n' if newline else '\r'
-        if self._just_progressed:
-            # to prevent two progress chars in a row: "[#++ ]:"
-            self._step += 1
+        if self._just_began:
+            # to prevent 2x begin_char in a row: "[#++ ]:"
+            self._completions += 1
         progress_bar = self._get_progess_bar(busy=True)
         padding = self._get_line_clear_padding(text)
         debug(progress_bar + text + padding, color="light_blue", end=end)
         self._previous_message_length = len(text) if not newline else 0
-        self._just_progressed = True
+        self._just_began = True
 
-    def step(self, text, *, newline=True):
+    def complete(self, text, *, newline=True):
         """
         Print a debug log, incrementing the progress bar with
-        the :py:attr:`_fill_char`.
+        the :py:attr:`_complete_char`.
 
         :param text: The text to place after the progress bar.
         :param newline: Whether to place a newline after the progress
          bar, or reset the caret to overwrite this line with the next
-         progress bar step.
+         progress bar completion.
         """
         end = '\n' if newline else '\r'
-        self._step += 1
+        self._completions += 1
         progress_bar = self._get_progess_bar(busy=False)
         padding = self._get_line_clear_padding(text)
         debug(progress_bar + text + padding, color="green", end=end)
         self._previous_message_length = len(text) if not newline else 0
-        self._just_progressed = False
+        self._just_began = False
diff --git a/unit_tests/resources/customs/test_progressbar.py b/unit_tests/resources/customs/test_progressbar.py
index 7a42ba9..427783d 100644
--- a/unit_tests/resources/customs/test_progressbar.py
+++ b/unit_tests/resources/customs/test_progressbar.py
@@ -28,13 +28,13 @@ def get_original_message(message: str) -> str:
     return message
 
 
-def test_progress(caplog):
+def test_begin(caplog):
     # Arrange
     progressbar = ProgressBar(4)
 
     # Act
     with caplog.at_level(logging.INFO):
-        progressbar.progress("a b c")
+        progressbar.begin("a b c")
 
     message = caplog.messages[0]
 
@@ -52,13 +52,13 @@ def test_progress(caplog):
     assert "[+   ]: a b c" == message
 
 
-def test_step(caplog):
+def test_complete(caplog):
     # Arrange
     progressbar = ProgressBar(5)
 
     # Act
     with caplog.at_level(logging.INFO):
-        progressbar.step("b c d")
+        progressbar.complete("b c d")
 
     message = caplog.messages[0]
 
@@ -66,7 +66,7 @@ def test_step(caplog):
     assert starts_with_green(message), \
         f"Message '{message}' was not green."
     assert ends_with_color_reset(message), \
-        f"Message '{message}' did not end with a color reset."
+        f"Message '{message}' did not end with a color reset (and newline)."
 
     # Act
     message = get_original_message(message)
@@ -75,13 +75,13 @@ def test_step(caplog):
     assert "[#    ]: b c d" == message
 
 
-def test_progress_newline(caplog):
+def test_begin_newline(caplog):
     # Arrange
     progressbar = ProgressBar(4)
 
     # Act
     with caplog.at_level(logging.INFO):
-        progressbar.progress("a b c", newline=True)
+        progressbar.begin("a b c", newline=True)
 
     message = caplog.messages[0]
 
@@ -89,7 +89,7 @@ def test_progress_newline(caplog):
     assert starts_with_light_blue(message), \
         f"Message '{message}' was not light blue."
     assert ends_with_color_reset(message), \
-        f"Message '{message}' did not end with a color reset."
+        f"Message '{message}' did not end with a color reset (and newline)."
 
     # Act
     message = get_original_message(message)
@@ -98,13 +98,13 @@ def test_progress_newline(caplog):
     assert "[+   ]: a b c" == message
 
 
-def test_step_no_newline(caplog):
+def test_complete_no_newline(caplog):
     # Arrange
     progressbar = ProgressBar(5)
 
     # Act
     with caplog.at_level(logging.INFO):
-        progressbar.step("b c d", newline=False)
+        progressbar.complete("b c d", newline=False)
 
     message = caplog.messages[0]
 
@@ -122,14 +122,14 @@ def test_step_no_newline(caplog):
     assert "[#    ]: b c d" == message
 
 
-def test_step_step(caplog):
+def test_complete_complete(caplog):
     # Arrange
     progressbar = ProgressBar(5)
-    progressbar.step("")
+    progressbar.complete("")
 
     # Act
     with caplog.at_level(logging.INFO):
-        progressbar.step("")
+        progressbar.complete("")
 
     message = caplog.messages[0]
 
@@ -137,7 +137,7 @@ def test_step_step(caplog):
     assert starts_with_green(message), \
         f"Message '{message}' was not green."
     assert ends_with_color_reset(message), \
-        f"Message '{message}' did not end with a color reset."
+        f"Message '{message}' did not end with a color reset (and newline)."
 
     # Act
     message = get_original_message(message)
@@ -147,14 +147,14 @@ def test_step_step(caplog):
 
 
 # noinspection DuplicatedCode
-def test_step_progress(caplog):
+def test_complete_begin(caplog):
     # Arrange
     progressbar = ProgressBar(5)
-    progressbar.step("")
+    progressbar.complete("")
 
     # Act
     with caplog.at_level(logging.INFO):
-        progressbar.progress("")
+        progressbar.begin("")
 
     message = caplog.messages[0]
 
@@ -173,14 +173,14 @@ def test_step_progress(caplog):
 
 
 # noinspection DuplicatedCode
-def test_progress_progress(caplog):
+def test_begin_begin(caplog):
     # Arrange
     progressbar = ProgressBar(5)
-    progressbar.progress("")
+    progressbar.begin("")
 
     # Act
     with caplog.at_level(logging.INFO):
-        progressbar.progress("")
+        progressbar.begin("")
 
     message = caplog.messages[0]
 
@@ -198,24 +198,22 @@ def test_progress_progress(caplog):
     assert "[#+   ]: " == message
 
 
-def test_progress_step(caplog):
+def test_begin_complete(caplog):
     # Arrange
     progressbar = ProgressBar(5)
-    progressbar.progress("")
+    progressbar.begin("")
 
     # Act
     with caplog.at_level(logging.INFO):
-        progressbar.step("")
+        progressbar.complete("")
 
     message = caplog.messages[0]
 
     # Assert
-    # todo: make a function "assert progress" to test both two \/
     assert starts_with_green(message), \
         f"Message '{message}' was not green."
     assert ends_with_color_reset(message), \
-        (f"Message '{message}' did not end with a color reset and "
-         f"carriage return.")
+        f"Message '{message}' did not end with a color reset (and newline)."
 
     # Act
     message = get_original_message(message)
@@ -227,27 +225,27 @@ def test_progress_step(caplog):
 def test_padding_length(caplog):
     # Arrange
     progressbar = ProgressBar(9)
-    progressbar.progress("qwerty" * 3)
-    expected_progress = "[#+       ]: "
+    progressbar.begin("qwerty" * 3)
+    expected_progress_bar = "[#+       ]: "
 
     # Act
     with caplog.at_level(logging.INFO):
-        progressbar.progress("")
+        progressbar.begin("")
 
     message = get_original_message(caplog.messages[0])
-    expected_output = expected_progress + " " * len("qwerty") * 3
+    expected_output = expected_progress_bar + " " * len("qwerty") * 3
 
     # Assert
     assert expected_output == message
 
 
-def test_short_bar_step(caplog):
+def test_short_bar_complete(caplog):
     # Arrange
     progressbar = ProgressBar(1)
 
     # Act
     with caplog.at_level(logging.INFO):
-        progressbar.step("")
+        progressbar.complete("")
 
     message = get_original_message(caplog.messages[0])
 
@@ -255,13 +253,13 @@ def test_short_bar_step(caplog):
     assert "[#]: " == message
 
 
-def test_short_bar_progress(caplog):
+def test_short_bar_begin(caplog):
     # Arrange
     progressbar = ProgressBar(1)
 
     # Act
     with caplog.at_level(logging.INFO):
-        progressbar.progress("")
+        progressbar.begin("")
 
     message = get_original_message(caplog.messages[0])
 
@@ -269,13 +267,13 @@ def test_short_bar_progress(caplog):
     assert "[+]: " == message
 
 
-def test_short_bar_progress_step(caplog):
+def test_short_bar_begin_complete(caplog):
     # Arrange
     progressbar = ProgressBar(1)
-    progressbar.progress("")
+    progressbar.begin("")
     # Act
     with caplog.at_level(logging.INFO):
-        progressbar.step("")
+        progressbar.complete("")
 
     message = get_original_message(caplog.messages[0])
 
@@ -286,8 +284,8 @@ def test_short_bar_progress_step(caplog):
 def test_progress_overflow(caplog):
     # Arrange
     progressbar = ProgressBar(1)
-    progressbar.step("")
+    progressbar.complete("")
 
     # Assert
     with pytest.raises(OverflowError):
-        progressbar.step("")
+        progressbar.complete("")

From ad9a55501ef99881f31ca112350dd56c157a9162 Mon Sep 17 00:00:00 2001
From: MysticMia <65396931+MysticMia@users.noreply.github.com>
Date: Tue, 20 May 2025 06:00:43 +0200
Subject: [PATCH 10/26] Clean up PageView typing, __init__, and wrap lines

---
 extensions/addons/equaldexregion.py |  3 +-
 extensions/help/views/helppage.py   |  3 +-
 resources/views/generics.py         | 81 ++++++++++++++++++-----------
 3 files changed, 55 insertions(+), 32 deletions(-)

diff --git a/extensions/addons/equaldexregion.py b/extensions/addons/equaldexregion.py
index 4e90c01..749c68b 100644
--- a/extensions/addons/equaldexregion.py
+++ b/extensions/addons/equaldexregion.py
@@ -26,4 +26,5 @@ def __init__(self, data: dict):
         self.name: str = data['name']
         self.continent = data['continent']
         self.url: str = data['url']
-        self.issues: dict[str, list | dict[str, dict[str]]] = data['issues']
+        # noinspection LongLine
+        self.issues: dict[str, list[str] | dict[str, dict[str, str]]] = data['issues']
diff --git a/extensions/help/views/helppage.py b/extensions/help/views/helppage.py
index eb1bef0..c2266a1 100644
--- a/extensions/help/views/helppage.py
+++ b/extensions/help/views/helppage.py
@@ -119,8 +119,7 @@ def __init__(
         first_page_index = list(self.pages).index(first_page_key)
         super().__init__(
             first_page_index,
-            len(self.pages) - 1,
-            self.update_page  # todo: remove redundant self.update_page arg
+            len(self.pages) - 1
         )
         # move jump_to_page button to the end of the view
         self._children.append(self._children.pop(1))
diff --git a/resources/views/generics.py b/resources/views/generics.py
index 565998a..673ad10 100644
--- a/resources/views/generics.py
+++ b/resources/views/generics.py
@@ -1,6 +1,10 @@
 from __future__ import annotations
+
+from abc import abstractmethod
+
 import discord
 import typing
+import types
 
 from resources.customs import Bot
 
@@ -9,8 +13,8 @@ def create_simple_button(
         label: str,
         style: discord.ButtonStyle,
         callback: typing.Callable[
-            [discord.Interaction],
-            typing.Coroutine[typing.Any, typing.Any, None]],
+            [discord.Interaction[Bot]],
+            typing.Coroutine[typing.Any, typing.Any, typing.Any]],
         disabled: bool = False,
         label_is_emoji: bool = False,
 ) -> discord.ui.Button:
@@ -64,11 +68,11 @@ def __init__(
             button_false[0], button_false[1], self.on_button_false
         ))
 
-    async def on_button_true(self, _: discord.Interaction):
+    async def on_button_true(self, _: discord.Interaction[Bot]):
         self.value = True
         self.stop()
 
-    async def on_button_false(self, _: discord.Interaction):
+    async def on_button_false(self, _: discord.Interaction[Bot]):
         self.value = False
         self.stop()
 
@@ -85,36 +89,47 @@ def page_down_style(self) -> tuple[discord.ButtonStyle, bool]:
         :return: A tuple of the button color and whether the button
          should be disabled.
         """
+        disabled = (self.page == 0
+                    and not self.loop_around_pages)
         # set color to gray if clicking the button would make the page
         #  out of bounds and thus loop around.
-        return (
-            (discord.ButtonStyle.gray if self.page == 0
-             else discord.ButtonStyle.blurple),
-            self.page == 0 and not self.loop_around_pages
-        )
+        if self.page == 0:
+            button_style = discord.ButtonStyle.gray
+        else:
+            button_style = discord.ButtonStyle.blurple
+        
+        return button_style, disabled
 
     @property
     def page_up_style(self) -> tuple[discord.ButtonStyle, bool]:
         """
-        Gives the button style depending on the current page: If incrementing the page index would
-        make it out of bounds, make it gray; else blurple. If loop_around_pages is disabled, gray
+        Gives the button style depending on the current page: If
+        incrementing the page index would make it out of bounds, make
+        it gray; else blurple. If loop_around_pages is disabled, gray
         buttons will be disabled too.
 
-        :return: A tuple of the button color and whether the button should be disabled.
+        :return: A tuple of the button color and whether the button
+         should be disabled.
         """
-        # set color to gray if clicking the button would make the page out of bounds and thus loop around
-        return (
-            (discord.ButtonStyle.gray if self.page == self.max_page_index
-             else discord.ButtonStyle.blurple),
-            self.page == self.max_page_index and not self.loop_around_pages
-        )
+        disabled = (self.page == self.max_page_index
+                    and not self.loop_around_pages)
+        # set color to gray if clicking the button would make the page
+        #  out of bounds and thus loop around.
+        if self.page == self.max_page_index:
+            button_style = discord.ButtonStyle.gray
+        else:
+            button_style = discord.ButtonStyle.blurple
 
+        return button_style, disabled
+
+    @abstractmethod
     async def update_page(self, itx: discord.Interaction[Bot], view: PageView):
         """
-        Update the page message. This typically involves calculating the message content for the message and updating
-        the original message.
+        Update the page message. This typically involves calculating the
+        message content for the message and updating the original message.
 
-        :param itx: The interaction gained from the button or modal interaction by the user.
+        :param itx: The interaction gained from the button or modal
+         interaction by the user.
         :param view: The view class instance.
         """
         pass
@@ -123,9 +138,13 @@ def __init__(
             self,
             starting_page: int,
             max_page_index: int,
-            page_update_function: typing.Callable[[discord.Interaction, PageView], typing.Awaitable[None]],
-            prepended_buttons: list[discord.ui.Button] = None,
-            appended_buttons: list[discord.ui.Button] = None,
+            page_update_function: typing.Callable[
+                                      [discord.Interaction[Bot], PageView],
+                                      types.CoroutineType[
+                                          typing.Any, typing.Any, None]
+                                  ] | None = None,
+            prepended_buttons: list[discord.ui.Button] | None = None,
+            appended_buttons: list[discord.ui.Button] | None = None,
             loop_around_pages: bool = True,
             timeout=None
     ):
@@ -137,7 +156,8 @@ def __init__(
         if appended_buttons is None:
             appended_buttons = []
         self.page: int = starting_page
-        self.update_page = page_update_function
+        if page_update_function is not None:
+            self.update_page = page_update_function
         self.loop_around_pages = loop_around_pages
 
         self.max_page_index: int = max_page_index
@@ -145,8 +165,11 @@ def __init__(
         for pre_button in prepended_buttons:
             self.add_item(pre_button)
 
-        page_up_style: tuple[discord.ButtonStyle, bool] = self.page_up_style
-        page_down_style: tuple[discord.ButtonStyle, bool] = self.page_down_style
+        page_up_style: tuple[discord.ButtonStyle, bool] \
+            = self.page_up_style
+        page_down_style: tuple[discord.ButtonStyle, bool] \
+            = self.page_down_style
+        
         self.page_down_button = create_simple_button(
             "◀️",
             page_down_style[0],
@@ -165,7 +188,7 @@ def __init__(
         for post_button in appended_buttons:
             self.add_item(post_button)
 
-    async def on_page_down(self, itx: discord.Interaction):
+    async def on_page_down(self, itx: discord.Interaction[Bot]):
         if self.page - 1 < 0:  # below lowest index
             self.page = self.max_page_index  # set to the highest index
         else:
@@ -174,7 +197,7 @@ async def on_page_down(self, itx: discord.Interaction):
         self.update_button_colors()
         await self.update_page(itx, self)
 
-    async def on_page_up(self, itx: discord.Interaction):
+    async def on_page_up(self, itx: discord.Interaction[Bot]):
         if self.page + 1 > self.max_page_index:  # above highest index
             self.page = 0  # set to the lowest index
         else:

From 8cca9c4721104ecb29f8bebaec2d7d0e546be6f6 Mon Sep 17 00:00:00 2001
From: MysticMia <65396931+MysticMia@users.noreply.github.com>
Date: Tue, 20 May 2025 06:11:48 +0200
Subject: [PATCH 11/26] v2 Replace all Interaction type function annotations
 Interaction[Bot]

I had missed a few. This one also does InteractionResponse.
Also replaces #noqa with #type:ignore when typing itx and itx.response.
---
 extensions/changechannel/cogs/changechannel.py            | 4 ++--
 extensions/crashhandling/cogs/crashhandling.py            | 2 +-
 extensions/customvcs/cogs/vctables.py                     | 2 +-
 extensions/customvcs/modals/staffeditor.py                | 3 ++-
 extensions/emojistats/cogs/emojistats.py                  | 2 +-
 extensions/emojistats/cogs/stickerstats.py                | 2 +-
 extensions/nameusage/modals/getnamemodal.py               | 4 +++-
 extensions/reminders/views/sharereminder.py               | 3 ++-
 extensions/settings/cogs/settings.py                      | 8 ++++----
 extensions/tags/tags.py                                   | 2 +-
 extensions/termdictionary/cogs/termdictionary.py          | 2 +-
 .../termdictionary/modals/dictionaryapi_sendpagemodal.py  | 4 +++-
 extensions/testing_commands/cogs/testingcommands.py       | 4 ++--
 extensions/watchlist/modals/watchlistreasonmodal.py       | 6 ++++--
 resources/checks/permission_checks.py                     | 5 +++--
 resources/modals/generics.py                              | 4 +++-
 resources/utils/utils.py                                  | 2 +-
 17 files changed, 35 insertions(+), 24 deletions(-)

diff --git a/extensions/changechannel/cogs/changechannel.py b/extensions/changechannel/cogs/changechannel.py
index 041cf5f..e126fed 100644
--- a/extensions/changechannel/cogs/changechannel.py
+++ b/extensions/changechannel/cogs/changechannel.py
@@ -19,8 +19,8 @@ async def changechannel(
             itx: discord.Interaction[Bot],
             destination: discord.TextChannel
     ):
-        itx.response: discord.InteractionResponse  # noqa
-        itx.followup: discord.Webhook  # noqa
+        itx.response: discord.InteractionResponse[Bot]  # type: ignore
+        itx.followup: discord.Webhook  # type: ignore
 
         if destination.id == itx.channel.id:
             await itx.response.send_message(
diff --git a/extensions/crashhandling/cogs/crashhandling.py b/extensions/crashhandling/cogs/crashhandling.py
index f5db0f8..ecdbdfc 100644
--- a/extensions/crashhandling/cogs/crashhandling.py
+++ b/extensions/crashhandling/cogs/crashhandling.py
@@ -124,7 +124,7 @@ async def _reply(itx: discord.Interaction[Bot], message: str) -> None:
         The function will always try to respond to the interaction
         ephemerally.
     """
-    itx.response: discord.InteractionResponse  # type: ignore
+    itx.response: discord.InteractionResponse[Bot]  # type: ignore
     itx.followup: discord.Webhook  # type: ignore
     try:
         if itx.response.is_done():
diff --git a/extensions/customvcs/cogs/vctables.py b/extensions/customvcs/cogs/vctables.py
index 3768b7a..a0e5792 100644
--- a/extensions/customvcs/cogs/vctables.py
+++ b/extensions/customvcs/cogs/vctables.py
@@ -396,7 +396,7 @@ async def vctable_create(
         name="disband",
         description="reset permissions and convert vctable back to customvc"
     )
-    async def vctable_disband(self, itx: discord.Interaction):
+    async def vctable_disband(self, itx: discord.Interaction[Bot]):
         vctable_prefix: str | None = itx.client.get_guild_attribute(
             itx.guild, AttributeKeys.vctable_prefix)
         if vctable_prefix is None:
diff --git a/extensions/customvcs/modals/staffeditor.py b/extensions/customvcs/modals/staffeditor.py
index fca516b..4dda65c 100644
--- a/extensions/customvcs/modals/staffeditor.py
+++ b/extensions/customvcs/modals/staffeditor.py
@@ -1,5 +1,6 @@
 import discord
 
+from resources.customs import Bot
 from resources.utils.utils import log_to_guild
 
 from extensions.customvcs.channel_rename_tracker import try_store_vc_rename
@@ -37,7 +38,7 @@ def __init__(
         self.add_item(self.name)
         self.add_item(self.limit)
 
-    async def on_submit(self, itx: discord.Interaction):
+    async def on_submit(self, itx: discord.Interaction[Bot]):
         name = str(self.name)
         if name == "":
             name = None
diff --git a/extensions/emojistats/cogs/emojistats.py b/extensions/emojistats/cogs/emojistats.py
index 18042bf..b7221c6 100644
--- a/extensions/emojistats/cogs/emojistats.py
+++ b/extensions/emojistats/cogs/emojistats.py
@@ -324,7 +324,7 @@ async def get_unused_emojis(
     @emojistats.command(name="getemojitop10",
                         description="Get top 10 most used emojis")
     @app_commands.check(not_in_dms_check)
-    async def get_emoji_top_10(self, itx: discord.Interaction):
+    async def get_emoji_top_10(self, itx: discord.Interaction[Bot]):
         collection = itx.client.async_rina_db["emojistats"]
         output = ""
         for source_type in ["messageUsedCount", "reactionUsedCount"]:
diff --git a/extensions/emojistats/cogs/stickerstats.py b/extensions/emojistats/cogs/stickerstats.py
index 9dc438d..33fea8c 100644
--- a/extensions/emojistats/cogs/stickerstats.py
+++ b/extensions/emojistats/cogs/stickerstats.py
@@ -208,7 +208,7 @@ async def get_unused_stickers(
     @stickerstats.command(name="getstickertop10",
                           description="Get top 10 most used stickers")
     @app_commands.check(not_in_dms_check)
-    async def get_sticker_top_10(self, itx: discord.Interaction):
+    async def get_sticker_top_10(self, itx: discord.Interaction[Bot]):
         collection = itx.client.async_rina_db["stickerstats"]
         output = ""
         for source_type in ["messageUsedCount"]:
diff --git a/extensions/nameusage/modals/getnamemodal.py b/extensions/nameusage/modals/getnamemodal.py
index 61b59bc..83e9344 100644
--- a/extensions/nameusage/modals/getnamemodal.py
+++ b/extensions/nameusage/modals/getnamemodal.py
@@ -1,5 +1,7 @@
 import discord
 
+from resources.customs import Bot
+
 
 class GetNameModal(discord.ui.Modal, title="Search page with word"):
     def __init__(self, pages, embed_title, timeout=None):
@@ -19,7 +21,7 @@ def __init__(self, pages, embed_title, timeout=None):
         )
         self.add_item(self.question_text)
 
-    async def on_submit(self, itx: discord.Interaction):
+    async def on_submit(self, itx: discord.Interaction[Bot]):
         self.value = 9  # failed; placeholder
         self.word = self.question_text.value.lower()
         for page_id in range(len(self.pages)):
diff --git a/extensions/reminders/views/sharereminder.py b/extensions/reminders/views/sharereminder.py
index ec88b61..a80f9e6 100644
--- a/extensions/reminders/views/sharereminder.py
+++ b/extensions/reminders/views/sharereminder.py
@@ -1,5 +1,6 @@
 import discord
 
+from resources.customs import Bot
 from resources.views.generics import create_simple_button
 
 
@@ -15,7 +16,7 @@ def __init__(self, timeout=300):
             self.callback)
         )
 
-    async def callback(self, interaction: discord.Interaction):
+    async def callback(self, interaction: discord.Interaction[Bot]):
         self.value = 1
         self.return_interaction = interaction
         self.stop()
diff --git a/extensions/settings/cogs/settings.py b/extensions/settings/cogs/settings.py
index 4342e6f..bf050f9 100644
--- a/extensions/settings/cogs/settings.py
+++ b/extensions/settings/cogs/settings.py
@@ -653,9 +653,9 @@ def __init__(self):
     # )
     # async def database(
     #         self,
-    #         itx: discord.Interaction
+    #         itx: discord.Interaction[Bot]
     # ):
-    #     itx.response: discord.InteractionResponse  # noqa
+    #     itx.response: discord.InteractionResponse[Bot]  # type: ignore
     #     await ServerSettings.migrate(itx.client.async_rina_db)
     #     await itx.response.send_message(
     #         "Successfully migrated databases.", ephemeral=True)
@@ -733,8 +733,8 @@ async def settings(
             mode: str | None = None,
             value: str | None = None
     ):
-        itx.response: discord.InteractionResponse  # noqa
-        itx.followup: discord.Webhook  # noqa
+        itx.response: discord.InteractionResponse[Bot]  # type: ignore
+        itx.followup: discord.Webhook  # type: ignore
         cmd_help = itx.client.get_command_mention_with_args(
             "help", page="900")
         help_str = f"Use {cmd_help} for more info."
diff --git a/extensions/tags/tags.py b/extensions/tags/tags.py
index 1fcd3ff..c109a19 100644
--- a/extensions/tags/tags.py
+++ b/extensions/tags/tags.py
@@ -153,7 +153,7 @@ async def _handle_send_privately(
         :param report_to_staff: Whether the guild wants to log when
          sensitive tags are sent.
         """
-        itx.response: discord.InteractionResponse  # noqa
+        itx.response: discord.InteractionResponse[Bot]  # type: ignore
         if anonymous:
             view = SendPubliclyTagView(
                 self, False, timeout=60)
diff --git a/extensions/termdictionary/cogs/termdictionary.py b/extensions/termdictionary/cogs/termdictionary.py
index 541b993..75581fa 100644
--- a/extensions/termdictionary/cogs/termdictionary.py
+++ b/extensions/termdictionary/cogs/termdictionary.py
@@ -404,7 +404,7 @@ async def dictionary(
         #  - Should also have an integration without custom dictionary
         #    that users can install.
         #  - Make all pageviews into actual PageView views.
-        itx.response: discord.InteractionResponse  # noqa
+        itx.response: discord.InteractionResponse[Bot]  # type: ignore
         # test if mode has been left unset or if mode has been selected: decides whether to move to the
         # online API search or not.
         result_str = ""
diff --git a/extensions/termdictionary/modals/dictionaryapi_sendpagemodal.py b/extensions/termdictionary/modals/dictionaryapi_sendpagemodal.py
index 2a61e80..28ae749 100644
--- a/extensions/termdictionary/modals/dictionaryapi_sendpagemodal.py
+++ b/extensions/termdictionary/modals/dictionaryapi_sendpagemodal.py
@@ -1,5 +1,7 @@
 import discord
 
+from resources.customs import Bot
+
 
 class DictionaryAPISendPageModal(discord.ui.Modal, title="Share single dictionary entry?"):
     def __init__(self, entries, timeout=None):
@@ -15,7 +17,7 @@ def __init__(self, entries, timeout=None):
                                                   )
         self.add_item(self.question_text)
 
-    async def on_submit(self, itx: discord.Interaction):
+    async def on_submit(self, itx: discord.Interaction[Bot]):
         self.value = 9  # failed; placeholder
         try:
             self.id = int(self.question_text.value)
diff --git a/extensions/testing_commands/cogs/testingcommands.py b/extensions/testing_commands/cogs/testingcommands.py
index 40c18cc..db0b504 100644
--- a/extensions/testing_commands/cogs/testingcommands.py
+++ b/extensions/testing_commands/cogs/testingcommands.py
@@ -185,7 +185,7 @@ async def update_test_page(
             e.set_footer(text=f"{page_index + 1}/{page_count}")
             pages.append(e)
 
-        async def go_to_page_button_callback(itx1: discord.Interaction):
+        async def go_to_page_button_callback(itx1: discord.Interaction[Bot]):
             # view: PageView = view
             await itx1.response.send_message(
                 f"This embed has {view.max_page_index + 1} pages!")
@@ -245,7 +245,7 @@ async def send_vc_log_test(
             from_channel: discord.VoiceChannel | discord.StageChannel = None,
             to_channel: discord.VoiceChannel | discord.StageChannel = None,
     ):
-        itx.response: discord.InteractionResponse  # type: ignore
+        itx.response: discord.InteractionResponse[Bot]  # type: ignore
         # jeez the log is inconsistent lol
         user = itx.user
 
diff --git a/extensions/watchlist/modals/watchlistreasonmodal.py b/extensions/watchlist/modals/watchlistreasonmodal.py
index 6b79df7..73748fc 100644
--- a/extensions/watchlist/modals/watchlistreasonmodal.py
+++ b/extensions/watchlist/modals/watchlistreasonmodal.py
@@ -1,6 +1,8 @@
 import discord
 import typing
 
+from resources.customs import Bot
+
 
 class WatchlistReasonModal(discord.ui.Modal):
     """
@@ -23,7 +25,7 @@ class WatchlistReasonModal(discord.ui.Modal):
     def __init__(
             self,
             add_to_watchlist_func: typing.Callable[
-                [discord.Interaction, discord.User | discord.Member,
+                [discord.Interaction[Bot], discord.User | discord.Member,
                  str, str | None, typing.Optional[str]],
                 typing.Coroutine[typing.Any, typing.Any, None]],
             title: str,
@@ -47,7 +49,7 @@ def __init__(
         )
         self.add_item(self.reason_text)
 
-    async def on_submit(self, itx: discord.Interaction):
+    async def on_submit(self, itx: discord.Interaction[Bot]):
         self.value = 1
         await self.add_to_watchlist_func(
             itx,
diff --git a/resources/checks/permission_checks.py b/resources/checks/permission_checks.py
index f2b023d..dc08556 100644
--- a/resources/checks/permission_checks.py
+++ b/resources/checks/permission_checks.py
@@ -1,6 +1,7 @@
 import discord
 
 from resources.checks.permissions import is_staff, is_admin
+from resources.customs import Bot
 from .errors import (
     CommandDoesNotSupportDMsCheckFailure,
     InsufficientPermissionsCheckFailure
@@ -8,7 +9,7 @@
 from .command_checks import is_in_dms
 
 
-def is_staff_check(itx: discord.Interaction):
+def is_staff_check(itx: discord.Interaction[Bot]):
     """
     A check to check if the command executor has a staff role.
     :param itx: The interaction to check.
@@ -24,7 +25,7 @@ def is_staff_check(itx: discord.Interaction):
     raise InsufficientPermissionsCheckFailure("User is not staff")
 
 
-def is_admin_check(itx: discord.Interaction):
+def is_admin_check(itx: discord.Interaction[Bot]):
     """
     A check to check if the command executor has an admin role.
     :param itx: The interaction to check.
diff --git a/resources/modals/generics.py b/resources/modals/generics.py
index 8e71be9..20c04d8 100644
--- a/resources/modals/generics.py
+++ b/resources/modals/generics.py
@@ -1,5 +1,7 @@
 import discord
 
+from resources.customs import Bot
+
 
 class SingleLineModal(discord.ui.Modal):
     def __init__(self, title: str, label: str, placeholder: str = ""):
@@ -12,6 +14,6 @@ def __init__(self, title: str, label: str, placeholder: str = ""):
         self.add_item(self.question_text)
         self.itx = None
 
-    async def on_submit(self, itx: discord.Interaction) -> None:
+    async def on_submit(self, itx: discord.Interaction[Bot]) -> None:
         self.itx = itx
         self.stop()
diff --git a/resources/utils/utils.py b/resources/utils/utils.py
index 4046834..f8ef44f 100644
--- a/resources/utils/utils.py
+++ b/resources/utils/utils.py
@@ -154,7 +154,7 @@ def debug(
 
 
 def get_mod_ticket_channel(
-        client: Bot, guild_id: int | discord.Guild | discord.Interaction
+        client: Bot, guild_id: int | discord.Guild | discord.Interaction[Bot]
 ) -> discord.abc.Messageable | None:
     """
     Fetch the #contact-staff ticket channel for a specific guild.

From e91b8a21f9d8f0b4b98fc40fa3a51c1c77894739 Mon Sep 17 00:00:00 2001
From: MysticMia <65396931+MysticMia@users.noreply.github.com>
Date: Tue, 20 May 2025 06:19:08 +0200
Subject: [PATCH 12/26] Make API urls use 'params' instead of manually putting
 them in the url.

---
 extensions/addons/cogs/otheraddons.py            | 13 +++++++++----
 extensions/addons/cogs/searchaddons.py           |  9 +++++++--
 extensions/termdictionary/cogs/termdictionary.py |  6 ++++--
 3 files changed, 20 insertions(+), 8 deletions(-)

diff --git a/extensions/addons/cogs/otheraddons.py b/extensions/addons/cogs/otheraddons.py
index 41f56be..1a0ce66 100644
--- a/extensions/addons/cogs/otheraddons.py
+++ b/extensions/addons/cogs/otheraddons.py
@@ -274,11 +274,16 @@ async def convert_unit(
             )
             return
         if mode == "currency":
-            # more info: https://docs.openexchangerates.org/reference/latest-json  # noqa
-            api_key = itx.client.api_tokens['Open Exchange Rates']
-            # todo: use requests `params`
+            # more info:
+            #  https://docs.openexchangerates.org/reference/latest-json
+            params = {
+                "appid": itx.client.api_tokens['Open Exchange Rates'],
+                "show_alternative": "true",
+            }
             response_api = requests.get(
-                f"https://openexchangerates.org/api/latest.json?app_id={api_key}&show_alternative=true").text
+                f"https://openexchangerates.org/api/latest.json",
+                params=params
+            ).text
             data = json.loads(response_api)
             if data.get("error", 0):
                 await itx.response.send_message(
diff --git a/extensions/addons/cogs/searchaddons.py b/extensions/addons/cogs/searchaddons.py
index 728b75b..d49e9cc 100644
--- a/extensions/addons/cogs/searchaddons.py
+++ b/extensions/addons/cogs/searchaddons.py
@@ -49,7 +49,7 @@ async def equaldex(self, itx: discord.Interaction[Bot], country_id: str):
         querystring = {
             "regionid": country_id,
             "apiKey": equaldex_key,
-            # "formatted": True,
+            # "formatted": "true",
         }
         response = requests.get(
             "https://www.equaldex.com/api/region",
@@ -185,9 +185,14 @@ async def math(self, itx: discord.Interaction[Bot], query: str):
         # pluses are interpreted as a space (`%20`) in urls. In LaTeX,
         #  that can mean multiply.
         api_key = itx.client.api_tokens['Wolfram Alpha']
+        params = {
+            "appid": api_key,
+            "input": query,
+            "output": "json",
+        }
         try:
             data = requests.get(
-                f"https://api.wolframalpha.com/v2/query?appid={api_key}&input={query}&output=json").json()
+                f"https://api.wolframalpha.com/v2/query", params=params).json()
         except requests.exceptions.JSONDecodeError:
             await itx.followup.send(
                 "Your input gave a malformed result! Perhaps it took "
diff --git a/extensions/termdictionary/cogs/termdictionary.py b/extensions/termdictionary/cogs/termdictionary.py
index 75581fa..a8d618e 100644
--- a/extensions/termdictionary/cogs/termdictionary.py
+++ b/extensions/termdictionary/cogs/termdictionary.py
@@ -69,7 +69,8 @@ async def dictionary_autocomplete(itx: discord.Interaction[Bot], current: str):
                     terms.append(result["word"].capitalize())
     # same for Urban Dictionary, searching only if there are no results for the others
     if len(terms) < 1:
-        response_api = requests.get(f'https://api.urbandictionary.com/v0/define?term={current}').text
+        params = {"term": current}
+        response_api = requests.get(f'https://api.urbandictionary.com/v0/define', params=params).text
         data = json.loads(response_api)['list']
         for result in data:
             if result["word"].capitalize() + " ([from UD])" not in terms:
@@ -458,7 +459,8 @@ async def dictionary(
         if source == 7 or source == 8:
             if not itx.response.is_done():
                 await itx.response.defer(ephemeral=True)
-            response_api = requests.get(f'https://api.urbandictionary.com/v0/define?term={term.lower()}').text
+            params = {"term": term.lower()}
+            response_api = requests.get(f'https://api.urbandictionary.com/v0/define', params=params).text
             # who decided to put the output into a dictionary with a list named 'list'? {"list":[{},{},{}]}
             data = json.loads(response_api)['list']
             if len(data) == 0:

From a8057e8565bfa22d8561d1aed2937d1e6893944a Mon Sep 17 00:00:00 2001
From: MysticMia <65396931+MysticMia@users.noreply.github.com>
Date: Thu, 22 May 2025 15:58:17 +0200
Subject: [PATCH 13/26] Ensure wolfram queries can actually use special
 characters

---
 extensions/addons/cogs/otheraddons.py  | 2 +-
 extensions/addons/cogs/searchaddons.py | 9 ++++++---
 2 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/extensions/addons/cogs/otheraddons.py b/extensions/addons/cogs/otheraddons.py
index 1a0ce66..3be4ccc 100644
--- a/extensions/addons/cogs/otheraddons.py
+++ b/extensions/addons/cogs/otheraddons.py
@@ -281,7 +281,7 @@ async def convert_unit(
                 "show_alternative": "true",
             }
             response_api = requests.get(
-                f"https://openexchangerates.org/api/latest.json",
+                "https://openexchangerates.org/api/latest.json",
                 params=params
             ).text
             data = json.loads(response_api)
diff --git a/extensions/addons/cogs/searchaddons.py b/extensions/addons/cogs/searchaddons.py
index d49e9cc..43cc6f3 100644
--- a/extensions/addons/cogs/searchaddons.py
+++ b/extensions/addons/cogs/searchaddons.py
@@ -1,4 +1,6 @@
 import json  # to read API json responses
+import urllib.parse
+
 import requests  # to read api calls
 
 import discord
@@ -181,18 +183,19 @@ async def math(self, itx: discord.Interaction[Bot], query: str):
                 ephemeral=True
             )
             return
-        query = query.replace("+", " plus ")
         # pluses are interpreted as a space (`%20`) in urls. In LaTeX,
         #  that can mean multiply.
         api_key = itx.client.api_tokens['Wolfram Alpha']
         params = {
             "appid": api_key,
-            "input": query,
+            "input": urllib.parse.quote(query),  # slashes are safe
             "output": "json",
         }
         try:
             data = requests.get(
-                f"https://api.wolframalpha.com/v2/query", params=params).json()
+                "https://api.wolframalpha.com/v2/query",
+                params=params
+            ).json()
         except requests.exceptions.JSONDecodeError:
             await itx.followup.send(
                 "Your input gave a malformed result! Perhaps it took "

From c49deb9fec4eefc21ad75b1621446f4fefcb040b Mon Sep 17 00:00:00 2001
From: MysticMia <65396931+MysticMia@users.noreply.github.com>
Date: Thu, 22 May 2025 16:00:07 +0200
Subject: [PATCH 14/26] Fix broken DMsCheckFailure error and change
 itx.user.nick to getattr.

---
 extensions/customvcs/cogs/customvcs.py     |  6 ++++--
 extensions/customvcs/modals/staffeditor.py |  6 ++++--
 extensions/qotw/cogs/devrequest.py         |  3 ++-
 extensions/qotw/cogs/qotw.py               |  3 ++-
 resources/checks/permission_checks.py      | 12 ++++++++----
 5 files changed, 20 insertions(+), 10 deletions(-)

diff --git a/extensions/customvcs/cogs/customvcs.py b/extensions/customvcs/cogs/customvcs.py
index 53b155f..4fd4034 100644
--- a/extensions/customvcs/cogs/customvcs.py
+++ b/extensions/customvcs/cogs/customvcs.py
@@ -381,12 +381,13 @@ async def edit_custom_vc(
                            f"to \"{name}\"{limit_info}",
                     name=name
                 )
+                username = getattr(itx.user, 'nick', None) or itx.user.name
                 await log_to_guild(
                     itx.client,
                     itx.guild,
                     f"Voice channel ({channel.id}) renamed from "
                     f"\"{old_name}\" to \"{name}\" (by "
-                    f"{itx.user.nick or itx.user.name}, {itx.user.id})"
+                    f"{username}, {itx.user.id})"
                 )
                 await itx.response.send_message(
                     warning + f"Voice channel successfully renamed "
@@ -423,10 +424,11 @@ async def edit_custom_vc(
                     user_limit=limit,
                     name=name
                 )
+                username = getattr(itx.user, 'nick', None) or itx.user.name
                 await log_to_guild(
                     itx.client,
                     itx.guild,
-                    f"{itx.user.nick or itx.user.name} ({itx.user.id}) "
+                    f"{username} ({itx.user.id}) "
                     f"changed VC ({channel.id}) name \"{old_name}\" to "
                     f"\"{name}\" and user limit from \"{old_limit}\" to "
                     f"\"{limit}\"{limit_info}"
diff --git a/extensions/customvcs/modals/staffeditor.py b/extensions/customvcs/modals/staffeditor.py
index 4dda65c..45f67ed 100644
--- a/extensions/customvcs/modals/staffeditor.py
+++ b/extensions/customvcs/modals/staffeditor.py
@@ -135,12 +135,13 @@ async def on_submit(self, itx: discord.Interaction[Bot]):
                            f"\"{old_limit}\" to \"{limit}\"",
                     user_limit=limit
                 )
+                username = getattr(itx.user, 'nick', None) or itx.user.name
                 await log_to_guild(
                     itx.client,
                     itx.guild,
                     f"Staff: Voice channel \"{old_name}\" ({channel.id}) "
                     f"edited the user limit from \"{old_limit}\" to "
-                    f"\"{limit}\" (by {itx.user.nick or itx.user.name}, "
+                    f"\"{limit}\" (by {username}, "
                     f"{itx.user.id}){limit_info}"
                 )
                 await itx.response.send_message(
@@ -194,11 +195,12 @@ async def on_submit(self, itx: discord.Interaction[Bot]):
                 )
         except discord.errors.HTTPException as ex:
             ex_message = repr(ex).split("(", 1)[1][1:-2]
+            username = getattr(itx.user, 'nick', None) or itx.user.name
             await log_to_guild(
                 itx.client,
                 itx.guild,
                 f"Staff: Warning! >> {ex_message} <<"
-                f" {itx.user.nick or itx.user.name} ({itx.user.id}) tried to "
+                f" {username} ({itx.user.id}) tried to "
                 f"change {old_name} ({channel.id}) to {name}, but wasn't "
                 f"allowed to by discord, probably because it's in a banned "
                 f"word list for discord's discovery <@262913789375021056>"
diff --git a/extensions/qotw/cogs/devrequest.py b/extensions/qotw/cogs/devrequest.py
index 48692a4..c2ac5e3 100644
--- a/extensions/qotw/cogs/devrequest.py
+++ b/extensions/qotw/cogs/devrequest.py
@@ -119,8 +119,9 @@ async def developer_request(
                         f"({copyable_version.jump_url})",
             timestamp=datetime.now()
         )
+        username = getattr(itx.user, 'nick', None) or itx.user.name
         embed.set_author(
-            name=f"{itx.user.nick or itx.user.name}",
+            name=f"{username}",
             url=f"https://original.poster/{itx.user.id}/",
             icon_url=itx.user.display_avatar.url
         )
diff --git a/extensions/qotw/cogs/qotw.py b/extensions/qotw/cogs/qotw.py
index b55eca3..a1f7ec0 100644
--- a/extensions/qotw/cogs/qotw.py
+++ b/extensions/qotw/cogs/qotw.py
@@ -85,8 +85,9 @@ async def qotw(self, itx: discord.Interaction[Bot], question: str):
                         f"({copyable_version.jump_url})",
             timestamp=datetime.now()
         )
+        username = getattr(itx.user, 'nick', None) or itx.user.name
         embed.set_author(
-            name=f"{itx.user.nick or itx.user.name}",
+            name=f"{username}",
             url=f"https://original.poster/{itx.user.id}/",
             icon_url=itx.user.display_avatar.url
         )
diff --git a/resources/checks/permission_checks.py b/resources/checks/permission_checks.py
index dc08556..31c8cbe 100644
--- a/resources/checks/permission_checks.py
+++ b/resources/checks/permission_checks.py
@@ -12,14 +12,17 @@
 def is_staff_check(itx: discord.Interaction[Bot]):
     """
     A check to check if the command executor has a staff role.
+    
     :param itx: The interaction to check.
     :return: ``True`` if the executor has a staff role, else
      an exception.
     :raise InsufficientPermissionsCheckFailure: If the user does not
      have a staff role.
+    :raise CommandDoesNotSupportDMsCheckFailure: If the command was
+     executed in DMs
     """
     if is_in_dms(itx.guild):
-        return CommandDoesNotSupportDMsCheckFailure()
+        raise CommandDoesNotSupportDMsCheckFailure()
     if is_staff(itx, itx.user):
         return True
     raise InsufficientPermissionsCheckFailure("User is not staff")
@@ -28,14 +31,15 @@ def is_staff_check(itx: discord.Interaction[Bot]):
 def is_admin_check(itx: discord.Interaction[Bot]):
     """
     A check to check if the command executor has an admin role.
+    
     :param itx: The interaction to check.
     :return: ``True`` if the executor has an admin role, else an
      exception.
     :raise InsufficientPermissionsCheckFailure: If the user does not
      have an admin role.
+    :raise CommandDoesNotSupportDMsCheckFailure: If the command was
+     executed in DMs
     """
-    if is_in_dms(itx.guild):
-        return CommandDoesNotSupportDMsCheckFailure()
-    if is_admin(itx, itx.user):
+    if is_admin(itx, itx.user):  # can raise DMsCheckFailure
         return True
     raise InsufficientPermissionsCheckFailure("User is not admin")

From 88cc7941e439c08803826bff32c749a4549178fc Mon Sep 17 00:00:00 2001
From: MysticMia <65396931+MysticMia@users.noreply.github.com>
Date: Thu, 22 May 2025 16:09:56 +0200
Subject: [PATCH 15/26] Remove page_update_function from PageView and improve
 documentation

Also invert some if-statements to make it more readable (i think?)
---
 resources/checks/permission_checks.py |  4 +-
 resources/customs/bot.py              | 63 +++++++++++-------
 resources/views/generics.py           | 95 +++++++++++++++++++++++----
 3 files changed, 126 insertions(+), 36 deletions(-)

diff --git a/resources/checks/permission_checks.py b/resources/checks/permission_checks.py
index 31c8cbe..e66b5e4 100644
--- a/resources/checks/permission_checks.py
+++ b/resources/checks/permission_checks.py
@@ -12,7 +12,7 @@
 def is_staff_check(itx: discord.Interaction[Bot]):
     """
     A check to check if the command executor has a staff role.
-    
+
     :param itx: The interaction to check.
     :return: ``True`` if the executor has a staff role, else
      an exception.
@@ -31,7 +31,7 @@ def is_staff_check(itx: discord.Interaction[Bot]):
 def is_admin_check(itx: discord.Interaction[Bot]):
     """
     A check to check if the command executor has an admin role.
-    
+
     :param itx: The interaction to check.
     :return: ``True`` if the executor has an admin role, else an
      exception.
diff --git a/resources/customs/bot.py b/resources/customs/bot.py
index 5368b62..189b7c3 100644
--- a/resources/customs/bot.py
+++ b/resources/customs/bot.py
@@ -1,13 +1,14 @@
 from __future__ import annotations
+
+import discord  # for main discord bot functionality
+import discord.ext.commands as commands
+import discord.app_commands as app_commands
+
 from apscheduler.schedulers.asyncio import AsyncIOScheduler
 # ^ for scheduling Reminders
 from datetime import datetime
 # ^ for startup and crash logging, and Reminders
 from typing import TYPE_CHECKING, TypeVar
-
-import discord  # for main discord bot functionality
-import discord.ext.commands as commands
-
 import motor.core as motorcore  # for typing
 from pymongo.database import Database as PyMongoDatabase
 # ^ for MongoDB database typing
@@ -66,20 +67,26 @@ def get_command_mention(self, command_string: str) -> str:
         # 
         #              /\- is posed as 'subcommand', to make searching easier
         for command in self.commandList:
-            if command.name == command_name:
-                if subcommand is None:
-                    return command.mention
-                for subgroup in command.options:
-                    if subgroup.name == subcommand:
-                        if subcommand_group is None:
-                            return subgroup.mention
-                        # now it technically searches subcommand in
-                        #  subcmdgroup.options but to remove additional
-                        #  renaming of variables, it stays as is.
-                        # subcmdgroup = subgroup  # hm
-                        for subcmdgroup in subgroup.options:
-                            if subcmdgroup.name == subcommand_group:
-                                return subcmdgroup.mention
+            if command.name != command_name:
+                continue
+            if subcommand is None:
+                return command.mention
+
+            for subgroup in command.options:
+                if subgroup.name != subcommand:
+                    continue
+                if subcommand_group is None:
+                    assert type(subgroup) is app_commands.AppCommandGroup
+                    return subgroup.mention
+
+                # now it technically searches subcommand in
+                #  subcmdgroup.options but to remove additional
+                #  renaming of variables, it stays as is.
+                # subcmdgroup = subgroup  # hm
+                for subcmdgroup in subgroup.options:
+                    if subcmdgroup.name == subcommand_group:
+                        return subcmdgroup.mention
+        # no command found.
         return "/" + command_string
 
     def get_command_mention_with_args(
@@ -87,6 +94,14 @@ def get_command_mention_with_args(
             command_string: str,
             **kwargs: str
     ) -> str:
+        """
+        Turn a string into a command mention and format passed arguments.
+        :param command_string: Command you want to convert into a
+         mention (without slash in front of it).
+        :param kwargs: Additional arguments and their values to pass to
+         the command.
+        :return:
+        """
         command_mention = self.get_command_mention(command_string)
         for key, value in kwargs.items():
             command_mention += f" `{key}:{value}`"
@@ -95,14 +110,14 @@ def get_command_mention_with_args(
 
     def get_guild_attribute(
             self,
-            guild_id: discord.Guild | int,
+            guild: discord.Guild | int,
             *args: str,
             default: T = None
     ) -> object | list[object] | T:
         """
         Get ServerSettings attributes for the given guild.
 
-        :param guild_id: The guild or guild id of the server you want to
+        :param guild: The guild or guild id of the server you want to
          get attributes for.
         :param args: The attribute(s) to get the values of. Must be keys
          of ServerAttributes.
@@ -110,8 +125,12 @@ def get_guild_attribute(
         :return: A single or list of values matching the requested
          attributes, with *default* if attributes are not found.
         """
-        if type(guild_id) is discord.Guild:
-            guild_id: int = guild_id.id
+        if type(guild) is discord.Guild:
+            guild_id: int = guild.id
+        else:
+            assert type(guild) is int  # why doesn't the interpreter see this?
+            guild_id: int = guild
+
         if len(args) == 0:
             raise ValueError("You must provide at least one argument!")
 
diff --git a/resources/views/generics.py b/resources/views/generics.py
index 673ad10..622a475 100644
--- a/resources/views/generics.py
+++ b/resources/views/generics.py
@@ -4,7 +4,6 @@
 
 import discord
 import typing
-import types
 
 from resources.customs import Bot
 
@@ -39,6 +38,15 @@ def create_simple_button(
 
 
 class GenericTwoButtonView(discord.ui.View):
+    """
+    A generic view with two buttons. They are typically set as an
+    accept and cancel button. When the user clicks a button, the
+    state is stored in :py:attr:`self.value`.
+
+    :ivar value: The state of the button. ``None`` if the view timed
+     out, or a boolean whether :py:meth:`on_button_true` (``True``) or
+     :py:meth:`on_button_false` (``False``) was triggered.
+    """
     def __init__(
             self,
             button_true: tuple[str, discord.ButtonStyle] = (
@@ -78,6 +86,74 @@ async def on_button_false(self, _: discord.Interaction[Bot]):
 
 
 class PageView(discord.ui.View):
+    """
+    A generic page view to nagivate through given pages.
+
+    The view can be subclassed to add new buttons. To do so, simply
+    add new button functions, then initialize this superclass. The
+    buttons will be placed on the left of the navigation buttons.
+
+    When adding custom buttons, the order is important. The one placed
+    first will get index 0 in :py:attr:`_children`. Subsequent ones
+    will get the next index, etc. Initializing the superclass will
+    place the page_up and page_down buttons in the following two
+    indexes.
+
+    If you wish to place buttons on the right side, you have to move
+    them manually AFTER initializing this superclass::
+
+        # Example:
+        # self._children contains 4 buttons:
+        # - A "Jump to index" button
+        # - A "Jump to page" button
+        # - The automatically-added "page_down" button
+        # - The automatically-added "page_up" button
+
+        # To move the "Jump to page" button after the navigation
+        #  buttons: Remove the second button: "Jump to page"
+        button_to_move = self._children.pop(1)
+        # Add the button to the end.
+        self._children.append(button_to_move)
+
+        # Outcome:
+        # - "Jump to index" button
+        # - "page_down" and "page_up" button
+        # - "Jump to page" button
+
+    While not recommended, the :py:meth:`on_page_down` and
+    :py:meth:`on_page_up` methods can be overridden to change the
+    page navigation functionality. By default, they control the
+    :py:attr:`page` index integer. But it's recommended
+    to use the :py:meth:`update_page` method instead.
+
+    When the user navigates the pages, the function
+    :py:meth:`update_page` is called. This function handles the page
+    updating and must be implemented by subclasses. For basic
+    implementation, subclasses can pass a list of pages, and use
+    :py:attr:`page` to select the correct page to display.
+
+    By default, when the user reaches the first or last page, the button
+    style changes to represent this boundary. When navigating beyond
+    the bounds of the pages, the selected page wraps around: If the user
+    goes down from the first page, it wraps around and jumps to the last
+    page. The same when going up a page from the last page. This can
+    be disabled with :py:attr:`loop_around_pages`.
+
+    The style of the buttons are handled by the properties
+    :py:meth:`page_down_style` and :py:meth:`page_up_style`.
+
+    :ivar page: The currently selected page index by the user. Typically
+     controlled by :py:meth:`on_page_down` and :py:meth:`on_page_up`.
+    :ivar loop_around_pages: Whether going to a page out of bounds
+     should loop the page index (<0 should go to page max_page_index,
+     and >max_page_index should go to page index 0). If this is
+     ``False``, the user will be told they have reached the first/last
+     page instead.
+    :ivar max_page_index: The maximum page index, after which to deny
+     any further page increments.
+    :ivar timeout: The duration (in seconds) to wait for inactivity
+     before the view times out.
+    """
     @property
     def page_down_style(self) -> tuple[discord.ButtonStyle, bool]:
         """
@@ -97,7 +173,7 @@ def page_down_style(self) -> tuple[discord.ButtonStyle, bool]:
             button_style = discord.ButtonStyle.gray
         else:
             button_style = discord.ButtonStyle.blurple
-        
+
         return button_style, disabled
 
     @property
@@ -125,8 +201,10 @@ def page_up_style(self) -> tuple[discord.ButtonStyle, bool]:
     @abstractmethod
     async def update_page(self, itx: discord.Interaction[Bot], view: PageView):
         """
-        Update the page message. This typically involves calculating the
-        message content for the message and updating the original message.
+        Update the page message.
+
+        This typically involves calculating the message content for the
+        message and updating the original message.
 
         :param itx: The interaction gained from the button or modal
          interaction by the user.
@@ -138,11 +216,6 @@ def __init__(
             self,
             starting_page: int,
             max_page_index: int,
-            page_update_function: typing.Callable[
-                                      [discord.Interaction[Bot], PageView],
-                                      types.CoroutineType[
-                                          typing.Any, typing.Any, None]
-                                  ] | None = None,
             prepended_buttons: list[discord.ui.Button] | None = None,
             appended_buttons: list[discord.ui.Button] | None = None,
             loop_around_pages: bool = True,
@@ -156,8 +229,6 @@ def __init__(
         if appended_buttons is None:
             appended_buttons = []
         self.page: int = starting_page
-        if page_update_function is not None:
-            self.update_page = page_update_function
         self.loop_around_pages = loop_around_pages
 
         self.max_page_index: int = max_page_index
@@ -169,7 +240,7 @@ def __init__(
             = self.page_up_style
         page_down_style: tuple[discord.ButtonStyle, bool] \
             = self.page_down_style
-        
+
         self.page_down_button = create_simple_button(
             "◀️",
             page_down_style[0],

From a70fa5018296006cac934d4bdababf409b6ad6e8 Mon Sep 17 00:00:00 2001
From: MysticMia <65396931+MysticMia@users.noreply.github.com>
Date: Thu, 22 May 2025 16:10:34 +0200
Subject: [PATCH 16/26] Fix more discord.Interaction to
 discord.Interaction[Bot]

---
 extensions/crashhandling/cogs/crashhandling.py   | 2 +-
 extensions/customvcs/cogs/vctables.py            | 2 +-
 extensions/reminders/cogs/reminders.py           | 2 +-
 extensions/reminders/views/copyreminder.py       | 2 +-
 extensions/reminders/views/sharereminder.py      | 2 +-
 extensions/reminders/views/timeofdayselection.py | 2 +-
 6 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/extensions/crashhandling/cogs/crashhandling.py b/extensions/crashhandling/cogs/crashhandling.py
index ecdbdfc..7a6be01 100644
--- a/extensions/crashhandling/cogs/crashhandling.py
+++ b/extensions/crashhandling/cogs/crashhandling.py
@@ -28,7 +28,7 @@ async def _send_crash_message(
         traceback_text: str,
         error_source: str,
         color: discord.Colour,
-        itx: discord.Interaction | None = None
+        itx: discord.Interaction[Bot] | None = None
 ) -> None:
     """
     Sends crash message to Rina's main logging channel
diff --git a/extensions/customvcs/cogs/vctables.py b/extensions/customvcs/cogs/vctables.py
index a0e5792..1a9e6a5 100644
--- a/extensions/customvcs/cogs/vctables.py
+++ b/extensions/customvcs/cogs/vctables.py
@@ -160,7 +160,7 @@ async def _get_current_voice_channel(
 
 
 async def _get_channel_if_owner(
-        itx: discord.Interaction | discord.Member,
+        itx: discord.Interaction[Bot] | discord.Member,
         action: str,
         from_event: bool = False
 ):
diff --git a/extensions/reminders/cogs/reminders.py b/extensions/reminders/cogs/reminders.py
index f5c1ca8..038abae 100644
--- a/extensions/reminders/cogs/reminders.py
+++ b/extensions/reminders/cogs/reminders.py
@@ -174,7 +174,7 @@ async def reminders(
                                    f"")
                         index += 1
                     cmd_reminders = itx.client.get_command_mention_with_args(
-                        "reminder reminders", item="")
+                        "reminder reminders", item=" ")
                     out_msg = (
                         (f"You have {len(db_data['reminders'])} reminders "
                          f"(use {cmd_reminders} to get more info about a "
diff --git a/extensions/reminders/views/copyreminder.py b/extensions/reminders/views/copyreminder.py
index 46c2599..4ea8b24 100644
--- a/extensions/reminders/views/copyreminder.py
+++ b/extensions/reminders/views/copyreminder.py
@@ -22,7 +22,7 @@ def __init__(
     ):
         super().__init__()
         self.timeout = timeout
-        self.return_interaction: discord.Interaction | None = None
+        self.return_interaction: discord.Interaction[Bot] | None = None
         self.reminder = reminder
 
         # required to prevent circular imports ;-;
diff --git a/extensions/reminders/views/sharereminder.py b/extensions/reminders/views/sharereminder.py
index a80f9e6..bcf15ef 100644
--- a/extensions/reminders/views/sharereminder.py
+++ b/extensions/reminders/views/sharereminder.py
@@ -9,7 +9,7 @@ def __init__(self, timeout=300):
         super().__init__()
         self.value = 0
         self.timeout = timeout
-        self.return_interaction: discord.Interaction | None = None
+        self.return_interaction: discord.Interaction[Bot] | None = None
         self.add_item(create_simple_button(
             "Share reminder in chat",
             discord.ButtonStyle.gray,
diff --git a/extensions/reminders/views/timeofdayselection.py b/extensions/reminders/views/timeofdayselection.py
index 4888fd0..2618524 100644
--- a/extensions/reminders/views/timeofdayselection.py
+++ b/extensions/reminders/views/timeofdayselection.py
@@ -8,7 +8,7 @@ class TimeOfDaySelection(discord.ui.View):
     def __init__(self, options: list[str], timeout=180):
         super().__init__()
         self.value: str | None = None
-        self.return_interaction: discord.Interaction | None = None
+        self.return_interaction: discord.Interaction[Bot] | None = None
         self.timeout = timeout
 
         for option in options:

From 6e837157fb8f90880d478a0e31a31994335eb71e Mon Sep 17 00:00:00 2001
From: MysticMia <65396931+MysticMia@users.noreply.github.com>
Date: Thu, 22 May 2025 16:10:50 +0200
Subject: [PATCH 17/26] Revamp dictionary command

---
 extensions/termdictionary/__init__.py         |   0
 .../termdictionary/cogs/termdictionary.py     | 689 ++++++------------
 .../dictionaries/CustomDictionary.py          | 124 ++++
 .../dictionaries/DictionaryApiDictionary.py   | 144 ++++
 .../dictionaries/DictionaryBase.py            |  76 ++
 .../dictionaries/PronounsPageDictionary.py    | 268 +++++++
 .../dictionaries/UrbanDictionary.py           | 147 ++++
 .../termdictionary/dictionaries/__init__.py   |  12 +
 .../objects/CustomDictionaryEntry.py}         |   2 +-
 .../objects/DictionaryApiEntry.py             |  77 ++
 .../dictionaries/objects/PronounsPageEntry.py |  37 +
 .../objects/UrbanDictionaryEntry.py           |  14 +
 .../dictionaries/objects/__init__.py          |  17 +
 .../modals/dictionaryapi_sendpagemodal.py     |  34 +-
 extensions/termdictionary/utils.py            |  16 +
 extensions/termdictionary/views/__init__.py   |   6 +-
 .../termdictionary/views/dictionaryapi.py     |  88 +++
 .../views/dictionaryapi_pageview.py           |  66 --
 .../termdictionary/views/urbandictionary.py   |  25 +
 .../views/urbandictionary_pageview.py         |  36 -
 20 files changed, 1276 insertions(+), 602 deletions(-)
 create mode 100644 extensions/termdictionary/__init__.py
 create mode 100644 extensions/termdictionary/dictionaries/CustomDictionary.py
 create mode 100644 extensions/termdictionary/dictionaries/DictionaryApiDictionary.py
 create mode 100644 extensions/termdictionary/dictionaries/DictionaryBase.py
 create mode 100644 extensions/termdictionary/dictionaries/PronounsPageDictionary.py
 create mode 100644 extensions/termdictionary/dictionaries/UrbanDictionary.py
 create mode 100644 extensions/termdictionary/dictionaries/__init__.py
 rename extensions/termdictionary/{dictionary_data.py => dictionaries/objects/CustomDictionaryEntry.py} (68%)
 create mode 100644 extensions/termdictionary/dictionaries/objects/DictionaryApiEntry.py
 create mode 100644 extensions/termdictionary/dictionaries/objects/PronounsPageEntry.py
 create mode 100644 extensions/termdictionary/dictionaries/objects/UrbanDictionaryEntry.py
 create mode 100644 extensions/termdictionary/dictionaries/objects/__init__.py
 create mode 100644 extensions/termdictionary/utils.py
 create mode 100644 extensions/termdictionary/views/dictionaryapi.py
 delete mode 100644 extensions/termdictionary/views/dictionaryapi_pageview.py
 create mode 100644 extensions/termdictionary/views/urbandictionary.py
 delete mode 100644 extensions/termdictionary/views/urbandictionary_pageview.py

diff --git a/extensions/termdictionary/__init__.py b/extensions/termdictionary/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/extensions/termdictionary/cogs/termdictionary.py b/extensions/termdictionary/cogs/termdictionary.py
index a8d618e..7b4fb3f 100644
--- a/extensions/termdictionary/cogs/termdictionary.py
+++ b/extensions/termdictionary/cogs/termdictionary.py
@@ -1,355 +1,63 @@
-import json
-import re  # to parse and remove https:/pronouns.page/ in-text page linking
-from datetime import datetime
-import requests  # for API calls to dictionary apis
+import asyncio
 
 import discord
 import discord.app_commands as app_commands
 import discord.ext.commands as commands
 
-from extensions.termdictionary.dictionary_data import DictionaryData
+from extensions.termdictionary.dictionaries import (
+    CustomDictionary,
+    DictionaryApiDictionary,
+    DictionaryBase,
+    PronounsPageDictionary,
+)
+from extensions.termdictionary.dictionaries.UrbanDictionary import \
+    UrbanDictionary
 from extensions.termdictionary.dictionary_sources import DictionarySources
+from extensions.termdictionary.utils import simplify
 from resources.checks import is_staff_check  # for staff dictionary commands
 from resources.customs import Bot
 # for logging custom dictionary changes, or when a search query returns nothing or >2000 characters
 from resources.utils.utils import log_to_guild
 
-from extensions.termdictionary.views import DictionaryApi_PageView, UrbanDictionary_PageView
 
-
-del_separators_table = str.maketrans({" ": "", "-": "", "_": ""})
-
-
-def simplify(q: str | list[str]) -> str | list[str]:
-    if type(q) is str:
-        return q.lower().translate(del_separators_table)
-    if type(q) is list:
-        return [text.lower().translate(del_separators_table)
-                for text in q]
-    raise NotImplementedError()
+dictionary_sources: list[tuple[DictionarySources, DictionaryBase]] = [
+    # (d.CustomDictionary, CustomDictionary(client=itx.client)),
+    (DictionarySources.PronounsPage, PronounsPageDictionary()),
+    (DictionarySources.DictionaryApi, DictionaryApiDictionary()),
+    (DictionarySources.UrbanDictionary, UrbanDictionary()),
+]
 
 
 async def dictionary_autocomplete(itx: discord.Interaction[Bot], current: str):
-
-    terms = []
     if current == '':
         return []
 
-    # find results in custom dictionary
-    collection = itx.client.rina_db["termDictionary"]
-    query = {"synonyms": simplify(current)}
-    search = collection.find(query)
-    for item in search:
-        if simplify(current) in simplify(item["synonyms"]):
-            terms.append(item["term"])
-
-    # get list of choices from online
-    response_api = requests.get(f'https://en.pronouns.page/api/terms/search/{current}').text
-    data = json.loads(response_api)
-    # find exact results online
-    if len(data) != 0:
-        for item in data:
-            if item['term'].split("|")[0] not in terms:
-                if simplify(current) in simplify(item['term'].split('|')):
-                    terms.append(item['term'].split('|')[0])
-
-        # then, find whichever other terms are there (append / last) online
-        for item in data:
-            if item['term'].split("|")[0] not in terms:
-                terms.append(item['term'].split("|")[0])
+    # select the right sources
+    sources = dictionary_sources[:]
+    if itx.namespace.source in DictionarySources:
+        if type(itx.namespace.source) is not DictionarySources:
+            source = DictionarySources(itx.namespace.source)
+        else:
+            source = itx.namespace.source
 
-    # Next to that, also add generic dictionary options if your query exactly matches that of the dictionary
-    # but only if there aren't already 7 responses; to prevent extra loading time
-    if len(terms) < 7:
-        response_api = requests.get(f'https://api.dictionaryapi.dev/api/v2/entries/en/{current}').text
-        data = json.loads(response_api)
-        if type(data) is not dict:
-            for result in data:
-                if result["word"].capitalize() not in terms:
-                    terms.append(result["word"].capitalize())
-    # same for Urban Dictionary, searching only if there are no results for the others
-    if len(terms) < 1:
-        params = {"term": current}
-        response_api = requests.get(f'https://api.urbandictionary.com/v0/define', params=params).text
-        data = json.loads(response_api)['list']
-        for result in data:
-            if result["word"].capitalize() + " ([from UD])" not in terms:
-                terms.append(result["word"].capitalize() + " ([from UD])")
+        sources = [
+            dictionary for dictionary in dictionary_sources[:]
+            if dictionary[0] == source
+        ]
 
-    # limit choices to the first 7
-    terms = terms[:7]
+    # fetch autocompletion results
+    tasks = [
+        source.get_autocomplete(current)
+        for _, source in sources
+    ]
+    results: list[set[str]] = await asyncio.gather(*tasks)
+    terms: set[str] = set.union(*results)
 
+    # respond with found autocompletion terms
     return [
-        app_commands.Choice(name=term, value=term.replace(" ([from UD])", ""))
+        app_commands.Choice(name=term, value=term)
         for term in terms
-    ]
-
-
-async def _get_custom_dictionary_output(
-        client: Bot, term: str
-):
-    collection = client.async_rina_db["termDictionary"]
-    query = {"synonyms": term.lower()}
-    search = collection.find(query)
-    results: list[tuple[str, str]] = []
-    async for item in search:
-        item: DictionaryData
-        if simplify(term) in simplify(item["synonyms"]):
-            results.append((item["term"], item["definition"]))
-    if not results:
-        cmd_define = client.get_command_mention("dictionary_staff define")
-        raise KeyError(
-            f"No information found for '{term}' in the custom dictionary.\n"
-            f"If you would like to add a term, message a staff member "
-            f"(to use {cmd_define})"
-        )
-
-    result_str = (f"I found {len(results)} result{'s' * (len(results) > 1)} "
-                  f"for '{term}' in our dictionary:\n")
-    for result_term, result_definition in results:
-        result_str += f"> **{result_term}**: {result_definition}\n"
-
-    long_line = False
-    exceeding_line = False
-    if len(result_str.split("\n")) > 3:
-        long_line = True
-        result_str += "\nDidn't send your message as public cause it would be spammy, having this many results."
-    if len(result_str) > 1999:
-        exceeding_line = True
-        result_str = (
-            f"Your search ({term}) returned too many results (discord has a 2000-character "
-            f"message length D:). (Please ask staff to fix this (synonyms and stuff).)")
-    return result_str, long_line, exceeding_line
-
-
-async def _get_pronouns_page_output(public, result_str,
-                                    source, term):
-    http_safe_term = term.lower().replace("/", " ").replace("%", " ")
-    response_api = requests.get(
-        f'https://en.pronouns.page/api/terms/search/{http_safe_term}'
-    ).text
-    data = json.loads(response_api)
-    if len(data) == 0:
-        if source == 3:
-            source = 5
-        else:
-            result_str = f"I didn't find any results for '{term}' on en.pronouns.page"
-            public = False
-
-    # edit definitions to hide links to other pages:
-    else:
-        search = []
-        for item in data:
-            item_db = item['definition']
-            while item['definition'] == item_db:
-                replacement = re.search("(?<==).+?(?=})",
-                                        item['definition'])
-                if replacement is not None:
-                    item['definition'] = re.sub("{(#.+?=).+?}",
-                                                replacement.group(),
-                                                item['definition'], 1)
-                if item['definition'] == item_db:  # if nothing changed:
-                    break
-                item_db = item['definition']
-            while item['definition'] == item_db:
-                replacement = re.search("(?<={).+?(?=})",
-                                        item['definition'])
-                if replacement is not None:
-                    item['definition'] = re.sub("{.+?}",
-                                                replacement.group(),
-                                                item['definition'], 1)
-                if item['definition'] == item_db:  # if nothing changed:
-                    break
-                item_db = item['definition']
-            search.append(item)
-
-        # if one of the search results matches exactly with the search, give that definition
-        results: list[dict] = []
-        for item in search:
-            if simplify(term) in simplify(item['term'].split('|')):
-                results.append(item)
-        if len(results) > 0:
-            result_str = (
-                f"I found {len(results)} exact result{'s' * (len(results) != 1)} for "
-                f"'{term}' on en.pronouns.page! \n")
-            for item in results:
-                result_str += f"> **{', '.join(item['term'].split('|'))}:** {item['definition']}\n"
-            if (len(search) - len(results)) > 0:
-                result_str += f"{len(search) - len(results)} other non-exact results found."
-            if len(result_str) > 1999:
-                result_str = (
-                    f"Your search ('{term}') returned a too-long "
-                    f"result! (discord has a 2000-character message "
-                    f"length D:). To still let you get better results, "
-                    f"I've rewritten the terms so you might be able "
-                    f"to look for a more specific one:"
-                )
-                for item in results:
-                    result_str += f"> {', '.join(item['term'].split('|'))}\n"
-            raise OverflowError(result_str)
-
-        # if search doesn't exactly match with a result / synonym
-        result_str = (
-            f"I found {len(search)} result{'s' * (len(results) != 1)} for "
-            f"'{term}' on en.pronouns.page! ")
-        if len(search) > 25:
-            result_str += "Here is a list to make your search more specific:\n"
-            results: list[str] = []
-            for item in search:
-                temp = item['term']
-                if "|" in temp:
-                    temp = temp.split("|")[0]
-                results.append(temp)
-            result_str += ', '.join(results)
-            public = False
-        elif len(search) > 2:
-            result_str += "Here is a list to make your search more specific:\n"
-            results: list[str] = []
-            for item in search:
-                if "|" in item['term']:
-                    temp = "- __" + item['term'].split("|")[0] + "__"
-                    temp += " (" + ', '.join(
-                        item['term'].split("|")[1:]) + ")"
-                else:
-                    temp = "- __" + item['term'] + "__"
-                results.append(temp)
-            result_str += '\n'.join(results)
-            public = False
-        elif len(search) > 0:
-            result_str += "\n"
-            for item in search:
-                result_str += f"> **{', '.join(item['term'].split('|'))}:** {item['definition']}\n"
-        else:
-            result_str = f"I didn't find any results for '{term}' on en.pronouns.page!"
-            if source == 4:
-                source = 6
-        msg_length = len(result_str)
-        if msg_length > 1999:
-            public = False
-            result_str = (
-                f"Your search ('{term}') returned too many results ({len(search)} in total!) "
-                f"(discord has a 2000-character message length, and this message "
-                f"was {msg_length} characters D:). Please search more specifically.\n"
-                f"    Here is a link for expanded info on each term: "
-                f"")
-    return public, result_str, source
-
-
-async def _get_urban_dictionary_pages(data):
-    pages = []
-    page = 0
-    for result in data:
-        embed = discord.Embed(color=8481900,
-                              title=f"__{result['word'].capitalize()}__",
-                              description=result['definition'],
-                              url=result['permalink'])
-        post_date = int(
-            datetime.strptime(
-                result['written_on'][:-1],
-                # "2009-03-04T01:16:08.000Z" ([:-1] to remove Z at end)
-                "%Y-%m-%dT%H:%M:%S.%f"
-            ).timestamp()
-        )
-        warning = ""
-        if len(result['example']) > 800:
-            warning = "... (shortened due to size)"
-        embed.add_field(name="Example",
-                        value=f"{result['example'][:800]}{warning}\n\n"
-                              f"{result['thumbs_up']}:thumbsup: :thumbsdown: {result['thumbs_down']}\n"
-                              f"Sent by {result['author']} on  at "
-                              f" ()",
-                        inline=False)
-        pages.append(embed)
-    return page, pages
-
-
-def _get_dictionary_api_pages(data, results):
-    for result in data:
-        meanings = []
-        synonyms = []
-        antonyms = []
-        for meaning in result["meanings"]:
-            meaning_list = [meaning['partOfSpeech']]
-            # **verb**:
-            # meaning one is very useful
-            # meaning two is not as useful
-            for definition in meaning["definitions"]:
-                meaning_list.append("- " + definition['definition'])
-                for synonym in definition['synonyms']:
-                    if synonym not in synonyms:
-                        synonyms.append(synonym)
-                for antonym in definition['antonyms']:
-                    if antonym not in antonyms:
-                        antonyms.append(antonym)
-            for synonym in meaning['synonyms']:
-                if synonym not in synonyms:
-                    synonyms.append(synonym)
-            for antonym in meaning['antonyms']:
-                if antonym not in antonyms:
-                    antonyms.append(antonym)
-            meanings.append(meaning_list)
-
-        results.append([
-            # train
-            result["word"],
-            # [  ["noun", "- Hello there this is 1", "- number two"], ["verb", ...], [...]  ]
-            meanings,
-            ', '.join(synonyms),
-            ', '.join(antonyms),
-            '\n'.join(result["sourceUrls"])
-        ])
-    pages = []
-    pages_detailed = []
-    page = 0
-    for result in results:
-        result_id = 0
-        page_detailed = []
-        embed = discord.Embed(color=8481900,
-                              title=f"__{result[0].capitalize()}__")
-        for meaning_index in range(len(result[1])):
-            _part = result[1][meaning_index][1:]
-            part = []
-            for definition in _part:
-                page_detailed.append(
-                    [result_id, f"__{result[0].capitalize()}__",
-                     result[1][meaning_index][0].capitalize(),
-                     definition])
-                part.append(f"`{result_id}`" + definition)
-                result_id += 1
-            value = '\n'.join(part)
-            if len(value) > 995:  # limit to 1024 chars in Value field
-                value = value[:995] + "... (shortened due to size)"
-            embed.add_field(name=result[1][meaning_index][0].capitalize(),
-                            value=value,
-                            inline=False)
-        if len(result[2]) > 0:
-            embed.add_field(name="Synonyms",
-                            value=f"`{result_id}`" + result[2],
-                            inline=False)
-            page_detailed.append(
-                [result_id, f"__{result[0].capitalize()}__", "Synonyms",
-                 result[2]])
-            result_id += 1
-        if len(result[3]) > 0:
-            embed.add_field(name="Antonyms",
-                            value=f"`{result_id}`" + result[3],
-                            inline=False)
-            page_detailed.append(
-                [result_id, f"__{result[0].capitalize()}__", "Antonyms",
-                 result[3]])
-            result_id += 1
-        if len(result[4]) > 0:
-            embed.add_field(name=f"`{result_id}`-" + "More info:",
-                            value=result[4],
-                            inline=False)
-            page_detailed.append(
-                [result_id, f"__{result[0].capitalize()}__", "More info:",
-                 result[4]])
-            result_id += 1
-        pages.append(embed)
-        pages_detailed.append(page_detailed)
-        # [meaning, [type, definition1, definition2], synonym, antonym, sources]
-    return page, pages, pages_detailed
+    ][:7]  # limit choices to the first 7
 
 
 class TermDictionary(commands.Cog):
@@ -395,110 +103,47 @@ async def dictionary(
             public: bool = False,
             source: DictionarySources = DictionarySources.All,
     ):
-        # todo: rewrite this whole command.
-        #  - Make the sources an enum.
-        #  - Allow going to the next source if you're not satisfied with
-        #    a dictionary's result.
-        #  - Perhaps make a view with multi-selectable options for which
-        #    sources you would want to search through
-        #    though this sounds like a bad idea Xd.
-        #  - Should also have an integration without custom dictionary
-        #    that users can install.
-        #  - Make all pageviews into actual PageView views.
         itx.response: discord.InteractionResponse[Bot]  # type: ignore
-        # test if mode has been left unset or if mode has been selected: decides whether to move to the
-        # online API search or not.
-        result_str = ""
-        # to make my IDE happy. Will still crash on discord if it actually tries to send it tho: 'Empty message'
-        results: list
-        if (source == DictionarySources.All
-                or source == DictionarySources.CustomDictionary):
-            public, result_str, source = await _get_custom_dictionary_output(
-                itx.client, term
-            )
-        if ((source == DictionarySources.All and not result_str)
-                or source == DictionarySources.PronounsPage):
-            try:
-                public, result_str, source = \
-                    await _get_pronouns_page_output(
-                        public, result_str, source, term
-                    )
-            except OverflowError as ex:
-                await itx.reponse.send_message(str(ex), ephemeral=True,
-                                               suppress_embeds=True)
-        if source == 5 or source == 6:
-            await itx.response.defer(ephemeral=True)
-            response_api = requests.get(
-                f'https://api.dictionaryapi.dev/api/v2/entries/en/{term.lower().replace("/", "%2F")}').text
-            try:
-                data: any = json.loads(response_api)
-            except json.decoder.JSONDecodeError:
-                # if a bad api response is given, catch and continue as if empty results
-                data: dict = {}  # specify class to make IDE happy
-            results = []
-            if type(data) is dict:
-                if source == 5:
-                    source = 7
-                else:
-                    result_str = f"I didn't find any results for '{term}' on dictionaryapi.dev!"
-                    public = False
-            else:
-                page, pages, pages_detailed = \
-                    _get_dictionary_api_pages(
-                        data, results
-                    )
-
-                embed = pages[page]
-                embed.set_footer(text="page: " + str(page + 1) + " / " + str(int(len(pages))))
-                view = DictionaryApi_PageView(pages, pages_detailed, timeout=90)
-                await itx.followup.send(f"I found the following `{len(results)}` results on dictionaryapi.dev: ",
-                                        embed=embed, view=view, ephemeral=True)
-                await view.wait()
-                if view.value in [None, 1, 2]:
-                    await itx.edit_original_response(view=None)
-        if source == 7 or source == 8:
-            if not itx.response.is_done():
-                await itx.response.defer(ephemeral=True)
-            params = {"term": term.lower()}
-            response_api = requests.get(f'https://api.urbandictionary.com/v0/define', params=params).text
-            # who decided to put the output into a dictionary with a list named 'list'? {"list":[{},{},{}]}
-            data = json.loads(response_api)['list']
-            if len(data) == 0:
-                if source == 7:
-                    result_str = f"I didn't find any results for '{term}' online or in our fancy dictionaries"
-                    cmd_dictionary = itx.client.get_command_mention(
-                        "dictionary")
-                    cmd_define = itx.client.get_command_mention_with_args(
-                        "dictionary_staff define", term=" ", definition=" ")
-                    await log_to_guild(
-                        itx.client,
-                        itx.guild,
-                        f":warning: **!! Alert:** {itx.user.name} "
-                        f"({itx.user.id}) searched for '{term}' in the "
-                        f"terminology dictionary and online, but there were "
-                        f"no results. Maybe we should add this term to "
-                        f"the {cmd_dictionary} command ({cmd_define})")
-                else:
-                    result_str = f"I didn't find any results for '{term}' on urban dictionary"
-                public = False
-            else:
-                page, pages = await _get_urban_dictionary_pages(data)
+        await itx.response.defer(ephemeral=public)
+
+        sources = dictionary_sources[:]
+        if source != DictionarySources.All:
+            # filter dictionary sources to only the enabled ones.
+            sources = [
+                dictionary for dictionary in sources
+                if dictionary[0] == source
+            ]
+
+        await asyncio.gather(*[
+            source.construct_response(term)
+            for _, source in sources
+        ])
 
-                embed = pages[page]
-                embed.set_footer(text="page: " + str(page + 1) + " / " + str(int(len(pages))))
-                view = UrbanDictionary_PageView(pages, timeout=90)
-                await itx.followup.send(f"I found the following `{len(pages)}` results on urbandictionary.com: ",
-                                        embed=embed, view=view, ephemeral=True)
-                await view.wait()
-                if view.value in [None, 1, 2]:
-                    await itx.edit_original_response(view=None)
+        for dict_source, strategy in sources:
+            if strategy.has_response:
+                await strategy.send_response(itx, public)
+                return
+            elif dict_source == source:
+                if public:
+                    # remove public defer message so itx.followup.send
+                    #  doesn't also send the deny message publicly.
+                    await itx.delete_original_response()
+                await strategy.handle_no_response(itx, term)
                 return
 
-        assert len(result_str) > 0
-        if itx.response.is_done():
-            await itx.followup.send(result_str, ephemeral=not public, suppress_embeds=True)
-        else:
-            await itx.response.send_message(result_str, ephemeral=not public, suppress_embeds=True)
+        cmd_dictionary = itx.client.get_command_mention(
+            "dictionary")
+        cmd_define = itx.client.get_command_mention_with_args(
+            "dictionary_staff define", term=" ", definition=" ")
+        await log_to_guild(
+            itx.client,
+            itx.guild,
+            f":warning: **!! Alert:** {itx.user.name} "
+            f"({itx.user.id}) searched for '{term}' in the "
+            f"terminology dictionary and online, but there were "
+            f"no results. Maybe we should add this term to "
+            f"the {cmd_dictionary} command ({cmd_define})"
+        )
 
     admin = app_commands.Group(name='dictionary_staff', description='Change custom entries in the dictionary')
 
@@ -525,38 +170,56 @@ async def define(self, itx: discord.Interaction[Bot], term: str, definition: str
         await itx.response.defer(ephemeral=True)
         # Test if a synonym is already used before
         if synonyms != "":
-            synonyms = synonyms.split(", ")
-            synonyms = [simplify(i) for i in synonyms]
+            synonym_list = synonyms.split(", ")
+            synonym_list = [simplify(i) for i in synonym_list]
         else:
-            synonyms = []
-        if simplify(term) not in synonyms:
-            synonyms.append(simplify(term))
+            synonym_list = []
+        if simplify(term) not in synonym_list:
+            synonym_list.append(simplify(term))
 
-        query = {"synonyms": {"$in": synonyms}}
+        query = {"synonyms": {"$in": synonym_list}}
         synonym_overlap = collection.find(query)
         warnings = ""
         for overlap in synonym_overlap:
             warnings += f"You have already given a synonym before in {overlap['term']}.\n"
 
         # Add term to dictionary
-        post = {"term": term, "definition": definition, "synonyms": synonyms}
+        post = {
+            "term": term,
+            "definition": definition,
+            "synonyms": synonym_list
+        }
         collection.insert_one(post)
 
-        await log_to_guild(itx.client, itx.guild,
-                           f"{itx.user.nick or itx.user.name} ({itx.user.id}) added the dictionary definition "
-                           f"of '{term}' and set it to '{definition}', with synonyms: {synonyms}")
+        username = getattr(itx.user, 'nick', None) or itx.user.name
+        await log_to_guild(
+            itx.client,
+            itx.guild,
+            f"{username} ({itx.user.id}) added "
+            f"the dictionary definition of '{term}' and set it to "
+            f"'{definition}', with synonyms: {synonym_list}",
+        )
         await itx.followup.send(
-            content=warnings + f"Successfully added '{term}' to the dictionary "
-                               f"(with synonyms: {synonyms}): {definition}",
-            ephemeral=True)
+            content=warnings
+            + f"Successfully added '{term}' to the dictionary "
+              f"(with synonyms: {synonym_list}): {definition}",
+            ephemeral=True,
+        )
 
     @app_commands.check(is_staff_check)
-    @admin.command(name="redefine", description="Edit a dictionary entry for a word!")
+    @admin.command(name="redefine",
+                   description="Edit a dictionary entry for a word!")
     @app_commands.describe(
-        term="This is the main word for the dictionary entry (case sens.) Example: Egg, Hormone "
-             "Replacement Therapy (HRT), etc.",
-        definition="Redefine this definition")
-    async def redefine(self, itx: discord.Interaction[Bot], term: str, definition: str):
+        term="This is the main word for the dictionary entry (case sens.) "
+             "Example: Egg, Hormone Replacement Therapy (HRT), etc.",
+        definition="Redefine this definition"
+    )
+    async def redefine(
+            self,
+            itx: discord.Interaction[Bot],
+            term: str,
+            definition: str
+    ):
         collection = itx.client.rina_db["termDictionary"]
         query = {"term": term}
         search = collection.find_one(query)
@@ -566,82 +229,146 @@ async def redefine(self, itx: discord.Interaction[Bot], term: str, definition: s
             await itx.response.send_message(
                 f"This term hasn't been added to the dictionary yet, and "
                 f"thus cannot be redefined! Use {cmd_define}.",
-                ephemeral=True)
+                ephemeral=True,
+            )
             return
         collection.update_one(query, {"$set": {"definition": definition}})
 
-        await log_to_guild(itx.client, itx.guild,
-                           f"{itx.user.nick or itx.user.name} ({itx.user.id}) changed the dictionary definition "
-                           f"of '{term}' to '{definition}'")
-        await itx.response.send_message(f"Successfully redefined '{term}'", ephemeral=True)
+        username = getattr(itx.user, 'nick', None) or itx.user.name
+        await log_to_guild(
+            itx.client,
+            itx.guild,
+            f"{username} ({itx.user.id}) changed the dictionary definition "
+            f"of '{term}' to '{definition}'",
+        )
+        await itx.response.send_message(
+            f"Successfully redefined '{term}'",
+            ephemeral=True
+        )
 
     @app_commands.check(is_staff_check)
-    @admin.command(name="undefine", description="Remove a dictionary entry for a word!")
+    @admin.command(name="undefine",
+                   description="Remove a dictionary entry for a word!")
     @app_commands.describe(
-        term="What word do you need to undefine (case sensitive). Example: Egg, Hormone Replacement Therapy (HRT), etc")
+        term="What word do you need to undefine (case sensitive). Example: "
+             "Egg, Hormone Replacement Therapy (HRT), etc",
+    )
     async def undefine(self, itx: discord.Interaction[Bot], term: str):
         collection = itx.client.rina_db["termDictionary"]
         query = {"term": term}
         search = collection.find_one(query)
         if search is None:
             await itx.response.send_message(
-                "This term hasn't been added to the dictionary yet, and thus cannot be undefined!", ephemeral=True)
+                "This term hasn't been added to the dictionary yet, and "
+                "thus cannot be undefined!",
+                ephemeral=True,
+            )
             return
-        await log_to_guild(itx.client, itx.guild,
-                           f"{itx.user.nick or itx.user.name} ({itx.user.id}) undefined the dictionary "
-                           f"definition of '{term}' from '{search['definition']}' with synonyms: {search['synonyms']}")
+        username = getattr(itx.user, 'nick', None) or itx.user.name
+        await log_to_guild(
+            itx.client,
+            itx.guild,
+            f"{username} ({itx.user.id}) undefined the dictionary "
+            f"definition of '{term}' from '{search['definition']}' with "
+            f"synonyms: {search['synonyms']}",
+        )
         collection.delete_one(query)
 
-        await itx.response.send_message(f"Successfully undefined '{term}'", ephemeral=True)
+        await itx.response.send_message(
+            f"Successfully undefined '{term}'",
+            ephemeral=True
+        )
 
     @app_commands.check(is_staff_check)
-    @admin.command(name="editsynonym", description="Add a synonym to a previously defined word")
+    @admin.command(name="editsynonym",
+                   description="Add a synonym to a previously defined word")
     @app_commands.describe(
-        term="This is the main word for the dictionary entry (case sens.): Egg, Hormone Transfer Therapy, etc",
+        term="This is the main word for the dictionary entry (case sens.): "
+             "Egg, Hormone Transfer Therapy, etc",
         mode="Add or remove a synonym?",
-        synonym="Which synonym to add/remove?")
+        synonym="Which synonym to add/remove?"
+    )
     @app_commands.choices(mode=[
         discord.app_commands.Choice(name='Add a synonym', value=1),
         discord.app_commands.Choice(name='Remove a synonym', value=2),
     ])
-    async def edit_synonym(self, itx: discord.Interaction[Bot], term: str, mode: int, synonym: str):
+    async def edit_synonym(
+            self,
+            itx: discord.Interaction[Bot],
+            term: str,
+            mode: int,
+            synonym: str
+    ):
         collection = itx.client.rina_db["termDictionary"]
         query = {"term": term}
         search = collection.find_one(query)
         if search is None:
             cmd_define = itx.client.get_command_mention_with_args(
-                "dictionary_staff define", term=" ", definition=" ")
+                "dictionary_staff define",
+                term=" ",
+                definition=" "
+            )
             await itx.response.send_message(
-                f"This term hasn't been added to the dictionary yet, and thus cannot get "
-                f"new synonyms! Use {cmd_define}.",
+                f"This term hasn't been added to the dictionary yet, and "
+                f"thus cannot get new synonyms! Use {cmd_define}.",
                 ephemeral=True)
             return
 
+        username = getattr(itx.user, 'nick', None) or itx.user.name
+
         if mode == 1:
             synonyms = search["synonyms"]
             if synonym.lower() in synonyms:
-                await itx.response.send_message("This term already has this synonym!", ephemeral=True)
+                await itx.response.send_message(
+                    "This term already has this synonym!",
+                    ephemeral=True,
+                )
                 return
             synonyms.append(synonym.lower())
-            collection.update_one(query, {"$set": {"synonyms": synonyms}}, upsert=True)
-            await log_to_guild(itx.client, itx.guild,
-                               f"{itx.user.nick or itx.user.name} ({itx.user.id}) added synonym '{synonym}' "
-                               f"the dictionary definition of '{term}'")
-            await itx.response.send_message("Successfully added synonym", ephemeral=True)
+            collection.update_one(
+                query,
+                {"$set": {"synonyms": synonyms}},
+                upsert=True,
+            )
+            await log_to_guild(
+                itx.client,
+                itx.guild,
+                f"{username} ({itx.user.id}) added synonym '{synonym}' "
+                f"the dictionary definition of '{term}'",
+            )
+            await itx.response.send_message(
+                "Successfully added synonym",
+                ephemeral=True,
+            )
         if mode == 2:
             synonyms = search["synonyms"]
             if synonym.lower() not in synonyms:
-                await itx.response.send_message("This term doesn't have this synonym!", ephemeral=True)
+                await itx.response.send_message(
+                    "This term doesn't have this synonym!",
+                    ephemeral=True,
+                )
                 return
             synonyms.remove(synonym.lower())
             if len(synonyms) < 1:
                 await itx.response.send_message(
-                    "You can't remove all the synonyms to a term! Then you can't find it in the dictionary anymore :P. "
-                    "First, add a synonym before removing one.",
-                    ephemeral=True)
+                    "You can't remove all the synonyms to a term! Then you "
+                    "can't find it in the dictionary anymore :P. First, add "
+                    "a synonym before removing one.",
+                    ephemeral=True,
+                )
                 return
-            collection.update_one(query, {"$set": {"synonyms": synonyms}}, upsert=True)
-            await log_to_guild(itx.client, itx.guild,
-                               f"{itx.user.nick or itx.user.name} ({itx.user.id}) removed synonym '{synonym}' "
-                               f"the dictionary definition of '{term}'")
-            await itx.response.send_message("Successfully removed synonym", ephemeral=True)
+            collection.update_one(
+                query,
+                {"$set": {"synonyms": synonyms}},
+                upsert=True,
+            )
+            await log_to_guild(
+                itx.client,
+                itx.guild,
+                f"{username} ({itx.user.id}) removed synonym '{synonym}' "
+                f"the dictionary definition of '{term}'",
+            )
+            await itx.response.send_message(
+                "Successfully removed synonym",
+                ephemeral=True,
+            )
diff --git a/extensions/termdictionary/dictionaries/CustomDictionary.py b/extensions/termdictionary/dictionaries/CustomDictionary.py
new file mode 100644
index 0000000..3d91f81
--- /dev/null
+++ b/extensions/termdictionary/dictionaries/CustomDictionary.py
@@ -0,0 +1,124 @@
+from typing import override
+
+import discord
+
+from resources.checks.permissions import is_staff
+from resources.customs import Bot
+from .DictionaryBase import DictionaryBase
+from extensions.termdictionary.dictionaries.objects import \
+    CustomDictionaryEntry
+from extensions.termdictionary.utils import simplify
+
+
+class CustomDictionary(DictionaryBase):
+    def __init__(self, client):
+        super().__init__()
+        self.client = client
+        self._result_str: str | None = None
+        self._long_line: bool = False
+        self._character_overflow: bool = False
+
+    async def _get_database_response(self, term: str) -> list[tuple[str, str]]:
+        """Helper to get custom dictionary data from the database."""
+        collection = self.client.async_rina_db["termDictionary"]
+        query = {"synonyms": term.lower()}
+        search = collection.find(query)
+        results: list[tuple[str, str]] = []
+        async for item in search:
+            item: CustomDictionaryEntry
+            if simplify(term) in simplify(item["synonyms"]):
+                results.append((item["term"], item["definition"]))
+
+        return results
+
+    @override
+    async def get_autocomplete(self, current: str) -> set[str]:
+        results = await self._get_database_response(current)
+        if not results:
+            return set()
+        return set(current for current, _ in results)
+
+    @override
+    async def construct_response(self, term: str) -> None:
+        results = await self._get_database_response(term)
+        if not results:
+            return
+        self.has_response = True
+
+        self._result_str = (
+            f"I found {len(results)} result{'s' * (len(results) > 1)} "
+            f"for '{term}' in our dictionary:\n"
+        )
+        for result_term, result_definition in results:
+            self._result_str += f"> **{result_term}**: {result_definition}\n"
+
+        if len(self._result_str.split("\n")) > 3:
+            self._long_line = True
+
+        if len(self._result_str) > 1999:
+            self._character_overflow = True
+
+    @override
+    async def send_response(
+            self,
+            itx: discord.Interaction[Bot],
+            public: bool
+    ) -> None:
+        itx.followup: discord.Webhook  # type: ignore
+        assert self._result_str is not None
+
+        if self._character_overflow:
+            self._result_str = (
+                "Your search returned too many results (discord has "
+                "a 2000-character message length D:). (Please ask staff to "
+                "fix this (synonyms and stuff).)\n\n"
+                + self._result_str
+            )[:2000]
+            public = False
+
+        if public and self._long_line:
+            self._result_str += (
+                "\nDidn't send your message as public cause it"
+                " would be spammy, having this many results."
+            )
+            public = False
+
+        await itx.followup.send(
+            self._result_str, ephemeral=not public, suppress_embeds=True,
+            allowed_mentions=discord.AllowedMentions.none()
+        )
+
+    @override
+    async def handle_no_response(
+            self,
+            itx: discord.Interaction[Bot],
+            term: str
+    ) -> None:
+        if is_staff(itx, itx.user):
+            cmd_define = itx.client.get_command_mention_(
+                "dictionary_staff define",
+                term=term,
+                definition=" ",
+                synonyms="a, b, c"
+            )
+            result_str = (
+                f"No information found in the custom dictionary for "
+                f"this term.\n"
+                f"If you would like to add a term,  use {cmd_define}."
+            )
+        else:
+            cmd_define = itx.client.get_command_mention(
+                "dictionary_staff define")
+            result_str = (
+                f"No information found in the custom dictionary for "
+                f"this term.\n"
+                f"If you would like to add a term, message a staff member "
+                f"(to use {cmd_define})"
+            )
+
+        await itx.followup.send(
+            result_str,
+            ephemeral=True,
+            suppress_embeds=True,
+            allowed_mentions=discord.AllowedMentions.none()
+        )
diff --git a/extensions/termdictionary/dictionaries/DictionaryApiDictionary.py b/extensions/termdictionary/dictionaries/DictionaryApiDictionary.py
new file mode 100644
index 0000000..3bb9764
--- /dev/null
+++ b/extensions/termdictionary/dictionaries/DictionaryApiDictionary.py
@@ -0,0 +1,144 @@
+
+import discord
+
+import json
+import requests
+from typing import override
+import urllib.parse
+
+from resources.customs import Bot
+from extensions.termdictionary.dictionaries.DictionaryBase import \
+    DictionaryBase
+from extensions.termdictionary.dictionaries.objects import (
+    DictionaryApiEntry,
+    DetailedTermPage, term_page_to_embed
+)
+from extensions.termdictionary.views import DictionaryapiPageview
+
+
+CompressedApiData = list[tuple[str, dict[str, list[str]], str, str, str]]
+
+
+def _format_page_sections(antonyms, meanings, synonyms):
+    page_sections: dict[str, list[str]] = {}
+    for part_of_speech, definitions in meanings.items():
+        part_of_speech = part_of_speech.capitalize()
+        page_sections[part_of_speech] = definitions
+    if synonyms:
+        page_sections["Synonyms"] = [", ".join(synonyms)]
+    if antonyms:
+        page_sections["Antonyms"] = [", ".join(antonyms)]
+    if "sourceUrls" in page_sections:
+        page_sections["More info:"] \
+            = ['\n'.join(page_sections["sourceUrls"])]
+    return page_sections
+
+
+def _extract_api_data(result):
+    meanings: dict[str, list[str]] = {}
+    synonyms: set[str] = set()
+    antonyms: set[str] = set()
+    for meaning in result["meanings"]:
+        meaning_list = []
+        for definition in meaning["definitions"]:
+            meaning_list.append("- " + definition['definition'])
+            synonyms.update(definition['synonyms'])
+            antonyms.update(definition['antonyms'])
+
+        part_of_speech = meaning['partOfSpeech'].capitalize()
+        meanings[part_of_speech] = meaning_list
+        synonyms.update(meaning['synonyms'])
+        antonyms.update(meaning['antonyms'])
+    return antonyms, meanings, synonyms
+
+
+class DictionaryApiDictionary(DictionaryBase):
+    def __init__(self):
+        super().__init__()
+        self._embed: discord.Embed | None = None
+        self._view: DictionaryapiPageview | None = None
+
+    @staticmethod
+    def _get_pages(
+            data: list[DictionaryApiEntry]
+    ) -> list[DetailedTermPage]:
+        """
+        Helper to
+        """
+        pages = []
+        for result in data:
+            antonyms, meanings, synonyms = _extract_api_data(result)
+            page_sections = _format_page_sections(antonyms, meanings, synonyms)
+            pages.append((result["word"].capitalize(), page_sections))
+
+        return pages
+
+    @staticmethod
+    async def _get_api_response(term: str) -> list[DictionaryApiEntry] | None:
+        response_api = requests.get(
+            "https://api.dictionaryapi.dev/api/v2/entries/en/"
+            + urllib.parse.quote(term.lower(), safe=())
+            # ^ slashes aren't safe
+        ).text
+        try:
+            return json.loads(response_api)
+        except json.decoder.JSONDecodeError:
+            return None
+
+    @override
+    async def get_autocomplete(self, current: str) -> set[str]:
+        data = await self._get_api_response(current)
+        if data is None:
+            return set()
+
+        terms = set()
+        for result in data:
+            terms.add(result["word"])
+        return terms
+
+    @override
+    async def construct_response(self, term: str) -> None:
+        data = await self._get_api_response(term)
+        if data is None:
+            return
+        pages = DictionaryApiDictionary._get_pages(data)
+
+        start_page = 0
+        embed = term_page_to_embed(pages[start_page])
+        embed.set_footer(
+            text=f"page: 1 / {len(pages)}")
+
+        self._embed = embed
+        self._view = DictionaryapiPageview(pages, timeout=90)
+        self.has_response = True
+
+    @override
+    async def send_response(
+            self,
+            itx: discord.Interaction[Bot],
+            public: bool
+    ) -> None:
+        itx.followup: discord.Webhook  # type: ignore
+        assert (self.has_response
+                and self._view is not None
+                and self._embed is not None)
+        await itx.followup.send(
+            embed=self._embed,
+            view=self._view
+        )
+        await self._view.wait()
+        await itx.edit_original_response(view=None)
+
+    @override
+    async def handle_no_response(
+            self,
+            itx: discord.Interaction[Bot],
+            term: str
+    ) -> None:
+        itx.followup: discord.Webhook  # type: ignore
+        await itx.followup.send(
+            f"I didn't find any results for '{term}' on dictionaryapi.dev!",
+            ephemeral=True,
+            suppress_embeds=True,
+            allowed_mentions=discord.AllowedMentions.none()
+        )
diff --git a/extensions/termdictionary/dictionaries/DictionaryBase.py b/extensions/termdictionary/dictionaries/DictionaryBase.py
new file mode 100644
index 0000000..6d66cec
--- /dev/null
+++ b/extensions/termdictionary/dictionaries/DictionaryBase.py
@@ -0,0 +1,76 @@
+from abc import abstractmethod
+
+import discord
+
+from resources.customs import Bot
+
+
+class DictionaryBase:
+    """
+    Base class for dictionary sources to handle making and sending responses.
+
+    Provides base functionality for dictionary sources. Subclasses must
+    implement methods to construct and send responses.
+
+    :ivar has_response: Indicates whether a response has been
+     constructed.
+    :ivar character_overflow: Indicates whether the constructed response
+     exceeds Discord's character limit.
+    """
+    def __init__(self):
+        self.has_response = False
+        self._character_overflow = False
+
+    @abstractmethod
+    async def get_autocomplete(self, current: str) -> set[str]:
+        """Get a list of autocomplete terms based on the current input.
+
+        :param current: The partial term input by the user for which
+         autocomplete suggestions should be generated.
+        :return: A set of possible autocomplete suggestions.
+        """
+        pass
+
+    @abstractmethod
+    async def construct_response(self, term: str) -> None:
+        """Construct a response for the given dictionary term.
+
+        :param term: The dictionary term to construct a response for.
+        :return: ``None``. The response is stored internally.
+         Use :py:attr:has_response: to see if a response could be
+         constructed. If so, run :py:meth:`send_response`.
+        """
+        pass
+
+    @abstractmethod
+    async def send_response(
+            self,
+            itx: discord.Interaction[Bot],
+            public: bool
+    ) -> None:
+        """Send the dictionary response to the user.
+
+        :param itx: The interaction to respond to.
+        :param public: Whether the response should be sent publicly.
+        """
+        pass
+
+    @abstractmethod
+    async def handle_no_response(
+            self,
+            itx: discord.Interaction[Bot],
+            term: str
+    ) -> None:
+        """
+        Send an error message that no valid response was found.
+
+        .. note::
+
+            This function has no checks, so you should first check
+            if :py:attr:has_response: is ``True``.
+
+        :param term:
+        :param itx: The interaction to respond to.
+        :return: None.
+        """
+        pass
diff --git a/extensions/termdictionary/dictionaries/PronounsPageDictionary.py b/extensions/termdictionary/dictionaries/PronounsPageDictionary.py
new file mode 100644
index 0000000..6dfa077
--- /dev/null
+++ b/extensions/termdictionary/dictionaries/PronounsPageDictionary.py
@@ -0,0 +1,268 @@
+
+import discord
+
+import json
+import re  # to parse and remove https:/pronouns.page/ in-text page linking
+import requests
+from typing import override
+
+from .DictionaryBase import DictionaryBase
+from resources.customs import Bot
+from extensions.termdictionary.utils import simplify
+from .objects import PronounsPageEntry
+
+
+class PronounsPageDictionary(DictionaryBase):
+    """
+    Implementation of DictionaryBase for querying the pronouns.page API.
+
+    This class handles dictionary entry lookup, response construction,
+    and result formatting specific to the pronouns.page service.
+    It manages internal state for search results, response expansion, and Discord character limit constraints.
+
+    :ivar _result_str: Final response string to send to the user, or None if no results found.
+    :ivar _result_count: Number of matching results found for the current term.
+    :ivar _character_overflow: Internal flag to track if response exceeds Discord's 2000-character limit.
+    :ivar _expand_search: Boolean indicating whether to expand search to include synonyms/related terms.
+    """
+    def __init__(self):
+        super().__init__()
+        self._result_str: str | None = None
+        self._result_count = 0
+        self._character_overflow = False
+        self._expand_search = False
+        self._term: str | None = None
+
+    @staticmethod
+    async def _clean_pronouns_page_response_links(
+            data: list[PronounsPageEntry]
+    ) -> list[PronounsPageEntry]:
+        """
+        Helper for removing links in pronouns.page responses.
+        :param data: The api response to clean up.
+        :return: The given response data with cleaned up descriptions.
+        """
+        search = []
+        for item in data:
+            item_db = item['definition']
+            while item['definition'] == item_db:
+                replacement = re.search("(?<==).+?(?=})",
+                                        item['definition'])
+                if replacement is not None:
+                    item['definition'] = re.sub("{(#.+?=).+?}",
+                                                replacement.group(),
+                                                item['definition'], 1)
+                if item['definition'] == item_db:  # if nothing changed:
+                    break
+                item_db = item['definition']
+            while item['definition'] == item_db:
+                replacement = re.search("(?<={).+?(?=})",
+                                        item['definition'])
+                if replacement is not None:
+                    item['definition'] = re.sub("{.+?}",
+                                                replacement.group(),
+                                                item['definition'], 1)
+                if item['definition'] == item_db:  # if nothing changed:
+                    break
+                item_db = item['definition']
+            search.append(item)
+        return search
+
+    @staticmethod
+    async def _get_expand_medium_search_string(result_str, search):
+        result_str += "Here is a list to make your search more specific:\n"
+        results: list[str] = []
+        for item in search:
+            if "|" in item['term']:
+                temp = "- __" + item['term'].split("|")[0] + "__"
+                temp += " (" + ', '.join(
+                    item['term'].split("|")[1:]) + ")"
+            else:
+                temp = "- __" + item['term'] + "__"
+            results.append(temp)
+        result_str += '\n'.join(results)
+        return result_str
+
+    @staticmethod
+    async def _get_expand_big_search_string(result_str, search):
+        result_str += "Here is a list to make your search more specific:\n"
+        results: list[str] = []
+        for item in search:
+            temp = item['term']
+            if "|" in temp:
+                temp = temp.split("|")[0]
+            results.append(temp)
+        result_str += ', '.join(results)
+        return result_str
+
+    @staticmethod
+    async def _select_exact_items(
+            search: list[PronounsPageEntry], term
+    ) -> list[PronounsPageEntry]:
+        """
+        Select search results whose key or synonyms exactly match the
+        given term.
+
+        :param search: The API response to filter.
+        :param term: The term the user searched for.
+        :return: The filtered search results.
+        """
+        # If one of the search results matches exactly with the
+        #  search, give that definition.
+        results: list[PronounsPageEntry] = []
+        for item in search:
+            if simplify(term) in simplify(item['term'].split('|')):
+                results.append(item)
+        return results
+
+    @staticmethod
+    async def _get_result_string(
+            results: list[PronounsPageEntry],
+            search: list[PronounsPageEntry],
+            term: str
+    ) -> str:
+        """
+        Construct a response from the list of results.
+        :param results: The selected search results containing the term.
+        :param search: The api-provided search results (that may contain
+         the term in the description, but not necessarily in the title
+         or synonyms)
+        :param term: The term the user searched for.
+        :return: The constructed response.
+        :raise OverflowError: If the API response has too many results.
+        """
+        result_str = (
+            f"I found {len(results)} exact result{'s' * (len(results) != 1)} "
+            f"for '{term}' on en.pronouns.page! \n")
+        for item in results:
+            result_str += (f"> **{', '.join(item['term'].split('|'))}:** "
+                           f"{item['definition']}\n")
+        if (len(search) - len(results)) > 0:
+            result_str += (f"{len(search) - len(results)} other non-exact "
+                           f"results found.")
+        if len(result_str) > 1999:
+            result_str = (
+                f"Your search ('{term}') returned a too-long "
+                f"result! (discord has a 2000-character message "
+                f"length D:). To still let you get better results, "
+                f"I've rewritten the terms so you might be able "
+                f"to look for a more specific one:"
+            )
+            for item in results:
+                result_str += f"> {', '.join(item['term'].split('|'))}\n"
+            raise OverflowError(result_str)
+        return result_str
+
+    @staticmethod
+    async def _get_api_response(term) -> list[PronounsPageEntry]:
+        http_safe_term = term.lower().replace("/", " ").replace("%", " ")
+        response_api = requests.get(
+            f'https://en.pronouns.page/api/terms/search/{http_safe_term}'
+        ).text
+        data = json.loads(response_api)
+        return data
+
+    @override
+    async def get_autocomplete(self, current: str) -> set[str]:
+        data = await self._get_api_response(current)
+        # find exact results online
+        if len(data) == 0:
+            return set()
+
+        terms = set()
+        for item in data:
+            synonyms = item['term'].split("|")
+            if simplify(current) in simplify(synonyms):
+                terms.add(synonyms[0])
+
+        return terms
+
+    @override
+    async def construct_response(self, term: str) -> None:
+        self._term = term
+        data = await self._get_api_response(term)
+        if len(data) == 0:
+            return
+
+        search = await self._clean_pronouns_page_response_links(data)
+        self._result_count = len(search)
+        results = await self._select_exact_items(search, term)
+
+        if len(results) > 0:
+            try:
+                self._result_str = await self._get_result_string(
+                    results, search, term)
+            except OverflowError as ex:
+                self._character_overflow = True
+                self._result_str = str(ex)
+            return
+
+        # if search doesn't exactly match with a result / synonym
+        start_string = (
+            f"I found {len(search)} result{'s' * (len(results) != 1)} for "
+            f"'{term}' on en.pronouns.page! ")
+        if len(search) > 25:
+            self._result_str = await self._get_expand_big_search_string(
+                start_string, search)
+            self._expand_search = True
+        elif len(search) > 2:
+            self._result_str = await self._get_expand_medium_search_string(
+                start_string, search)
+            self._expand_search = True
+        elif len(search) > 0:
+            start_string += "\n"
+            for item in search:
+                start_string += (f"> **{', '.join(item['term'].split('|'))}:**"
+                                 f" {item['definition']}\n")
+            self._result_str = start_string
+
+        self.has_response = True
+
+    @override
+    async def send_response(
+            self,
+            itx: discord.Interaction[Bot],
+            public: bool
+    ) -> None:
+        itx.followup: discord.Webhook  # type: ignore
+        assert (self.has_response
+                and self._result_str is not None
+                and self._term is not None)
+        msg_length = len(self._result_str)
+        if msg_length > 1999:
+            more_info_link = ("https://en.pronouns.page/dictionary/terminology"
+                              f"?filter={self._term.lower()}")
+            self._result_str = (
+                f"Your search ('{self._term}') returned too many results "
+                f"({self._result_count} in total!) (discord has a "
+                f"2000-character message length, and this message was "
+                f"{msg_length} characters D:). Please search more "
+                f"specifically.\n Here is a link for expanded info on "
+                f"each term: <{more_info_link}>")
+            public = False
+
+        if self._expand_search:
+            public = False
+        if self._character_overflow:
+            public = False
+
+        await itx.followup.send(
+            self._result_str,
+            ephemeral=not public,
+            suppress_embeds=True,
+            allowed_mentions=discord.AllowedMentions.none()
+        )
+
+    @override
+    async def handle_no_response(
+            self,
+            itx: discord.Interaction[Bot],
+            term: str
+    ) -> None:
+        itx.followup: discord.Webhook  # type: ignore
+        await itx.followup.send(
+            f"I didn't find any results for '{term}' on en.pronouns.page",
+            ephemeral=True,
+            suppress_embeds=True,
+            allowed_mentions=discord.AllowedMentions.none()
+        )
diff --git a/extensions/termdictionary/dictionaries/UrbanDictionary.py b/extensions/termdictionary/dictionaries/UrbanDictionary.py
new file mode 100644
index 0000000..9786fd5
--- /dev/null
+++ b/extensions/termdictionary/dictionaries/UrbanDictionary.py
@@ -0,0 +1,147 @@
+import math
+
+import discord
+
+from datetime import datetime
+import json
+import requests
+from typing import override
+
+from extensions.termdictionary.dictionaries import DictionaryBase
+from extensions.termdictionary.dictionaries.objects.UrbanDictionaryEntry import \
+    UrbanDictionaryEntry
+from extensions.termdictionary.views import UrbanDictionaryPageView
+from resources.customs import Bot
+
+
+class UrbanDictionary(DictionaryBase):
+    def __init__(self):
+        super().__init__()
+        self._pages: list[discord.Embed] | None = None
+
+    @staticmethod
+    def _calculate_post_score(result: UrbanDictionaryEntry) -> float:
+        # Based on the Wilson score interval:
+        #  https://stackoverflow.com/a/10029645
+        upvotes = result["thumbs_up"]
+        downvotes = result["thumbs_down"]
+        z = 1.96  # 95% confidence
+
+        votes = upvotes + downvotes
+        if votes == 0:
+            # Avoid division by zero
+            return 0
+        phat = upvotes / votes
+
+        score = (
+            (phat
+             + (z * z) / (2 * votes)
+             - z * math.sqrt((phat * (1 - phat) + z * z / (4 * votes))
+                             / votes)
+             )
+            / (1 + z * z / votes)
+        )
+        return score
+
+    @staticmethod
+    async def _get_urban_dictionary_pages(data) -> list[discord.Embed]:
+        data = sorted(
+            data,
+            key=lambda r: UrbanDictionary._calculate_post_score(r),
+            reverse=True  # sort from highest to lowest
+        )
+
+        pages = []
+        for result in data:
+            embed = discord.Embed(
+                title=f"__{result['word'].capitalize()}__",
+                description=result['definition'],
+                url=result['permalink'],
+                color=8481900,
+            )
+            post_date = int(
+                datetime.fromisoformat(result['written_on']).timestamp()
+            )
+
+            if len(result['example']) > 800:
+                result['example'] = (result['example'][:800]
+                                     + "... (shortened due to size)")
+            embed.add_field(
+                name="Example",
+                value=f"{result['example']}\n\n"
+                      f"{result['thumbs_up']}:thumbsup: "
+                      f":thumbsdown: {result['thumbs_down']}\n"
+                      f"Sent by {result['author']} "
+                      f"on  "
+                      f"at  ()",
+                inline=False
+            )
+            pages.append(embed)
+        return pages
+
+    @staticmethod
+    async def _get_api_response(current) -> list[UrbanDictionaryEntry]:
+        params = {"term": current}
+        response_api = requests.get(
+            'https://api.urbandictionary.com/v0/define',
+            params=params
+        ).text
+        data: dict[str, list[UrbanDictionaryEntry]] = json.loads(response_api)
+        return data['list']  # empty responses have {"list":[]}
+
+    @override
+    async def get_autocomplete(self, current: str) -> set[str]:
+        data = await self._get_api_response(current)
+        if len(data) == 0:
+            return set()
+
+        terms = set()
+        for result in data:
+            terms.add(result["word"].capitalize() + " [UD]")
+        return terms
+
+    @override
+    async def construct_response(self, term: str) -> None:
+        data = await self._get_api_response(term)
+        if len(data) == 0:
+            return
+
+        self._pages = await self._get_urban_dictionary_pages(data)
+        self.has_response = True
+
+    @override
+    async def send_response(
+            self,
+            itx: discord.Interaction[Bot],
+            public: bool
+    ) -> None:
+        assert (self.has_response
+                and self._pages is not None)
+
+        embed = self._pages[0]
+        embed.set_footer(text=f"page: 1 / {len(self._pages)}")
+
+        view = UrbanDictionaryPageView(self._pages, timeout=90)
+        await itx.followup.send(
+            f"I found the following `{len(self._pages)}` results on "
+            f"urbandictionary.com: ",
+            embed=embed,
+            view=view,
+            ephemeral=True,
+        )
+        await view.wait()
+        await itx.edit_original_response(view=None)
+
+    @override
+    async def handle_no_response(
+            self,
+            itx: discord.Interaction[Bot],
+            term: str
+    ) -> None:
+        itx.followup: discord.Webhook  # type: ignore
+        await itx.followup.send(
+            f"I didn't find any results for '{term}' on urbandictionary.com",
+            ephemeral=True,
+            suppress_embeds=True,
+            allowed_mentions=discord.AllowedMentions.none(),
+        )
diff --git a/extensions/termdictionary/dictionaries/__init__.py b/extensions/termdictionary/dictionaries/__init__.py
new file mode 100644
index 0000000..a510873
--- /dev/null
+++ b/extensions/termdictionary/dictionaries/__init__.py
@@ -0,0 +1,12 @@
+__all__ = [
+    "CustomDictionary",
+    "DictionaryApiDictionary",
+    "DictionaryBase",
+    "PronounsPageDictionary",
+]
+
+
+from .CustomDictionary import CustomDictionary
+from .DictionaryApiDictionary import DictionaryApiDictionary
+from .DictionaryBase import DictionaryBase
+from .PronounsPageDictionary import PronounsPageDictionary
diff --git a/extensions/termdictionary/dictionary_data.py b/extensions/termdictionary/dictionaries/objects/CustomDictionaryEntry.py
similarity index 68%
rename from extensions/termdictionary/dictionary_data.py
rename to extensions/termdictionary/dictionaries/objects/CustomDictionaryEntry.py
index 660f613..734ad34 100644
--- a/extensions/termdictionary/dictionary_data.py
+++ b/extensions/termdictionary/dictionaries/objects/CustomDictionaryEntry.py
@@ -1,7 +1,7 @@
 from typing import TypedDict
 
 
-class DictionaryData(TypedDict):
+class CustomDictionaryEntry(TypedDict):
     term: str
     definition: str
     synonyms: list[str]
diff --git a/extensions/termdictionary/dictionaries/objects/DictionaryApiEntry.py b/extensions/termdictionary/dictionaries/objects/DictionaryApiEntry.py
new file mode 100644
index 0000000..9a00743
--- /dev/null
+++ b/extensions/termdictionary/dictionaries/objects/DictionaryApiEntry.py
@@ -0,0 +1,77 @@
+import typing
+
+import discord
+
+
+class MeaningDefinitions(typing.TypedDict):
+    definition: str
+    synonyms: list[str]
+    antonyms: list[str]
+
+
+class EntryMeanings(typing.TypedDict):
+    partOfSpeech: str  # think 'Noun', 'Verb', 'Interjection'
+    definitions: list[MeaningDefinitions]
+    synonyms: list[str]
+    antonyms: list[str]
+
+
+class DictionaryApiEntry(typing.TypedDict):
+    word: str
+    meanings: list[EntryMeanings]  # each EntryMeanings.partOfSpeech is unique
+    sourceUrls: list[str]
+
+
+DetailedTermPage = tuple[str, dict[str, list[str]]]
+
+
+def term_page_to_embed(page: DetailedTermPage) -> discord.Embed:
+    """
+    Convert and format a term page into a Discord embed.
+
+    Each section's values are prefixed with line IDs and displayed in
+    a field. If a section exceeds character limits, it is truncated.
+
+    :param page: The page to format.
+    :return: The formatted embed matching the given term page.
+    """
+    word, sections = page
+    embed = discord.Embed(title=f"__{word.capitalize()}__",
+                          color=8481900)
+
+    line_id = 0
+    for section_name, section_values in sections.items():
+        section_text = ""
+        for value in section_values:
+            section_text += f"{line_id}-{value}\n"
+            line_id += 1
+        if len(section_text) > 995:
+            # limit to 1024 chars in Value field
+            section_text = section_text[:995] + "... (shortened due to size)"
+
+        embed.add_field(name=section_name,
+                        value=section_text or "null",
+                        # ^ prevent possible crash from empty field.
+                        #  idk if this actually happens, but still.
+                        inline=False)
+
+    return embed
+
+
+def get_term_lines(page: DetailedTermPage) -> list[tuple[str, str]]:
+    """
+    Get a list of lines from a term page.
+
+    :param page: The page to get lines for.
+    :return: A list of tuples of the section a line came from, and the
+     line itself.
+    """
+    word, sections = page
+    lines: list[tuple[str, str]] = []
+    for section_name, section_values in sections.items():
+        for value in section_values:
+            lines.append((
+                section_name,
+                f"{len(lines)}-{value}"
+            ))
+    return lines
diff --git a/extensions/termdictionary/dictionaries/objects/PronounsPageEntry.py b/extensions/termdictionary/dictionaries/objects/PronounsPageEntry.py
new file mode 100644
index 0000000..e30a62f
--- /dev/null
+++ b/extensions/termdictionary/dictionaries/objects/PronounsPageEntry.py
@@ -0,0 +1,37 @@
+from typing import TypedDict, Required, Any
+
+
+class PronousPageLocaleEntry(TypedDict, total=False):
+    # Mostly same as PronounsPageEntry. Only missing `versions`
+    id: str
+    term: Required[str]
+    original: str
+    definition: Required[str]
+    locale: str  # everything except 'en' (English)
+    approved: int
+    base_id: Any
+    author_id: str
+    deleted: int
+    flags: str
+    category: str
+    images: str
+    key: str
+    author: str
+
+
+class PronounsPageEntry(TypedDict, total=False):
+    id: str
+    term: Required[str]
+    original: str
+    definition: Required[str]
+    locale: str  # by default just 'en' (English) I think
+    approved: int
+    base_id: Any  # no clue what this is supposed to be
+    author_id: str
+    deleted: int
+    flags: str  # "[]"
+    category: str
+    images: str
+    key: str
+    author: str
+    versions: list[PronousPageLocaleEntry]
diff --git a/extensions/termdictionary/dictionaries/objects/UrbanDictionaryEntry.py b/extensions/termdictionary/dictionaries/objects/UrbanDictionaryEntry.py
new file mode 100644
index 0000000..f8387e8
--- /dev/null
+++ b/extensions/termdictionary/dictionaries/objects/UrbanDictionaryEntry.py
@@ -0,0 +1,14 @@
+from typing import TypedDict
+
+
+class UrbanDictionaryEntry(TypedDict):
+    definition: str
+    permalink: str
+    thumbs_up: int
+    author: str
+    word: str
+    defid: int
+    current_vote: str
+    written_on: str  # ISO YYYY-mm-dd T HH:MM:SS.fff Z
+    example: str
+    thumbs_down: int
diff --git a/extensions/termdictionary/dictionaries/objects/__init__.py b/extensions/termdictionary/dictionaries/objects/__init__.py
new file mode 100644
index 0000000..b84dfa9
--- /dev/null
+++ b/extensions/termdictionary/dictionaries/objects/__init__.py
@@ -0,0 +1,17 @@
+__all__ = [
+    "CustomDictionaryEntry",
+    "PronounsPageEntry",
+    "DictionaryApiEntry",
+    "DetailedTermPage",
+    "term_page_to_embed",
+    "get_term_lines"
+]
+
+from .CustomDictionaryEntry import CustomDictionaryEntry
+from .PronounsPageEntry import PronounsPageEntry
+from .DictionaryApiEntry import (
+    DictionaryApiEntry,
+    DetailedTermPage,
+    term_page_to_embed,
+    get_term_lines
+)
diff --git a/extensions/termdictionary/modals/dictionaryapi_sendpagemodal.py b/extensions/termdictionary/modals/dictionaryapi_sendpagemodal.py
index 28ae749..f41eefd 100644
--- a/extensions/termdictionary/modals/dictionaryapi_sendpagemodal.py
+++ b/extensions/termdictionary/modals/dictionaryapi_sendpagemodal.py
@@ -4,35 +4,39 @@
 
 
 class DictionaryAPISendPageModal(discord.ui.Modal, title="Share single dictionary entry?"):
-    def __init__(self, entries, timeout=None):
+    def __init__(self, max_page, timeout=None):
         super().__init__()
-        self.value = None
+        self.succeeded: bool = False
         self.timeout = timeout
-        self.entries = entries
-        self.id = None
+        self.max_page = max_page
+        self.line: int | None = None
         self.question_text = discord.ui.TextInput(label='Entry index',
-                                                  placeholder=f"[A number from 0 to {len(entries) - 1} ]",
+                                                  placeholder=f"[A number from 0 to {max_page} ]",
                                                   # style=discord.TextStyle.short,
                                                   # required=True
                                                   )
         self.add_item(self.question_text)
 
     async def on_submit(self, itx: discord.Interaction[Bot]):
-        self.value = 9  # failed; placeholder
         try:
-            self.id = int(self.question_text.value)
+            self.line = int(self.question_text.value)
         except ValueError:
             await itx.response.send_message(
-                content=f"Couldn't send entry: '{self.question_text.value}' is not an integer. "
-                        "It has to be an index number from an entry in the /dictionary response.",
-                ephemeral=True)
+                content=f"Couldn't send entry: '{self.question_text.value}' "
+                        f"is not an integer. It has to be an index number "
+                        f"from an entry in the /dictionary response.",
+                ephemeral=True
+            )
             return
-        if self.id < 0 or self.id >= len(self.entries):
+        if self.line < 0 or self.line > self.max_page:
             await itx.response.send_message(
-                content=f"Couldn't send intry: '{self.id}' is not a possible index value for your dictionary entry. "
-                        "It has to be an index number from an entry in the /dictionary response.",
-                ephemeral=True)
+                content=f"Couldn't send intry: '{self.id}' is not a possible "
+                        f"index value for your dictionary entry. It has to "
+                        f"be an index number from an entry in the /dictionary "
+                        f"response.",
+                ephemeral=True
+            )
             return
-        self.value = 1  # succeeded
+        self.succeeded = True
         await itx.response.send_message("Sending item...", ephemeral=True, delete_after=8)
         self.stop()
diff --git a/extensions/termdictionary/utils.py b/extensions/termdictionary/utils.py
new file mode 100644
index 0000000..61bc54b
--- /dev/null
+++ b/extensions/termdictionary/utils.py
@@ -0,0 +1,16 @@
+from typing import TypeVar
+
+
+del_separators_table = str.maketrans({" ": "", "-": "", "_": ""})
+
+
+StringOrStrings = TypeVar("StringOrStrings", str, list[str])
+
+
+def simplify(query: StringOrStrings) -> StringOrStrings:
+    if type(query) is str:
+        return query.lower().translate(del_separators_table)
+    if type(query) is list:
+        return [text.lower().translate(del_separators_table)
+                for text in query]
+    raise NotImplementedError()
diff --git a/extensions/termdictionary/views/__init__.py b/extensions/termdictionary/views/__init__.py
index 7a8729c..9ccfc00 100644
--- a/extensions/termdictionary/views/__init__.py
+++ b/extensions/termdictionary/views/__init__.py
@@ -1,4 +1,4 @@
-__all__ = ['DictionaryApi_PageView', 'UrbanDictionary_PageView']
+__all__ = ['DictionaryapiPageview', 'UrbanDictionaryPageView']
 
-from extensions.termdictionary.views.dictionaryapi_pageview import DictionaryApi_PageView
-from extensions.termdictionary.views.urbandictionary_pageview import UrbanDictionary_PageView
+from extensions.termdictionary.views.dictionaryapi import DictionaryapiPageview
+from extensions.termdictionary.views.urbandictionary import UrbanDictionaryPageView
diff --git a/extensions/termdictionary/views/dictionaryapi.py b/extensions/termdictionary/views/dictionaryapi.py
new file mode 100644
index 0000000..2b84ab7
--- /dev/null
+++ b/extensions/termdictionary/views/dictionaryapi.py
@@ -0,0 +1,88 @@
+from typing import override
+
+import discord
+
+from extensions.termdictionary.dictionaries.objects import (
+    term_page_to_embed,
+    get_term_lines
+)
+from extensions.termdictionary.modals import DictionaryAPISendPageModal
+from resources.customs import Bot
+from resources.views.generics import PageView
+
+
+DetailedTermPage = tuple[str, dict[str, list[str]]]
+
+
+class DictionaryapiPageview(PageView):
+    def __init__(
+            self,
+            pages: list[DetailedTermPage],
+            timeout=90
+    ):
+        super().__init__(
+            starting_page=0,
+            max_page_index=len(pages),
+            timeout=timeout,
+        )
+        self.timeout = timeout
+        self.pages = pages
+
+    @override
+    async def update_page(
+            self, itx: discord.Interaction[Bot], view: PageView
+    ) -> None:
+        embed = term_page_to_embed(self.pages[self.page])
+        embed.set_footer(
+            text=f"page: {self.page + 1} / {self.max_page_index + 1}")
+
+        await itx.response.edit_message(
+            embed=embed,
+            view=view
+        )
+
+    @discord.ui.button(label='Send publicly', style=discord.ButtonStyle.gray)
+    async def send_publicly(
+            self,
+            itx: discord.Interaction[Bot],
+            _button: discord.ui.Button
+    ):
+        embed = term_page_to_embed(self.pages[self.page])
+        await itx.followup.send(
+            f"{itx.user.mention} shared a dictionary entry!",
+            embed=embed,
+            ephemeral=False,
+            allowed_mentions=discord.AllowedMentions.none()
+        )
+        await itx.response.edit_message(
+            content="Sent successfully!", embed=None)
+        self.stop()
+
+    @discord.ui.button(label='Send one entry', style=discord.ButtonStyle.gray)
+    async def send_single_entry(
+            self,
+            itx: discord.Interaction[Bot],
+            _button: discord.ui.Button
+    ):
+        term_lines = get_term_lines(self.pages[self.page])
+
+        max_page_index = len(term_lines) - 1
+        send_one_modal = DictionaryAPISendPageModal(max_page_index)
+        await itx.response.send_modal(send_one_modal)
+        await send_one_modal.wait()
+        if not send_one_modal.succeeded:
+            return
+        assert send_one_modal.line is not None
+
+        term: str = self.pages[self.page][0]
+        page: tuple[str, str] = term_lines[send_one_modal.line]
+        embed = discord.Embed(title=term, color=8481900)
+        embed.add_field(name=page[0],  # section name
+                        value=page[1],  # line content
+                        inline=False)
+        await itx.followup.send(
+            f"{itx.user.mention} shared a section of a dictionary entry! "
+            f"(item {send_one_modal.line})",
+            embed=embed,
+            allowed_mentions=discord.AllowedMentions.none(),
+        )
diff --git a/extensions/termdictionary/views/dictionaryapi_pageview.py b/extensions/termdictionary/views/dictionaryapi_pageview.py
deleted file mode 100644
index e0433ff..0000000
--- a/extensions/termdictionary/views/dictionaryapi_pageview.py
+++ /dev/null
@@ -1,66 +0,0 @@
-import discord
-
-from extensions.termdictionary.modals import DictionaryAPISendPageModal
-from resources.customs import Bot
-
-
-class DictionaryApi_PageView(discord.ui.View):
-    def __init__(self, pages, pages_detailed, timeout=None):
-        super().__init__()
-        self.value = None
-        self.timeout = timeout
-        self.page = 0
-        self.pages = pages
-        self.pages_detailed = pages_detailed
-
-    @discord.ui.button(label='Previous', style=discord.ButtonStyle.blurple)
-    async def previous(self, itx: discord.Interaction[Bot], _button: discord.ui.Button):
-        self.page -= 1
-        if self.page < 0:
-            self.page = len(self.pages) - 1
-        embed = self.pages[self.page]
-        embed.set_footer(text="page: " + str(self.page + 1) + " / " + str(int(len(self.pages))))
-        await itx.response.edit_message(embed=embed)
-
-    @discord.ui.button(label='Next', style=discord.ButtonStyle.blurple)
-    async def next(self, itx: discord.Interaction[Bot], _button: discord.ui.Button):
-        self.page += 1
-        if self.page >= (len(self.pages)):
-            self.page = 0
-        embed = self.pages[self.page]
-        embed.set_footer(text="page: " + str(self.page + 1) + " / " + str(int(len(self.pages))))
-        try:
-            await itx.response.edit_message(embed=embed)
-        except discord.errors.HTTPException:
-            self.page -= 1
-            await itx.response.send_message("This is the last page, you can't go to a next page!",
-                                            ephemeral=True)
-
-    @discord.ui.button(label='Send publicly', style=discord.ButtonStyle.gray)
-    async def send_publicly(self, itx: discord.Interaction[Bot], _button: discord.ui.Button):
-        self.value = 1
-        embed = self.pages[self.page]
-        await itx.response.edit_message(content="Sent successfully!", embed=None)
-        await itx.followup.send(f"{itx.user.mention} shared a dictionary entry!", embed=embed,
-                                ephemeral=False, allowed_mentions=discord.AllowedMentions.none())
-        self.stop()
-
-    @discord.ui.button(label='Send one entry', style=discord.ButtonStyle.gray)
-    async def send_single_entry(self, itx: discord.Interaction[Bot], _button: discord.ui.Button):
-        self.value = 2
-
-        send_one = DictionaryAPISendPageModal(self.pages_detailed[self.page])
-        await itx.response.send_modal(send_one)
-        await send_one.wait()
-        if send_one.value in [None, 9]:
-            pass
-        else:
-            # # pages_detailed = [ [result_id: int,   term: str,   type: str,   value: str],    [...], [...] ]
-            page = self.pages_detailed[self.page][send_one.id]
-            # # page = [result_id: int,   term: str,   type: str,   value: str]
-            embed = discord.Embed(color=8481900, title=page[1])
-            embed.add_field(name=page[2],
-                            value=page[3],
-                            inline=False)
-            await itx.followup.send(f"{itx.user.mention} shared a section of a dictionary entry! (item {page[0]})",
-                                    embed=embed, allowed_mentions=discord.AllowedMentions.none())
diff --git a/extensions/termdictionary/views/urbandictionary.py b/extensions/termdictionary/views/urbandictionary.py
new file mode 100644
index 0000000..4fbfb62
--- /dev/null
+++ b/extensions/termdictionary/views/urbandictionary.py
@@ -0,0 +1,25 @@
+from typing import override
+
+import discord
+
+from resources.customs import Bot
+from resources.views.generics import PageView
+
+
+class UrbanDictionaryPageView(PageView):
+    def __init__(self, pages: list[discord.Embed], timeout=None):
+        super().__init__(
+            starting_page=0,
+            max_page_index=len(pages),
+            timeout=timeout
+        )
+        self.timeout = timeout
+        self.pages = pages
+
+    @override
+    async def update_page(self, itx: discord.Interaction[Bot], view: PageView):
+        itx.response: discord.InteractionResponse[Bot]  # type: ignore
+        await itx.response.edit_message(
+            embed=self.pages[self.page],
+            view=view
+        )
diff --git a/extensions/termdictionary/views/urbandictionary_pageview.py b/extensions/termdictionary/views/urbandictionary_pageview.py
deleted file mode 100644
index f60df01..0000000
--- a/extensions/termdictionary/views/urbandictionary_pageview.py
+++ /dev/null
@@ -1,36 +0,0 @@
-import discord
-
-from resources.customs import Bot
-
-
-class UrbanDictionary_PageView(discord.ui.View):
-    def __init__(self, pages, timeout=None):
-        # todo: just make this a PageView
-        super().__init__()
-        self.value = None
-        self.timeout = timeout
-        self.page = 0
-        self.pages = pages
-
-    @discord.ui.button(label='Previous', style=discord.ButtonStyle.blurple)
-    async def previous(self, itx: discord.Interaction[Bot], _button: discord.ui.Button):
-        self.page -= 1
-        if self.page < 0:
-            self.page = len(self.pages) - 1
-        embed = self.pages[self.page]
-        embed.set_footer(text="page: " + str(self.page + 1) + " / " + str(int(len(self.pages))))
-        await itx.response.edit_message(embed=embed)
-
-    @discord.ui.button(label='Next', style=discord.ButtonStyle.blurple)
-    async def next(self, itx: discord.Interaction[Bot], _button: discord.ui.Button):
-        self.page += 1
-        if self.page >= (len(self.pages)):
-            self.page = 0
-        embed = self.pages[self.page]
-        embed.set_footer(text="page: " + str(self.page + 1) + " / " + str(int(len(self.pages))))
-        try:
-            await itx.response.edit_message(embed=embed)
-        except discord.errors.HTTPException:
-            self.page -= 1
-            await itx.response.send_message("This is the last page, you can't go to a next page!",
-                                            ephemeral=True)

From aed60975eb6c1570d1e271db16c9d84aa732b312 Mon Sep 17 00:00:00 2001
From: MysticMia <65396931+MysticMia@users.noreply.github.com>
Date: Fri, 23 May 2025 05:48:40 +0200
Subject: [PATCH 18/26] Make API calls async (aiohttp vs requests), so they can
 be run async.

This also lets the autocomplete ignore delayed responses with a timeout.
Also rename CustomDictionary._get_database_response to be consistent with the other dictionary classes.
And catch an error when a view times out but the original message was not found (deleted).
---
 .../termdictionary/cogs/termdictionary.py     | 111 +++++++++++-------
 .../dictionaries/CustomDictionary.py          |   9 +-
 .../dictionaries/DictionaryApiDictionary.py   |  33 ++++--
 .../dictionaries/PronounsPageDictionary.py    |  21 ++--
 .../dictionaries/UrbanDictionary.py           |  26 ++--
 requirements.txt                              |   1 +
 6 files changed, 127 insertions(+), 74 deletions(-)

diff --git a/extensions/termdictionary/cogs/termdictionary.py b/extensions/termdictionary/cogs/termdictionary.py
index 7b4fb3f..db52c50 100644
--- a/extensions/termdictionary/cogs/termdictionary.py
+++ b/extensions/termdictionary/cogs/termdictionary.py
@@ -1,9 +1,10 @@
-import asyncio
-
 import discord
 import discord.app_commands as app_commands
 import discord.ext.commands as commands
 
+import aiohttp
+import asyncio
+
 from extensions.termdictionary.dictionaries import (
     CustomDictionary,
     DictionaryApiDictionary,
@@ -14,55 +15,30 @@
     UrbanDictionary
 from extensions.termdictionary.dictionary_sources import DictionarySources
 from extensions.termdictionary.utils import simplify
+
 from resources.checks import is_staff_check  # for staff dictionary commands
 from resources.customs import Bot
 # for logging custom dictionary changes, or when a search query returns nothing or >2000 characters
 from resources.utils.utils import log_to_guild
 
 
-dictionary_sources: list[tuple[DictionarySources, DictionaryBase]] = [
-    # (d.CustomDictionary, CustomDictionary(client=itx.client)),
-    (DictionarySources.PronounsPage, PronounsPageDictionary()),
-    (DictionarySources.DictionaryApi, DictionaryApiDictionary()),
-    (DictionarySources.UrbanDictionary, UrbanDictionary()),
-]
-
-
-async def dictionary_autocomplete(itx: discord.Interaction[Bot], current: str):
-    if current == '':
-        return []
-
-    # select the right sources
-    sources = dictionary_sources[:]
-    if itx.namespace.source in DictionarySources:
-        if type(itx.namespace.source) is not DictionarySources:
-            source = DictionarySources(itx.namespace.source)
-        else:
-            source = itx.namespace.source
-
-        sources = [
-            dictionary for dictionary in dictionary_sources[:]
-            if dictionary[0] == source
-        ]
-
-    # fetch autocompletion results
-    tasks = [
-        source.get_autocomplete(current)
-        for _, source in sources
-    ]
-    results: list[set[str]] = await asyncio.gather(*tasks)
-    terms: set[str] = set.union(*results)
-
-    # respond with found autocompletion terms
-    return [
-        app_commands.Choice(name=term, value=term)
-        for term in terms
-    ][:7]  # limit choices to the first 7
-
-
 class TermDictionary(commands.Cog):
     def __init__(self):
-        pass
+        self._session = aiohttp.ClientSession()
+        self._dictionary_sources: list[
+            tuple[DictionarySources, DictionaryBase]
+        ] = [
+            # (d.CustomDictionary, CustomDictionary(client=itx.client)),
+            (DictionarySources.PronounsPage,
+             PronounsPageDictionary(self._session)),
+            (DictionarySources.DictionaryApi,
+             DictionaryApiDictionary(self._session)),
+            (DictionarySources.UrbanDictionary,
+             UrbanDictionary(self._session)),
+        ]
+
+    async def cog_unload(self):
+        await self._session.close()
 
     @app_commands.command(name="dictionary",
                           description="Look for terms in online dictionaries!")
@@ -95,7 +71,6 @@ def __init__(self):
             value=DictionarySources.UrbanDictionary.value
         ),
     ])
-    @app_commands.autocomplete(term=dictionary_autocomplete)
     async def dictionary(
             self,
             itx: discord.Interaction[Bot],
@@ -106,7 +81,7 @@ async def dictionary(
         itx.response: discord.InteractionResponse[Bot]  # type: ignore
         await itx.response.defer(ephemeral=public)
 
-        sources = dictionary_sources[:]
+        sources = self._dictionary_sources[:]
         if source != DictionarySources.All:
             # filter dictionary sources to only the enabled ones.
             sources = [
@@ -145,6 +120,52 @@ async def dictionary(
             f"the {cmd_dictionary} command ({cmd_define})"
         )
 
+    @dictionary.autocomplete("term")
+    async def dict_autocomplete_helper(
+            self,
+            itx: discord.Interaction[Bot],
+            current: str
+    ):
+        if current == '':
+            return []
+
+        # select the right sources
+        sources = self._dictionary_sources[:]
+        if itx.namespace.source in DictionarySources:
+            if type(itx.namespace.source) is not DictionarySources:
+                source = DictionarySources(itx.namespace.source)
+            else:
+                source = itx.namespace.source
+
+            sources = [
+                dictionary for dictionary in self._dictionary_sources[:]
+                if dictionary[0] == source
+            ]
+
+        # fetch autocompletion results
+        async def fetch_with_timeout(dictionary: DictionaryBase):
+            try:
+                return await asyncio.wait_for(
+                    dictionary.get_autocomplete(current),
+                    timeout=2.5,
+                )
+            except asyncio.TimeoutError:
+                return set()
+
+        tasks = [
+            fetch_with_timeout(source)
+            for _, source in sources
+        ]
+        results: list[set[str]] = await asyncio.gather(*tasks)
+
+        terms: set[str] = set.union(*results)
+
+        # respond with found autocompletion terms
+        return [
+            app_commands.Choice(name=term, value=term)
+            for term in terms
+        ][:7]  # limit choices to the first 7
+
     admin = app_commands.Group(name='dictionary_staff', description='Change custom entries in the dictionary')
 
     @app_commands.check(is_staff_check)
diff --git a/extensions/termdictionary/dictionaries/CustomDictionary.py b/extensions/termdictionary/dictionaries/CustomDictionary.py
index 3d91f81..fad0c5d 100644
--- a/extensions/termdictionary/dictionaries/CustomDictionary.py
+++ b/extensions/termdictionary/dictionaries/CustomDictionary.py
@@ -18,7 +18,10 @@ def __init__(self, client):
         self._long_line: bool = False
         self._character_overflow: bool = False
 
-    async def _get_database_response(self, term: str) -> list[tuple[str, str]]:
+    async def _get_api_response(
+            self,
+            term: str
+    ) -> list[tuple[str, str]]:
         """Helper to get custom dictionary data from the database."""
         collection = self.client.async_rina_db["termDictionary"]
         query = {"synonyms": term.lower()}
@@ -33,14 +36,14 @@ async def _get_database_response(self, term: str) -> list[tuple[str, str]]:
 
     @override
     async def get_autocomplete(self, current: str) -> set[str]:
-        results = await self._get_database_response(current)
+        results = await self._get_api_response(current)
         if not results:
             return set()
         return set(current for current, _ in results)
 
     @override
     async def construct_response(self, term: str) -> None:
-        results = await self._get_database_response(term)
+        results = await self._get_api_response(term)
         if not results:
             return
         self.has_response = True
diff --git a/extensions/termdictionary/dictionaries/DictionaryApiDictionary.py b/extensions/termdictionary/dictionaries/DictionaryApiDictionary.py
index 3bb9764..f72a7b9 100644
--- a/extensions/termdictionary/dictionaries/DictionaryApiDictionary.py
+++ b/extensions/termdictionary/dictionaries/DictionaryApiDictionary.py
@@ -1,8 +1,7 @@
-
 import discord
 
+import aiohttp
 import json
-import requests
 from typing import override
 import urllib.parse
 
@@ -53,8 +52,9 @@ def _extract_api_data(result):
 
 
 class DictionaryApiDictionary(DictionaryBase):
-    def __init__(self):
+    def __init__(self, session: aiohttp.ClientSession):
         super().__init__()
+        self._session = session
         self._embed: discord.Embed | None = None
         self._view: DictionaryapiPageview | None = None
 
@@ -73,15 +73,26 @@ def _get_pages(
 
         return pages
 
-    @staticmethod
-    async def _get_api_response(term: str) -> list[DictionaryApiEntry] | None:
-        response_api = requests.get(
+    async def _get_api_response(
+            self,
+            term: str
+    ) -> list[DictionaryApiEntry] | None:
+        url = (
             "https://api.dictionaryapi.dev/api/v2/entries/en/"
             + urllib.parse.quote(term.lower(), safe=())
             # ^ slashes aren't safe
-        ).text
+        )
+        async with self._session.get(url) as response:
+            response_api = await response.text()
+
         try:
-            return json.loads(response_api)
+            data = json.loads(response_api)
+            if type(data) is dict:
+                # should be a list of entries
+                return None
+
+            assert type(data) is list
+            return data
         except json.decoder.JSONDecodeError:
             return None
 
@@ -127,7 +138,11 @@ async def send_response(
             view=self._view
         )
         await self._view.wait()
-        await itx.edit_original_response(view=None)
+        try:
+            await itx.edit_original_response(view=None)
+        except discord.NotFound:
+            # message was deleted?
+            pass
 
     @override
     async def handle_no_response(
diff --git a/extensions/termdictionary/dictionaries/PronounsPageDictionary.py b/extensions/termdictionary/dictionaries/PronounsPageDictionary.py
index 6dfa077..5f24e42 100644
--- a/extensions/termdictionary/dictionaries/PronounsPageDictionary.py
+++ b/extensions/termdictionary/dictionaries/PronounsPageDictionary.py
@@ -1,9 +1,8 @@
-
 import discord
 
+import aiohttp
 import json
 import re  # to parse and remove https:/pronouns.page/ in-text page linking
-import requests
 from typing import override
 
 from .DictionaryBase import DictionaryBase
@@ -25,8 +24,10 @@ class PronounsPageDictionary(DictionaryBase):
     :ivar _character_overflow: Internal flag to track if response exceeds Discord's 2000-character limit.
     :ivar _expand_search: Boolean indicating whether to expand search to include synonyms/related terms.
     """
-    def __init__(self):
+
+    def __init__(self, session: aiohttp.ClientSession) -> None:
         super().__init__()
+        self._session = session
         self._result_str: str | None = None
         self._result_count = 0
         self._character_overflow = False
@@ -153,12 +154,16 @@ async def _get_result_string(
             raise OverflowError(result_str)
         return result_str
 
-    @staticmethod
-    async def _get_api_response(term) -> list[PronounsPageEntry]:
+    async def _get_api_response(
+            self,
+            term: str
+    ) -> list[PronounsPageEntry]:
         http_safe_term = term.lower().replace("/", " ").replace("%", " ")
-        response_api = requests.get(
-            f'https://en.pronouns.page/api/terms/search/{http_safe_term}'
-        ).text
+        url = f'https://en.pronouns.page/api/terms/search/{http_safe_term}'
+
+        async with self._session.get(url) as response:
+            response_api = await response.text()
+
         data = json.loads(response_api)
         return data
 
diff --git a/extensions/termdictionary/dictionaries/UrbanDictionary.py b/extensions/termdictionary/dictionaries/UrbanDictionary.py
index 9786fd5..5397539 100644
--- a/extensions/termdictionary/dictionaries/UrbanDictionary.py
+++ b/extensions/termdictionary/dictionaries/UrbanDictionary.py
@@ -1,5 +1,6 @@
 import math
 
+import aiohttp
 import discord
 
 from datetime import datetime
@@ -15,8 +16,9 @@
 
 
 class UrbanDictionary(DictionaryBase):
-    def __init__(self):
+    def __init__(self, session: aiohttp.ClientSession):
         super().__init__()
+        self._session = session
         self._pages: list[discord.Embed] | None = None
 
     @staticmethod
@@ -79,13 +81,15 @@ async def _get_urban_dictionary_pages(data) -> list[discord.Embed]:
             pages.append(embed)
         return pages
 
-    @staticmethod
-    async def _get_api_response(current) -> list[UrbanDictionaryEntry]:
-        params = {"term": current}
-        response_api = requests.get(
-            'https://api.urbandictionary.com/v0/define',
-            params=params
-        ).text
+    async def _get_api_response(
+            self,
+            term: str
+    ) -> list[UrbanDictionaryEntry]:
+        params = {"term": term}
+        url = "https://api.urbandictionary.com/v0/define"
+        async with self._session.get(url, params=params) as response:
+            response_api = await response.text()
+
         data: dict[str, list[UrbanDictionaryEntry]] = json.loads(response_api)
         return data['list']  # empty responses have {"list":[]}
 
@@ -130,7 +134,11 @@ async def send_response(
             ephemeral=True,
         )
         await view.wait()
-        await itx.edit_original_response(view=None)
+        try:
+            await itx.edit_original_response(view=None)
+        except discord.NotFound:
+            # message was deleted?
+            pass
 
     @override
     async def handle_no_response(
diff --git a/requirements.txt b/requirements.txt
index 9898eb0..591bc7c 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -7,3 +7,4 @@ apscheduler>=3.11.0
 matplotlib>=3.10.1
 requests>=2.32.3
 pytest>=8.3.5
+aiohttp>=3.9.5

From 1fbdd25e13a998ee5adce25c68803f40f754c2fc Mon Sep 17 00:00:00 2001
From: MysticMia <65396931+MysticMia@users.noreply.github.com>
Date: Fri, 23 May 2025 06:49:13 +0200
Subject: [PATCH 19/26] Ensure all dictionaries work as intended (ephemeral,
 page numbers, buttons).

Also make some async functions sync cuz they don't have any await statements.
---
 .../termdictionary/cogs/termdictionary.py     | 18 ++++-----
 .../dictionaries/DictionaryApiDictionary.py   |  6 ++-
 .../dictionaries/PronounsPageDictionary.py    | 40 +++++++++++++------
 .../dictionaries/UrbanDictionary.py           | 20 +++++++---
 .../objects/DictionaryApiEntry.py             |  2 +-
 .../termdictionary/views/dictionaryapi.py     |  7 +++-
 .../termdictionary/views/urbandictionary.py   |  2 +-
 7 files changed, 64 insertions(+), 31 deletions(-)

diff --git a/extensions/termdictionary/cogs/termdictionary.py b/extensions/termdictionary/cogs/termdictionary.py
index db52c50..214477f 100644
--- a/extensions/termdictionary/cogs/termdictionary.py
+++ b/extensions/termdictionary/cogs/termdictionary.py
@@ -51,23 +51,23 @@ async def cog_unload(self):
                                   "with the rest of the channel? (True=yes)")
     @app_commands.choices(source=[
         discord.app_commands.Choice(
-            name='Search from whichever has an answer',
+            name='All',
             value=DictionarySources.All.value
         ),
+        # discord.app_commands.Choice(
+        #     name='Custom dictionary',
+        #     value=DictionarySources.CustomDictionary.value
+        # ),
         discord.app_commands.Choice(
-            name='Search from custom dictionary',
-            value=DictionarySources.CustomDictionary.value
-        ),
-        discord.app_commands.Choice(
-            name='Search from en.pronouns.page',
+            name='en.pronouns.page',
             value=DictionarySources.PronounsPage.value
         ),
         discord.app_commands.Choice(
-            name='Search from dictionaryapi.dev',
+            name='dictionaryapi.dev',
             value=DictionarySources.DictionaryApi.value
         ),
         discord.app_commands.Choice(
-            name='Search from urbandictionary.com',
+            name='urbandictionary.com',
             value=DictionarySources.UrbanDictionary.value
         ),
     ])
@@ -79,7 +79,7 @@ async def dictionary(
             source: DictionarySources = DictionarySources.All,
     ):
         itx.response: discord.InteractionResponse[Bot]  # type: ignore
-        await itx.response.defer(ephemeral=public)
+        await itx.response.defer(ephemeral=not public)
 
         sources = self._dictionary_sources[:]
         if source != DictionarySources.All:
diff --git a/extensions/termdictionary/dictionaries/DictionaryApiDictionary.py b/extensions/termdictionary/dictionaries/DictionaryApiDictionary.py
index f72a7b9..f7840b7 100644
--- a/extensions/termdictionary/dictionaries/DictionaryApiDictionary.py
+++ b/extensions/termdictionary/dictionaries/DictionaryApiDictionary.py
@@ -40,7 +40,7 @@ def _extract_api_data(result):
     for meaning in result["meanings"]:
         meaning_list = []
         for definition in meaning["definitions"]:
-            meaning_list.append("- " + definition['definition'])
+            meaning_list.append(definition['definition'])
             synonyms.update(definition['synonyms'])
             antonyms.update(definition['antonyms'])
 
@@ -133,6 +133,10 @@ async def send_response(
         assert (self.has_response
                 and self._view is not None
                 and self._embed is not None)
+
+        if public:
+            self._view.delete_extra_buttons()
+
         await itx.followup.send(
             embed=self._embed,
             view=self._view
diff --git a/extensions/termdictionary/dictionaries/PronounsPageDictionary.py b/extensions/termdictionary/dictionaries/PronounsPageDictionary.py
index 5f24e42..a9d21c2 100644
--- a/extensions/termdictionary/dictionaries/PronounsPageDictionary.py
+++ b/extensions/termdictionary/dictionaries/PronounsPageDictionary.py
@@ -35,7 +35,7 @@ def __init__(self, session: aiohttp.ClientSession) -> None:
         self._term: str | None = None
 
     @staticmethod
-    async def _clean_pronouns_page_response_links(
+    def _clean_pronouns_page_response_links(
             data: list[PronounsPageEntry]
     ) -> list[PronounsPageEntry]:
         """
@@ -70,7 +70,7 @@ async def _clean_pronouns_page_response_links(
         return search
 
     @staticmethod
-    async def _get_expand_medium_search_string(result_str, search):
+    def _get_expand_medium_search_string(result_str, search):
         result_str += "Here is a list to make your search more specific:\n"
         results: list[str] = []
         for item in search:
@@ -85,7 +85,7 @@ async def _get_expand_medium_search_string(result_str, search):
         return result_str
 
     @staticmethod
-    async def _get_expand_big_search_string(result_str, search):
+    def _get_expand_big_search_string(result_str, search):
         result_str += "Here is a list to make your search more specific:\n"
         results: list[str] = []
         for item in search:
@@ -97,7 +97,7 @@ async def _get_expand_big_search_string(result_str, search):
         return result_str
 
     @staticmethod
-    async def _select_exact_items(
+    def _select_exact_items(
             search: list[PronounsPageEntry], term
     ) -> list[PronounsPageEntry]:
         """
@@ -117,7 +117,7 @@ async def _select_exact_items(
         return results
 
     @staticmethod
-    async def _get_result_string(
+    def _get_result_string(
             results: list[PronounsPageEntry],
             search: list[PronounsPageEntry],
             term: str
@@ -170,7 +170,7 @@ async def _get_api_response(
     @override
     async def get_autocomplete(self, current: str) -> set[str]:
         data = await self._get_api_response(current)
-        # find exact results online
+        # find exact results online (or synonyms)
         if len(data) == 0:
             return set()
 
@@ -178,7 +178,7 @@ async def get_autocomplete(self, current: str) -> set[str]:
         for item in data:
             synonyms = item['term'].split("|")
             if simplify(current) in simplify(synonyms):
-                terms.add(synonyms[0])
+                terms.add(synonyms[0].capitalize())
 
         return terms
 
@@ -189,14 +189,16 @@ async def construct_response(self, term: str) -> None:
         if len(data) == 0:
             return
 
-        search = await self._clean_pronouns_page_response_links(data)
+        search = self._clean_pronouns_page_response_links(data)
         self._result_count = len(search)
-        results = await self._select_exact_items(search, term)
+        results = self._select_exact_items(search, term)
 
         if len(results) > 0:
+            # there are results that match exactly.
             try:
-                self._result_str = await self._get_result_string(
+                self._result_str = self._get_result_string(
                     results, search, term)
+                self.has_response = True
             except OverflowError as ex:
                 self._character_overflow = True
                 self._result_str = str(ex)
@@ -207,11 +209,11 @@ async def construct_response(self, term: str) -> None:
             f"I found {len(search)} result{'s' * (len(results) != 1)} for "
             f"'{term}' on en.pronouns.page! ")
         if len(search) > 25:
-            self._result_str = await self._get_expand_big_search_string(
+            self._result_str = self._get_expand_big_search_string(
                 start_string, search)
             self._expand_search = True
         elif len(search) > 2:
-            self._result_str = await self._get_expand_medium_search_string(
+            self._result_str = self._get_expand_medium_search_string(
                 start_string, search)
             self._expand_search = True
         elif len(search) > 0:
@@ -265,9 +267,21 @@ async def handle_no_response(
             term: str
     ) -> None:
         itx.followup: discord.Webhook  # type: ignore
+
+        if self._character_overflow:
+            assert self._result_str is not None
+
+            await itx.followup.send(
+                self._result_str[:2000],
+                ephemeral=True,
+                suppress_embeds=True,
+                allowed_mentions=discord.AllowedMentions.none(),
+            )
+            return
+
         await itx.followup.send(
             f"I didn't find any results for '{term}' on en.pronouns.page",
             ephemeral=True,
             suppress_embeds=True,
-            allowed_mentions=discord.AllowedMentions.none()
+            allowed_mentions=discord.AllowedMentions.none(),
         )
diff --git a/extensions/termdictionary/dictionaries/UrbanDictionary.py b/extensions/termdictionary/dictionaries/UrbanDictionary.py
index 5397539..de90386 100644
--- a/extensions/termdictionary/dictionaries/UrbanDictionary.py
+++ b/extensions/termdictionary/dictionaries/UrbanDictionary.py
@@ -1,11 +1,10 @@
-import math
 
-import aiohttp
 import discord
 
+import aiohttp
 from datetime import datetime
 import json
-import requests
+import math
 from typing import override
 
 from extensions.termdictionary.dictionaries import DictionaryBase
@@ -16,6 +15,8 @@
 
 
 class UrbanDictionary(DictionaryBase):
+    term_suffix = " [UD]"
+    
     def __init__(self, session: aiohttp.ClientSession):
         super().__init__()
         self._session = session
@@ -46,7 +47,7 @@ def _calculate_post_score(result: UrbanDictionaryEntry) -> float:
         return score
 
     @staticmethod
-    async def _get_urban_dictionary_pages(data) -> list[discord.Embed]:
+    def _get_urban_dictionary_pages(data) -> list[discord.Embed]:
         data = sorted(
             data,
             key=lambda r: UrbanDictionary._calculate_post_score(r),
@@ -78,6 +79,7 @@ async def _get_urban_dictionary_pages(data) -> list[discord.Embed]:
                       f"at  ()",
                 inline=False
             )
+            embed.set_footer(text=f"page: {len(pages) + 1} / {len(data)}")
             pages.append(embed)
         return pages
 
@@ -101,11 +103,13 @@ async def get_autocomplete(self, current: str) -> set[str]:
 
         terms = set()
         for result in data:
-            terms.add(result["word"].capitalize() + " [UD]")
+            terms.add(result["word"].capitalize() + self.term_suffix)
         return terms
 
     @override
     async def construct_response(self, term: str) -> None:
+        if term.endswith(self.term_suffix):
+            term = term[:-len(self.term_suffix)]
         data = await self._get_api_response(term)
         if len(data) == 0:
             return
@@ -122,6 +126,12 @@ async def send_response(
         assert (self.has_response
                 and self._pages is not None)
 
+        if public:
+            # Remove public defer message to instead send this reply
+            #  privately. (can often contain swears and unexpected info
+            #  that you may not want to send publicly.
+            await itx.delete_original_response()
+
         embed = self._pages[0]
         embed.set_footer(text=f"page: 1 / {len(self._pages)}")
 
diff --git a/extensions/termdictionary/dictionaries/objects/DictionaryApiEntry.py b/extensions/termdictionary/dictionaries/objects/DictionaryApiEntry.py
index 9a00743..9800d85 100644
--- a/extensions/termdictionary/dictionaries/objects/DictionaryApiEntry.py
+++ b/extensions/termdictionary/dictionaries/objects/DictionaryApiEntry.py
@@ -43,7 +43,7 @@ def term_page_to_embed(page: DetailedTermPage) -> discord.Embed:
     for section_name, section_values in sections.items():
         section_text = ""
         for value in section_values:
-            section_text += f"{line_id}-{value}\n"
+            section_text += f"`{line_id}`-{value}\n"
             line_id += 1
         if len(section_text) > 995:
             # limit to 1024 chars in Value field
diff --git a/extensions/termdictionary/views/dictionaryapi.py b/extensions/termdictionary/views/dictionaryapi.py
index 2b84ab7..3da0740 100644
--- a/extensions/termdictionary/views/dictionaryapi.py
+++ b/extensions/termdictionary/views/dictionaryapi.py
@@ -22,11 +22,16 @@ def __init__(
     ):
         super().__init__(
             starting_page=0,
-            max_page_index=len(pages),
+            max_page_index=len(pages) - 1,
             timeout=timeout,
         )
         self.timeout = timeout
         self.pages = pages
+        self._children.append(self._children.pop(1))
+
+    def delete_extra_buttons(self):
+        self._children.pop(3)  # remove last button: send one
+        self._children.pop(0)  # remove first button: public
 
     @override
     async def update_page(
diff --git a/extensions/termdictionary/views/urbandictionary.py b/extensions/termdictionary/views/urbandictionary.py
index 4fbfb62..7651e61 100644
--- a/extensions/termdictionary/views/urbandictionary.py
+++ b/extensions/termdictionary/views/urbandictionary.py
@@ -10,7 +10,7 @@ class UrbanDictionaryPageView(PageView):
     def __init__(self, pages: list[discord.Embed], timeout=None):
         super().__init__(
             starting_page=0,
-            max_page_index=len(pages),
+            max_page_index=len(pages) - 1,
             timeout=timeout
         )
         self.timeout = timeout

From 7adc1526fe5e9c9a34620d12338b461f96cf1e3d Mon Sep 17 00:00:00 2001
From: MysticMia <65396931+MysticMia@users.noreply.github.com>
Date: Fri, 23 May 2025 08:19:36 +0200
Subject: [PATCH 20/26] Set flake default line width to 79 characters, and
 apply to last files.

---
 extensions/addons/cogs/funaddons.py           | 75 ++++++++++++-------
 extensions/addons/cogs/otheraddons.py         |  4 +-
 extensions/addons/cogs/searchaddons.py        | 29 ++++---
 extensions/addons/equaldexregion.py           |  8 +-
 extensions/addons/views/__init__.py           |  4 +-
 extensions/compliments/cogs/compliments.py    |  2 +-
 extensions/customvcs/cogs/vctables.py         |  7 +-
 extensions/emojistats/cogs/emojistats.py      | 12 ---
 extensions/emojistats/database_dicts.py       |  9 ++-
 extensions/qotw/cogs/devrequest.py            |  6 +-
 extensions/reminders/cogs/reminders.py        |  3 +-
 extensions/reminders/views/copyreminder.py    |  3 +-
 extensions/settings/objects/__init__.py       | 17 +++--
 extensions/staffaddons/cogs/staffaddons.py    |  4 +-
 extensions/starboard/cogs/starboard.py        |  6 +-
 .../termdictionary/cogs/termdictionary.py     | 30 ++++++--
 .../dictionaries/PronounsPageDictionary.py    | 15 ++--
 .../dictionaries/UrbanDictionary.py           |  7 +-
 .../dictionaries/objects/__init__.py          |  6 +-
 extensions/termdictionary/modals/__init__.py  |  2 +-
 .../modals/dictionaryapi_sendpagemodal.py     | 27 +++++--
 extensions/termdictionary/views/__init__.py   |  4 +-
 extensions/watchlist/modals/__init__.py       |  2 +-
 resources/utils/__init__.py                   |  7 +-
 resources/views/generics.py                   |  2 +-
 setup.cfg                                     |  2 +-
 26 files changed, 181 insertions(+), 112 deletions(-)

diff --git a/extensions/addons/cogs/funaddons.py b/extensions/addons/cogs/funaddons.py
index 88facbc..291679b 100644
--- a/extensions/addons/cogs/funaddons.py
+++ b/extensions/addons/cogs/funaddons.py
@@ -117,12 +117,12 @@ async def _simplify_roll_output(rolls: list[int]) -> str:
         except KeyError:
             roll_db[roll] = 1
     # order dict by the eyes rolled: {"eyes":"count",1:4,2:1,3:4,4:1}
-    # x.items() gives a list of tuples [(1,4), (2,1), (3,4), (4,1)] that is then sorted b
-    # the first item in the tuple.
-    roll_db = dict(sorted([x for x in roll_db.items()]))
+    # x.items() gives a list of tuples [(1,4), (2,1), (3,4), (4,1)]
+    #  that is then sorted by the first item in the tuple.
+    roll_db = sorted([x for x in roll_db.items()])
     details = "You rolled "
-    for roll in roll_db:
-        details += f"'{roll}'x{roll_db[roll]}, "
+    for roll, count in roll_db:
+        details += f"'{roll}'x{count}, "
     return details
 
 
@@ -227,32 +227,40 @@ async def on_message(self, message: discord.Message):
                     [AttributeKeys.awawawa_emoji])
             await _handle_awawa_reaction(message, awawawa_emoji)
 
-    @app_commands.command(name="roll", description="Roll a die or dice with random chance!")
-    @app_commands.describe(dice="How many dice do you want to roll?",
-                           faces="How many sides does every die have?",
-                           mod="Do you want to add a modifier? (add 2 after rolling the dice)",
-                           advanced="Roll more advanced! example: 1d20+3*2d4. Overwrites dice/faces given; "
-                                    "'help' for more")
+    @app_commands.command(name="roll",
+                          description="Roll a die or dice with random chance!")
+    @app_commands.describe(
+        dice="How many dice do you want to roll?",
+        faces="How many sides does every die have?",
+        mod="Do you want to add a modifier? (add 2 after rolling the dice)",
+        advanced="Roll more advanced! example: 1d20+3*2d4. Overwrites "
+                 "dice/faces given; 'help' for more"
+    )
     async def roll(
-            self, itx: discord.Interaction[Bot],
+            self,
+            itx: discord.Interaction[Bot],
             dice: app_commands.Range[int, 1, 999999],
             faces: app_commands.Range[int, 1, 999999],
-            public: bool = False, mod: int | None = None, advanced: str | None = None
+            public: bool = False,
+            mod: int | None = None,
+            advanced: str | None = None,
     ):
+        itx.response: discord.InteractionResponse[Bot]  # type: ignore
+
         if advanced is None:
             await itx.response.defer(ephemeral=not public)
-            out, delete_original = await _get_dice_roll_output(
+            out, too_long = await _get_dice_roll_output(
                 dice, faces, mod)
-            if delete_original:
+            if too_long:
                 await itx.delete_original_response()
-            await itx.followup.send(out, ephemeral=not public)
+            await itx.followup.send(out, ephemeral=too_long)
         else:
             await itx.response.defer(ephemeral=not public)
             advanced = advanced.replace(" ", "")
             if advanced == "help":
                 cmd_help = itx.client.get_command_mention_with_args(
                     "help", page="112")
-                await itx.response.send(
+                await itx.response.send_message(
                     f"I don't think I ever added a help command... Ping "
                     f"mysticmia for more information about this command, or "
                     f"run {cmd_help} for more information."
@@ -262,8 +270,11 @@ async def roll(
                 if char not in "0123456789d+*-":  # kKxXrR": #!!pf≤≥
                     if public:
                         await itx.delete_original_response()
-                    await itx.followup.send(f"Invalid input! This command doesn't have support for '{char}' yet!",
-                                            ephemeral=True)
+                    await itx.followup.send(
+                        f"Invalid input! This command doesn't have "
+                        f"support for '{char}' yet!",
+                        ephemeral=True,
+                    )
                     return
             _add = advanced.replace('-', '+-').split('+')
             add = [add_section for add_section in _add if len(add_section) > 0]
@@ -273,20 +284,29 @@ async def roll(
                 multiply.append(add_section.split('*'))
             # print("multiply:  ",multiply)
             try:
-                result = [[sum(generate_roll(query)) for query in mult_section] for mult_section in multiply]
+                result = [[sum(generate_roll(query))
+                           for query in mult_section]
+                          for mult_section in multiply]
             except (TypeError, ValueError, OverflowError) as ex:
                 ex_type = ex.__class__.__name__
                 if public:
                     await itx.delete_original_response()
-                await itx.followup.send(f"Wasn't able to roll your dice!\n  {ex_type}: {ex}", ephemeral=True)
+                await itx.followup.send(
+                    f"Wasn't able to roll your dice!\n"
+                    f"  {ex_type}: {ex}",
+                    ephemeral=True,
+                )
                 return
             # print("result:    ",result)
             out = ["Input:  " + advanced]
             if "*" in advanced:
-                out += [' + '.join([' * '.join([str(x) for x in section]) for section in result])]
+                out += [' + '.join([' * '.join([str(x) for x in section])
+                                    for section in result])]
             if "+" in advanced or '-' in advanced:
-                out += [' + '.join([str(_product_of_list(section)) for section in result])]
-            out += [str(sum([_product_of_list(section) for section in result]))]
+                out += [' + '.join([str(_product_of_list(section))
+                                    for section in result])]
+            out += [str(sum([_product_of_list(section)
+                             for section in result]))]
             output = discord.utils.escape_markdown('\n= '.join(out))
             if len(output) >= 1950:
                 output = ("Your result was too long! I couldn't send it. "
@@ -299,5 +319,8 @@ async def roll(
             except discord.errors.NotFound:
                 if public:
                     await itx.delete_original_response()
-                await itx.user.send(f"Couldn't send you the result of your roll because it took too long "
-                                    f"or something. Here you go: \n{output}")
+                await itx.user.send(
+                    f"Couldn't send you the result of your roll because "
+                    f"it took too long or something. Here you go: \n"
+                    f"{output}"
+                )
diff --git a/extensions/addons/cogs/otheraddons.py b/extensions/addons/cogs/otheraddons.py
index 3be4ccc..d89c72b 100644
--- a/extensions/addons/cogs/otheraddons.py
+++ b/extensions/addons/cogs/otheraddons.py
@@ -219,10 +219,10 @@ async def on_message(self, message: discord.Message):
             return
 
         if "celcius" in message.content.lower():
-            # noinspection LongLine
             await message.reply(
                 "Fun fact: Celsius was named after a guy named "
-                "['Anders Celsius'](https://en.wikipedia.org/wiki/Anders_Celsius). "
+                "['Anders Celsius']"
+                "(https://en.wikipedia.org/wiki/Anders_Celsius). "
                 "'Celcius' is therefore an incorrect spelling. :)"
             )
 
diff --git a/extensions/addons/cogs/searchaddons.py b/extensions/addons/cogs/searchaddons.py
index 43cc6f3..800f69b 100644
--- a/extensions/addons/cogs/searchaddons.py
+++ b/extensions/addons/cogs/searchaddons.py
@@ -106,26 +106,30 @@ async def equaldex(self, itx: discord.Interaction[Bot], country_id: str):
 
         embed = discord.Embed(color=7829503, title=region.name)
         for issue in region.issues:
-            if type(region.issues[issue]['current_status']) is list:
+            if type(region.issues[issue]) is list:
                 value = "No data"
             else:
-                # value = region.issues[issue]['current_status']['value_formatted']
-                # if region.issues[issue]['current_status']['value'] in [
-                #     'Legal', 'Equal','No censorship',
+                assert type(region.issues[issue]) is not list
+                status = region.issues[issue]['current_status']
+                value = status['value_formatted']
+                # if status['value'] in [
+                #     'Legal',
+                #     'Equal',
+                #     'No censorship',
                 #     'surgery not required',
-                #     "Sexual orientation and gender identity", "Recognized"
+                #     "Sexual orientation and gender identity",
+                #     "Recognized"
                 # ]:
                 #     value = "❤️ " + value
-                # elif region.issues[issue]['current_status']['value'] in ["Illegal"]:
+                # elif status['value'] in ["Illegal"]:
                 #     value = "🚫 " + value
-                # elif region.issues[issue]['current_status']['value'] in [
-                #     "Not legally recognized", "Not banned", "Varies by Region"
-                # ]:
+                # elif status['value'] in ["Not legally recognized",
+                #                          "Not banned", "Varies by Region"]:
                 #     value = "🟨 " + value
                 # else:
                 #     value = "➖ " + value
                 status_description = \
-                    region.issues[issue]['current_status']['description']
+                    status['description']
                 description = region.issues[issue]['description']
                 if len(status_description) > 0:
                     if len(status_description) > 200:
@@ -285,10 +289,11 @@ async def math(self, itx: discord.Interaction[Bot], query: str):
                         #  a list of 1 value instead.
                         assumption["values"] = [assumption["values"]]
                     for value_index in range(len(assumption["values"])):
-                        assumption_data["${desc" + str(value_index + 1) + "}"] \
+                        word_id = str(value_index + 1)
+                        assumption_data["${desc" + word_id + "}"] \
                             = assumption["values"][value_index]["desc"]
                         try:
-                            assumption_data["${word" + str(value_index + 1) + "}"] \
+                            assumption_data["${word" + word_id + "}"] \
                                 = assumption["values"][value_index]["word"]
                         except KeyError:
                             # the "word" variable is only there
diff --git a/extensions/addons/equaldexregion.py b/extensions/addons/equaldexregion.py
index 749c68b..128a26b 100644
--- a/extensions/addons/equaldexregion.py
+++ b/extensions/addons/equaldexregion.py
@@ -13,7 +13,7 @@ class EqualDexRegion:
     :ivar issues: A list or dictionary of issues related to the laws of
      the country.
 
-    .. ISO Code: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements
+    .. ISO Code: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements # noqa
     """
     def __init__(self, data: dict):
         """
@@ -26,5 +26,7 @@ def __init__(self, data: dict):
         self.name: str = data['name']
         self.continent = data['continent']
         self.url: str = data['url']
-        # noinspection LongLine
-        self.issues: dict[str, list[str] | dict[str, dict[str, str]]] = data['issues']
+        self.issues: dict[
+            str,
+            list[str] | dict[str, dict[str, str]]
+        ] = data['issues']
diff --git a/extensions/addons/views/__init__.py b/extensions/addons/views/__init__.py
index 9f0ca21..efed4f5 100644
--- a/extensions/addons/views/__init__.py
+++ b/extensions/addons/views/__init__.py
@@ -1,4 +1,4 @@
 __all__ = ['SendPublicButtonMath', 'EqualDexAdditionalInfo']
 
-from extensions.addons.views.math_sendpublicbutton import SendPublicButtonMath
-from extensions.addons.views.equaldex_additionalinfo import EqualDexAdditionalInfo
+from .math_sendpublicbutton import SendPublicButtonMath
+from .equaldex_additionalinfo import EqualDexAdditionalInfo
diff --git a/extensions/compliments/cogs/compliments.py b/extensions/compliments/cogs/compliments.py
index ebde5d4..5fb093b 100644
--- a/extensions/compliments/cogs/compliments.py
+++ b/extensions/compliments/cogs/compliments.py
@@ -277,7 +277,7 @@ async def _rina_used_deflect_and_it_was_very_effective(
     if respond == "BAD!":
         # noinspection LongLine
         await message.channel.send(
-            "https://cdn.discordapp.com/emojis/902351699182780468.gif?size=56&quality=lossless",
+            "https://cdn.discordapp.com/emojis/902351699182780468.gif?size=56&quality=lossless",  # noqa
             allowed_mentions=discord.AllowedMentions.none()
         )
     await message.channel.send(
diff --git a/extensions/customvcs/cogs/vctables.py b/extensions/customvcs/cogs/vctables.py
index 1a9e6a5..159b42b 100644
--- a/extensions/customvcs/cogs/vctables.py
+++ b/extensions/customvcs/cogs/vctables.py
@@ -1193,10 +1193,11 @@ async def vctable_lock(self, itx: discord.Interaction[Bot]):
                 f"viewing the voice channel.",
                 allowed_mentions=discord.AllowedMentions.none()
             )
-            cmd_participant = itx.client.get_command_mention("vctable participant")
+            cmd_participant = itx.client.get_command_mention_with_args(
+                "vctable participant", user=" ")
             await itx.edit_original_response(
-                content=f"Successfully enabled whitelist. Use {cmd_participant} "
-                        f"`user: ` to let more people speak.",
+                content=f"Successfully enabled whitelist. Use "
+                        f"{cmd_participant} to let more people speak.",
                 view=None,
             )
         else:
diff --git a/extensions/emojistats/cogs/emojistats.py b/extensions/emojistats/cogs/emojistats.py
index b7221c6..2c16914 100644
--- a/extensions/emojistats/cogs/emojistats.py
+++ b/extensions/emojistats/cogs/emojistats.py
@@ -16,18 +16,6 @@
 from resources.checks import not_in_dms_check
 
 
-# todo: convert to TypedDict \/
-
-#   Rina.emojistats         # snippet of <:ask:987785257661108324> in a test db at 2024-02-17T00:06+01:00
-# ------------------------------------------------------
-#               _id = Object('62f3004156483575bb3175de')
-#                id = "987785257661108324"          #  str  of emoji.id
-#              name = "ask"                         #  str  of emoji.name
-#  messageUsedCount = 11                            #  int  of how often messages have contained this emoji
-#          lastUsed = 1666720691                    #  int  of unix timestamp of when this emoji was last used
-#          animated = false                         #  bool of emoji.animated
-# reactionUsedCount = 8                             #  int  of how often messages have been replied to with this emoji
-
 async def _add_to_emoji_data(
         emoji: tuple[bool, str, str],
         async_rina_db: motorcore.AgnosticDatabase,
diff --git a/extensions/emojistats/database_dicts.py b/extensions/emojistats/database_dicts.py
index a39b077..5a30c5c 100644
--- a/extensions/emojistats/database_dicts.py
+++ b/extensions/emojistats/database_dicts.py
@@ -2,9 +2,12 @@
 
 
 class EmojiStatsData(TypedDict, total=False):
-    id: Required[str]  # unique
-    name: Required[str]
+    id: Required[str]  # emoji.id (unique)
+    name: Required[str]  # emoji.name
     messageUsedCount: NotRequired[int]
+    # ^ how often messages have contained this emoji
     lastUsed: Required[int]
-    animated: Required[bool]
+    # ^ unix timestamp of when this emoji was last used
+    animated: Required[bool]  # emoji.animated
     reactionUsedCount: NotRequired[int]
+    # ^ how often messages have been replied to with this emoji
diff --git a/extensions/qotw/cogs/devrequest.py b/extensions/qotw/cogs/devrequest.py
index c2ac5e3..87a0c90 100644
--- a/extensions/qotw/cogs/devrequest.py
+++ b/extensions/qotw/cogs/devrequest.py
@@ -44,8 +44,10 @@ async def developer_request(
         if developer_request_channel is None or developer_role is None:
             # noinspection LongLine
             missing = [key for key, value in {
-                AttributeKeys.developer_request_channel: developer_request_channel,
-                AttributeKeys.developer_request_reaction_role: developer_role
+                AttributeKeys.developer_request_channel:
+                    developer_request_channel,
+                AttributeKeys.developer_request_reaction_role:
+                    developer_role
             }.items()
                 if value is None]
             raise MissingAttributesCheckFailure(
diff --git a/extensions/reminders/cogs/reminders.py b/extensions/reminders/cogs/reminders.py
index 038abae..8eb44f6 100644
--- a/extensions/reminders/cogs/reminders.py
+++ b/extensions/reminders/cogs/reminders.py
@@ -200,7 +200,8 @@ async def reminders(
                 )
                 return
         except IndexError:
-            cmd_reminders = itx.client.get_command_mention("reminder reminders")
+            cmd_reminders = itx.client.get_command_mention(
+                "reminder reminders")
             await itx.response.send_message(
                 f"I couldn't find any reminder with that ID!\n"
                 f"Look for the \"ID: `0`\" at the beginning of your reminder "
diff --git a/extensions/reminders/views/copyreminder.py b/extensions/reminders/views/copyreminder.py
index 4ea8b24..0d46da4 100644
--- a/extensions/reminders/views/copyreminder.py
+++ b/extensions/reminders/views/copyreminder.py
@@ -41,7 +41,8 @@ async def button_callback(self, itx: discord.Interaction[Bot]):
         #  (max 50 allowed (internally chosen limit))
         user_reminders = get_user_reminders(itx.client, itx.user)
         if len(user_reminders) > 50:
-            cmd_reminders = itx.client.get_command_mention("reminder reminders")
+            cmd_reminders = itx.client.get_command_mention(
+                "reminder reminders")
             cmd_remove = itx.client.get_command_mention_with_args(
                 "reminder remove", item=" ")
             await itx.response.send_message(
diff --git a/extensions/settings/objects/__init__.py b/extensions/settings/objects/__init__.py
index 0f4b99b..a81ea3d 100644
--- a/extensions/settings/objects/__init__.py
+++ b/extensions/settings/objects/__init__.py
@@ -14,10 +14,13 @@
     'ServerAttributeIds',
 ]
 
-from extensions.settings.objects.autocomplete_modes import ModeAutocomplete, TypeAutocomplete
-from extensions.settings.objects.enabled_modules import EnabledModules, ModuleKeys
-from extensions.settings.objects.server_settings import ServerSettings, get_attribute_type, parse_attribute
-
-from extensions.settings.objects.attribute_keys import AttributeKeys
-from extensions.settings.objects.server_attributes import ServerAttributes
-from extensions.settings.objects.server_attribute_ids import ServerAttributeIds
+from .autocomplete_modes import ModeAutocomplete, TypeAutocomplete
+from .enabled_modules import EnabledModules, ModuleKeys
+from .server_settings import (
+    ServerSettings,
+    get_attribute_type,
+    parse_attribute,
+)
+from .attribute_keys import AttributeKeys
+from .server_attributes import ServerAttributes
+from .server_attribute_ids import ServerAttributeIds
diff --git a/extensions/staffaddons/cogs/staffaddons.py b/extensions/staffaddons/cogs/staffaddons.py
index 1f8299b..3b96cb2 100644
--- a/extensions/staffaddons/cogs/staffaddons.py
+++ b/extensions/staffaddons/cogs/staffaddons.py
@@ -171,7 +171,9 @@ async def delete_week_selfies(self, itx: discord.Interaction[Bot]):
     async def get_bot_version(self, itx: discord.Interaction[Bot]):
         # get most recently pushed bot version
         # noinspection LongLine
-        latest_rina = requests.get("https://raw.githubusercontent.com/TransPlace-Devs/uncute-rina/main/main.py").text
+        latest_rina = requests.get(
+            "https://raw.githubusercontent.com/TransPlace-Devs/uncute-rina/main/main.py"  # noqa
+        ).text
         latest_version = (latest_rina
                           .split("BOT_VERSION = \"", 1)[1]
                           .split("\"", 1)[0])
diff --git a/extensions/starboard/cogs/starboard.py b/extensions/starboard/cogs/starboard.py
index eae5e1e..8787e53 100644
--- a/extensions/starboard/cogs/starboard.py
+++ b/extensions/starboard/cogs/starboard.py
@@ -107,7 +107,7 @@ async def _send_starboard_message(
         timestamp=message.created_at  # this, or datetime.now()
     )
     # noinspection LongLine
-    msg_link = f"https://discord.com/channels/{message.guild.id}/{message.channel.id}/{message.id}"
+    msg_link = f"https://discord.com/channels/{message.guild.id}/{message.channel.id}/{message.id}"  # noqa
     embed.add_field(name="Source", value=f"[Jump!]({msg_link})")
     embed.set_footer(text=f"{message.id}")
     if isinstance(message.author, discord.Member):
@@ -580,7 +580,7 @@ async def on_raw_reaction_add(
                 return
 
             # noinspection LongLine
-            broken_link = f"https://discord.com/channels/{payload.guild_id}/{payload.channel_id}/{payload.message_id}"
+            broken_link = f"https://discord.com/channels/{payload.guild_id}/{payload.channel_id}/{payload.message_id}"  # noqa
             await log_to_guild(
                 self.client,
                 self.client.get_guild(payload.guild_id),
@@ -724,7 +724,7 @@ async def on_raw_message_delete(
             raise MissingAttributesCheckFailure(ModuleKeys.starboard, missing)
 
         # noinspection LongLine
-        if message_payload.message_id in starboard_message_ids_marked_for_deletion:
+        if message_payload.message_id in starboard_message_ids_marked_for_deletion:  # noqa
             # marked messages is a global variable
             # this prevents having two 'message deleted' logs for
             # manual deletion of starboard message
diff --git a/extensions/termdictionary/cogs/termdictionary.py b/extensions/termdictionary/cogs/termdictionary.py
index 214477f..7651481 100644
--- a/extensions/termdictionary/cogs/termdictionary.py
+++ b/extensions/termdictionary/cogs/termdictionary.py
@@ -6,7 +6,7 @@
 import asyncio
 
 from extensions.termdictionary.dictionaries import (
-    CustomDictionary,
+    # CustomDictionary,
     DictionaryApiDictionary,
     DictionaryBase,
     PronounsPageDictionary,
@@ -18,7 +18,8 @@
 
 from resources.checks import is_staff_check  # for staff dictionary commands
 from resources.customs import Bot
-# for logging custom dictionary changes, or when a search query returns nothing or >2000 characters
+# For logging custom dictionary changes, or when a search query returns
+#  nothing or >2000 characters
 from resources.utils.utils import log_to_guild
 
 
@@ -166,15 +167,27 @@ async def fetch_with_timeout(dictionary: DictionaryBase):
             for term in terms
         ][:7]  # limit choices to the first 7
 
-    admin = app_commands.Group(name='dictionary_staff', description='Change custom entries in the dictionary')
+    admin = app_commands.Group(
+        name='dictionary_staff',
+        description='Change custom entries in the dictionary'
+    )
 
     @app_commands.check(is_staff_check)
-    @admin.command(name="define", description="Add a dictionary entry for a word!")
+    @admin.command(name="define",
+                   description="Add a dictionary entry for a word!")
     @app_commands.describe(
-        term="This is the main word for the dictionary entry: Egg, Hormone Replacement Therapy (HRT), (case sens.)",
+        term="This is the main word for the dictionary entry: "
+             "Egg, Hormone Replacement Therapy (HRT), (case sens.)",
         definition="Give this term a definition",
-        synonyms="Add synonyms (SEPARATE WITH \", \")")
-    async def define(self, itx: discord.Interaction[Bot], term: str, definition: str, synonyms: str = ""):
+        synonyms="Add synonyms (SEPARATE WITH \", \")"
+    )
+    async def define(
+            self,
+            itx: discord.Interaction[Bot],
+            term: str,
+            definition: str,
+            synonyms: str = ""
+    ):
         # Test if this term is already defined in this dictionary.
         collection = itx.client.rina_db["termDictionary"]
         query = {"term": term}
@@ -202,7 +215,8 @@ async def define(self, itx: discord.Interaction[Bot], term: str, definition: str
         synonym_overlap = collection.find(query)
         warnings = ""
         for overlap in synonym_overlap:
-            warnings += f"You have already given a synonym before in {overlap['term']}.\n"
+            warnings += (f"You have already given a synonym before "
+                         f"in {overlap['term']}.\n")
 
         # Add term to dictionary
         post = {
diff --git a/extensions/termdictionary/dictionaries/PronounsPageDictionary.py b/extensions/termdictionary/dictionaries/PronounsPageDictionary.py
index a9d21c2..8ac89c9 100644
--- a/extensions/termdictionary/dictionaries/PronounsPageDictionary.py
+++ b/extensions/termdictionary/dictionaries/PronounsPageDictionary.py
@@ -17,12 +17,17 @@ class PronounsPageDictionary(DictionaryBase):
 
     This class handles dictionary entry lookup, response construction,
     and result formatting specific to the pronouns.page service.
-    It manages internal state for search results, response expansion, and Discord character limit constraints.
+    It manages internal state for search results, response expansion,
+    and Discord character limit constraints.
 
-    :ivar _result_str: Final response string to send to the user, or None if no results found.
-    :ivar _result_count: Number of matching results found for the current term.
-    :ivar _character_overflow: Internal flag to track if response exceeds Discord's 2000-character limit.
-    :ivar _expand_search: Boolean indicating whether to expand search to include synonyms/related terms.
+    :ivar _result_str: Final response string to send to the user, or
+     ``None`` if no results found.
+    :ivar _result_count: Number of matching results found for the
+     current term.
+    :ivar _character_overflow: Internal flag to track if response
+     exceeds Discord's 2000-character limit.
+    :ivar _expand_search: Boolean indicating whether to expand search
+     to include synonyms/related terms.
     """
 
     def __init__(self, session: aiohttp.ClientSession) -> None:
diff --git a/extensions/termdictionary/dictionaries/UrbanDictionary.py b/extensions/termdictionary/dictionaries/UrbanDictionary.py
index de90386..7994fea 100644
--- a/extensions/termdictionary/dictionaries/UrbanDictionary.py
+++ b/extensions/termdictionary/dictionaries/UrbanDictionary.py
@@ -8,15 +8,16 @@
 from typing import override
 
 from extensions.termdictionary.dictionaries import DictionaryBase
-from extensions.termdictionary.dictionaries.objects.UrbanDictionaryEntry import \
+from extensions.termdictionary.dictionaries.objects import (
     UrbanDictionaryEntry
+)
 from extensions.termdictionary.views import UrbanDictionaryPageView
 from resources.customs import Bot
 
 
 class UrbanDictionary(DictionaryBase):
     term_suffix = " [UD]"
-    
+
     def __init__(self, session: aiohttp.ClientSession):
         super().__init__()
         self._session = session
@@ -114,7 +115,7 @@ async def construct_response(self, term: str) -> None:
         if len(data) == 0:
             return
 
-        self._pages = await self._get_urban_dictionary_pages(data)
+        self._pages = self._get_urban_dictionary_pages(data)
         self.has_response = True
 
     @override
diff --git a/extensions/termdictionary/dictionaries/objects/__init__.py b/extensions/termdictionary/dictionaries/objects/__init__.py
index b84dfa9..bee939a 100644
--- a/extensions/termdictionary/dictionaries/objects/__init__.py
+++ b/extensions/termdictionary/dictionaries/objects/__init__.py
@@ -4,7 +4,8 @@
     "DictionaryApiEntry",
     "DetailedTermPage",
     "term_page_to_embed",
-    "get_term_lines"
+    "get_term_lines",
+    "UrbanDictionaryEntry",
 ]
 
 from .CustomDictionaryEntry import CustomDictionaryEntry
@@ -13,5 +14,6 @@
     DictionaryApiEntry,
     DetailedTermPage,
     term_page_to_embed,
-    get_term_lines
+    get_term_lines,
 )
+from .UrbanDictionaryEntry import UrbanDictionaryEntry
diff --git a/extensions/termdictionary/modals/__init__.py b/extensions/termdictionary/modals/__init__.py
index 12e728f..9d58881 100644
--- a/extensions/termdictionary/modals/__init__.py
+++ b/extensions/termdictionary/modals/__init__.py
@@ -1,3 +1,3 @@
 __all__ = ['DictionaryAPISendPageModal']
 
-from extensions.termdictionary.modals.dictionaryapi_sendpagemodal import DictionaryAPISendPageModal
+from .dictionaryapi_sendpagemodal import DictionaryAPISendPageModal
diff --git a/extensions/termdictionary/modals/dictionaryapi_sendpagemodal.py b/extensions/termdictionary/modals/dictionaryapi_sendpagemodal.py
index f41eefd..5d93c7e 100644
--- a/extensions/termdictionary/modals/dictionaryapi_sendpagemodal.py
+++ b/extensions/termdictionary/modals/dictionaryapi_sendpagemodal.py
@@ -3,21 +3,28 @@
 from resources.customs import Bot
 
 
-class DictionaryAPISendPageModal(discord.ui.Modal, title="Share single dictionary entry?"):
+class DictionaryAPISendPageModal(
+        discord.ui.Modal,
+        title="Share single dictionary entry?"
+):
     def __init__(self, max_page, timeout=None):
         super().__init__()
         self.succeeded: bool = False
         self.timeout = timeout
         self.max_page = max_page
         self.line: int | None = None
-        self.question_text = discord.ui.TextInput(label='Entry index',
-                                                  placeholder=f"[A number from 0 to {max_page} ]",
-                                                  # style=discord.TextStyle.short,
-                                                  # required=True
-                                                  )
+        self.question_text = discord.ui.TextInput(
+            label='Entry index',
+            placeholder=f"[A number from 0 to {max_page} ]",
+            # style=discord.TextStyle.short,
+            # required=True
+        )
         self.add_item(self.question_text)
 
-    async def on_submit(self, itx: discord.Interaction[Bot]):
+    async def on_submit(  # type: ignore
+            self,
+            itx: discord.Interaction[Bot]
+    ):
         try:
             self.line = int(self.question_text.value)
         except ValueError:
@@ -38,5 +45,9 @@ async def on_submit(self, itx: discord.Interaction[Bot]):
             )
             return
         self.succeeded = True
-        await itx.response.send_message("Sending item...", ephemeral=True, delete_after=8)
+        await itx.response.send_message(
+            "Sending item...",
+            ephemeral=True,
+            delete_after=8
+        )
         self.stop()
diff --git a/extensions/termdictionary/views/__init__.py b/extensions/termdictionary/views/__init__.py
index 9ccfc00..58f3774 100644
--- a/extensions/termdictionary/views/__init__.py
+++ b/extensions/termdictionary/views/__init__.py
@@ -1,4 +1,4 @@
 __all__ = ['DictionaryapiPageview', 'UrbanDictionaryPageView']
 
-from extensions.termdictionary.views.dictionaryapi import DictionaryapiPageview
-from extensions.termdictionary.views.urbandictionary import UrbanDictionaryPageView
+from .dictionaryapi import DictionaryapiPageview
+from .urbandictionary import UrbanDictionaryPageView
diff --git a/extensions/watchlist/modals/__init__.py b/extensions/watchlist/modals/__init__.py
index fb83f5e..46e5629 100644
--- a/extensions/watchlist/modals/__init__.py
+++ b/extensions/watchlist/modals/__init__.py
@@ -1,3 +1,3 @@
 __all__ = ['WatchlistReasonModal']
 
-from extensions.watchlist.modals.watchlistreasonmodal import WatchlistReasonModal
+from .watchlistreasonmodal import WatchlistReasonModal
diff --git a/resources/utils/__init__.py b/resources/utils/__init__.py
index 03f4f47..8935af5 100644
--- a/resources/utils/__init__.py
+++ b/resources/utils/__init__.py
@@ -15,6 +15,11 @@
 
 from resources.checks.permissions import is_staff, is_admin
 from .stringhelper import replace_string_command_mentions
-from .timeparser import TimeParser, MissingQuantityException, MissingUnitException, TIMETERMS
+from .timeparser import (
+    TimeParser,
+    MissingQuantityException,
+    MissingUnitException,
+    TIMETERMS,
+)
 from .utils import DebugColor, debug, get_mod_ticket_channel, log_to_guild
 from .database import codec_options
diff --git a/resources/views/generics.py b/resources/views/generics.py
index 622a475..58bf9fe 100644
--- a/resources/views/generics.py
+++ b/resources/views/generics.py
@@ -219,7 +219,7 @@ def __init__(
             prepended_buttons: list[discord.ui.Button] | None = None,
             appended_buttons: list[discord.ui.Button] | None = None,
             loop_around_pages: bool = True,
-            timeout=None
+            timeout: float | None = None
     ):
         super().__init__(timeout=timeout)
         if prepended_buttons is None:
diff --git a/setup.cfg b/setup.cfg
index b2ac6a3..dc96c11 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -2,7 +2,7 @@
 disable_error_code = import-untyped
 
 [flake8]
-max-line-length = 120
+max-line-length = 79
 exclude = venv
 
 [tool:pytest]

From bd1fe6fabcfb2243a363391102725f651d292420 Mon Sep 17 00:00:00 2001
From: MysticMia <65396931+MysticMia@users.noreply.github.com>
Date: Fri, 23 May 2025 08:20:23 +0200
Subject: [PATCH 21/26] Turn NameUsage GetTopPageView into a PageView (+update
 GetNameModal)

---
 extensions/nameusage/cogs/nameusage.py      | 18 ++---
 extensions/nameusage/modals/getnamemodal.py | 59 +++++++----------
 extensions/nameusage/views/pageview.py      | 73 +++++++++++----------
 3 files changed, 64 insertions(+), 86 deletions(-)

diff --git a/extensions/nameusage/cogs/nameusage.py b/extensions/nameusage/cogs/nameusage.py
index fcfb512..1162bf7 100644
--- a/extensions/nameusage/cogs/nameusage.py
+++ b/extensions/nameusage/cogs/nameusage.py
@@ -133,28 +133,18 @@ def add(member_name_part):
             if result_page == "":
                 result_page = "_"
             pages.append(result_page)
-        page = 0
 
-        result_page = pages[page]
-        result_page2 = pages[page + 1]
         mode_text = ("usernames" if mode == 1
                      else "nicknames" if mode == 2
                      else "usernames and nicknames")
         embed_title = f'Most-used {mode_text} leaderboard!'
-        embed = discord.Embed(color=8481900, title=embed_title)
-        embed.add_field(name="Column 1", value=result_page)
-        embed.add_field(name="Column 2", value=result_page2)
-        embed.set_footer(
-            text="page: "
-                 + str(page + 1)
-                 + " / "
-                 + str(int(len(pages) / 2))
-        )
+
         view = GetTopPageView(pages, embed_title, timeout=60)
+        embed = view.make_page()
         await itx.followup.send("", embed=embed, view=view, ephemeral=True)
+
         await view.wait()
-        if view.value is None:
-            await itx.edit_original_response(view=None)
+        await itx.edit_original_response(view=None)
 
     @app_commands.command(
         name="name",
diff --git a/extensions/nameusage/modals/getnamemodal.py b/extensions/nameusage/modals/getnamemodal.py
index 83e9344..86943ce 100644
--- a/extensions/nameusage/modals/getnamemodal.py
+++ b/extensions/nameusage/modals/getnamemodal.py
@@ -4,15 +4,15 @@
 
 
 class GetNameModal(discord.ui.Modal, title="Search page with word"):
-    def __init__(self, pages, embed_title, timeout=None):
-        super().__init__()
-        self.value = None
-        self.timeout = timeout
-        self.embed_title = embed_title
+    def __init__(
+            self,
+            pages,
+            timeout: float | None = None
+    ):
+        super().__init__(timeout=timeout)
         self.pages = pages
-        self.page = None
-
-        self.word = None
+        self.page: int | None = None
+        self.return_interaction: discord.Interaction[Bot] | None = None
         self.question_text = discord.ui.TextInput(
             label='What word to look up in the server name list?',
             placeholder="The word you want to look up",
@@ -21,40 +21,25 @@ def __init__(self, pages, embed_title, timeout=None):
         )
         self.add_item(self.question_text)
 
-    async def on_submit(self, itx: discord.Interaction[Bot]):
-        self.value = 9  # failed; placeholder
-        self.word = self.question_text.value.lower()
+    async def on_submit(  # type: ignore (Interaction vs. Interaction[Bot])
+            self,
+            itx: discord.Interaction[Bot]
+    ):
+        word = self.question_text.value.lower()
         for page_id in range(len(self.pages)):
-            # self.pages[page_id] returns ['15 nora\n13 rose\n9 brand\n8
-            #  george\n4 rina\n3 grace\n2 eliza\n','_']
-            # split at \n and " " to get [["15", "nora"],
-            #  ["13", "rose"], ["9", "brand"], ["8", "george"]]
-            #  and compare self.word with the names.
-            if (self.word in [name.split(" ")[-1]
-                              for name in self.pages[page_id].split("\n")]):
+            # 1. self.pages[page_id] returns ['15 nora\n13 rose\n9
+            #    brand\n8 george\n4 rina\n3 grace\n2 eliza\n','_']
+            # 2. Split at \n and " " to get [["15", "nora"],
+            #    ["13", "rose"], ["9", "brand"], ["8", "george"]]
+            # 3. And compare the word with the names.
+            if (word in [name.split(" ")[-1]
+                         for name in self.pages[page_id].split("\n")]):
                 self.page = int(page_id / 2)
+                self.return_interaction = itx
                 break
         else:
             await itx.response.send_message(
-                content=f"I couldn't find '{self.word}' in any of the pages! "
+                content=f"I couldn't find '{word}' in any of the pages! "
                         f"Perhaps nobody chose this name!",
                 ephemeral=True)
             return
-        result_page = self.pages[self.page * 2]
-        result_page2 = self.pages[self.page * 2 + 1]
-        result_page = result_page.replace(f" {self.word}\n",
-                                          f" **__{self.word}__**\n")
-        result_page2 = result_page2.replace(f" {self.word}\n",
-                                            f" **__{self.word}__**\n")
-        embed = discord.Embed(color=8481900, title=self.embed_title)
-        embed.add_field(name="Column 1", value=result_page)
-        embed.add_field(name="Column 2", value=result_page2)
-        embed.set_footer(
-            text="page: "
-                 + str(self.page + 1)
-                 + " / "
-                 + str(int(len(self.pages) / 2))
-        )
-        await itx.response.edit_message(embed=embed)
-        self.value = 1
-        self.stop()
diff --git a/extensions/nameusage/views/pageview.py b/extensions/nameusage/views/pageview.py
index e9365f9..1cd829a 100644
--- a/extensions/nameusage/views/pageview.py
+++ b/extensions/nameusage/views/pageview.py
@@ -2,21 +2,25 @@
 
 from extensions.nameusage.modals.getnamemodal import GetNameModal
 from resources.customs import Bot
+from resources.views.generics import PageView
 
 
-# todo: use Generics.PageView
-class GetTopPageView(discord.ui.View):
-    def __init__(self, pages, embed_title, timeout=None):
-        super().__init__()
-        self.value = None
-        self.timeout = timeout
-        self.page = 0
+class GetTopPageView(PageView):
+    def __init__(
+            self,
+            pages: list[str],
+            embed_title,
+            timeout: float | None = None
+    ):
+        super().__init__(
+            starting_page=0,
+            max_page_index=int(len(pages) / 2) - 1,
+            timeout=timeout
+        )
         self.pages = pages
         self.embed_title = embed_title
 
-    async def _make_page(self):
-        # todo: extract duplicated code to a new PageView subclass.
-        #  Oh yeah. This should definitely be a PageView.
+    def make_page(self) -> discord.Embed:
         result_page = self.pages[self.page * 2]
         result_page2 = self.pages[self.page * 2 + 1]
         embed = discord.Embed(color=8481900, title=self.embed_title)
@@ -30,32 +34,31 @@ async def _make_page(self):
         )
         return embed
 
-    # When the confirm button is pressed, set the inner value to `True`
-    #  and stop the View from listening to more input. We also send the
-    #  user an ephemeral message that we're confirming their choice.
-    @discord.ui.button(label='Previous', style=discord.ButtonStyle.blurple)
-    async def previous(self, itx: discord.Interaction[Bot], _button: discord.ui.Button):
-        # self.value = "previous"
-        self.page -= 1
-        if self.page < 0:
-            self.page = int(len(self.pages) / 2) - 1
-        embed = await self._make_page()
-        await itx.response.edit_message(embed=embed)
-
-    @discord.ui.button(label='Next', style=discord.ButtonStyle.blurple)
-    async def next(self, itx: discord.Interaction[Bot], _button: discord.ui.Button):
-        self.page += 1
-        if self.page >= int(len(self.pages) / 2):
-            self.page = 0
-        embed = await self._make_page()
-        await itx.response.edit_message(embed=embed)
+    async def update_page(
+            self,
+            itx: discord.Interaction[Bot],
+            view: PageView
+    ):
+        itx.response: discord.InteractionResponse[Bot]  # type: ignore
+        embed = self.make_page()
+        await itx.response.edit_message(
+            embed=embed,
+            view=view
+        )
 
-    @discord.ui.button(label='Find my name', style=discord.ButtonStyle.blurple)
-    async def find_name(self, itx: discord.Interaction[Bot], _button: discord.ui.Button):
-        send_one = GetNameModal(self.pages, self.embed_title)
+    @discord.ui.button(
+        label='Find my name',
+        style=discord.ButtonStyle.blurple,
+    )
+    async def find_name(
+            self,
+            itx: discord.Interaction[Bot],
+            _button: discord.ui.Button
+    ):
+        send_one = GetNameModal(self.pages)
         await itx.response.send_modal(send_one)
         await send_one.wait()
-        if send_one.value in [None, 9]:
-            pass
-        else:
+        if (send_one.return_interaction is not None
+                and send_one.page is not None):
             self.page = send_one.page
+            await self.update_page(send_one.return_interaction, self)

From 0849f1cafcc45b060016d8602c0fc4ad8536924e Mon Sep 17 00:00:00 2001
From: MysticMia <65396931+MysticMia@users.noreply.github.com>
Date: Sat, 24 May 2025 10:33:17 +0200
Subject: [PATCH 22/26] fix compliments and fix devrequest not mentioning
 developer role.

---
 extensions/compliments/cogs/compliments.py |  2 +-
 extensions/qotw/cogs/devrequest.py         | 24 ++++++++--------------
 2 files changed, 10 insertions(+), 16 deletions(-)

diff --git a/extensions/compliments/cogs/compliments.py b/extensions/compliments/cogs/compliments.py
index 5fb093b..6365f18 100644
--- a/extensions/compliments/cogs/compliments.py
+++ b/extensions/compliments/cogs/compliments.py
@@ -332,7 +332,7 @@ async def on_message(self, message: discord.Message):
         if self.client.user.mention in message.content.split():
             msg = message.content.lower()
             called_cute: bool | None = self._contains_cuteness_assignment(
-                msg.content.lower())
+                msg)
             if called_cute is True:
                 try:
                     await message.add_reaction("<:this:960916817801535528>")
diff --git a/extensions/qotw/cogs/devrequest.py b/extensions/qotw/cogs/devrequest.py
index 87a0c90..e2bfe2f 100644
--- a/extensions/qotw/cogs/devrequest.py
+++ b/extensions/qotw/cogs/devrequest.py
@@ -33,25 +33,16 @@ async def developer_request(
             itx: discord.Interaction[Bot],
             suggestion: app_commands.Range[str, 25, 1500]
     ):
-        developer_request_channel: discord.TextChannel | None
-        developer_role: discord.Role | None
-        developer_request_channel, developer_role = \
+        developer_request_channel: discord.TextChannel | None = \
             itx.client.get_guild_attribute(
                 itx.guild,
                 AttributeKeys.developer_request_channel,
-                AttributeKeys.developer_request_reaction_role
             )
-        if developer_request_channel is None or developer_role is None:
-            # noinspection LongLine
-            missing = [key for key, value in {
-                AttributeKeys.developer_request_channel:
-                    developer_request_channel,
-                AttributeKeys.developer_request_reaction_role:
-                    developer_role
-            }.items()
-                if value is None]
+        if developer_request_channel is None:
             raise MissingAttributesCheckFailure(
-                ModuleKeys.dev_requests, missing)
+                ModuleKeys.dev_requests,
+                [AttributeKeys.developer_request_channel],
+            )
 
         if len(suggestion) > 4000:
             await itx.response.send_message(
@@ -91,8 +82,11 @@ async def developer_request(
         # without mentioning them and do the same for the requester, though
         # this will only work if they're in the staff server..
         joiner_msg = await thread.send("role mention placeholder")
+        developer_role: discord.Role | None
         developer_role = itx.client.get_guild_attribute(
-            itx.user.guild, AttributeKeys.developer_request_channel)
+            itx.user.guild,
+            AttributeKeys.developer_request_reaction_role
+        )
         if developer_role is None:
             cmd_settings = itx.client.get_command_mention_with_args(
                 "settings",

From 5ca98fe7ffb9b8bb538ea37d82df021e2a10de53 Mon Sep 17 00:00:00 2001
From: MysticMia <65396931+MysticMia@users.noreply.github.com>
Date: Sat, 24 May 2025 10:37:14 +0200
Subject: [PATCH 23/26] Fix remindme with yyyy-mm-dd time format, and split
 function.

---
 .../reminders/objects/reminderobject.py       | 142 +++++++++++-------
 .../reminders/views/timeofdayselection.py     |  18 ++-
 2 files changed, 103 insertions(+), 57 deletions(-)

diff --git a/extensions/reminders/objects/reminderobject.py b/extensions/reminders/objects/reminderobject.py
index f674f9c..59f1306 100644
--- a/extensions/reminders/objects/reminderobject.py
+++ b/extensions/reminders/objects/reminderobject.py
@@ -137,7 +137,7 @@ async def send_reminder(self):
             pass
         collection = self.client.rina_db["reminders"]
         query = {"userID": self.userID}
-        db_data = collection.find_one(query)
+        db_data: DatabaseData | None = collection.find_one(query)
         reminders = db_data["reminders"]
         index_subtraction = 0
         for reminder_index in range(len(reminders)):
@@ -171,7 +171,91 @@ async def _handle_reminder_timestamp_parsing(
         itx: discord.Interaction[Bot],
         reminder_datetime: str
 ) -> tuple[datetime, discord.Interaction[Bot]]:  # todo: docstring
-    # validate format
+    mode: TimestampFormats = _validate_timestamp_format(reminder_datetime)
+
+    # convert given time string to valid datetime
+    timestamp_format = [
+        "%Y-%m-%dt%H:%M:%S%z",
+        "%Y-%m-%dt%H:%M%z",
+        "%Y-%m-%d"
+    ][mode.value]
+    try:
+        timestamp = datetime.strptime(reminder_datetime, timestamp_format)
+    except ValueError:
+        raise ValueError(
+            f"Incorrect format given! I could not convert "
+            f"{reminder_datetime} to format {timestamp_format}"
+        )
+    if timestamp < datetime.now() - timedelta(hours=48):
+        raise ValueError(
+            "The given date is too far in the past! Please pick a date "
+            "that is later than.. yesterday. This guard is mainly to "
+            "prevent an error that happens when you try to set a timezone "
+            "to things from before the year 1970."
+        )
+    if timestamp > datetime.now() + timedelta(days=365 * 1000):
+        raise ValueError(
+            "The given date is too far in the future! Please pick a date "
+            "that is less than 1000 years into the future... This "
+            "is an Operating System error, because it simply doesn't "
+            "let me add a timezone to things from beyond the year 3001."
+        )
+
+    distance = timestamp
+
+    # clarify datetime timezone if necessary
+    if mode == TimestampFormats.DateNoTime:
+        assert timestamp.tzinfo is None
+        try:
+            timestamp = timestamp.astimezone(timezone.utc)
+        except OSError:
+            raise ValueError(
+                "Couldn't set timezone for this time object. I assume you "
+                "probably filled in something unrealistic."
+            )
+        assert timestamp.tzinfo is not None
+
+        options = {
+            "1": (timestamp - timedelta(hours=12)),
+            "2": (timestamp - timedelta(hours=12)),
+            "3": timestamp,
+            "4": (timestamp + timedelta(hours=6)),
+            "5": (timestamp + timedelta(hours=12)),
+            "6": (timestamp + timedelta(hours=18)),
+            "7": (timestamp + timedelta(hours=24)),
+        }
+        query = ("Since a date format doesn't tell me what time you want "
+                 "the reminder, you can pick a time yourself:")
+        for option in options:
+            timestamp_str = f""
+            query += f"\n  `{option}.` {timestamp_str}"
+        view = TimeOfDaySelection(list(options))
+        await itx.response.send_message(query, view=view, ephemeral=True)
+        await view.wait()
+        if view.value is None:
+            await itx.edit_original_response(
+                content="Reminder creation menu timed out.", view=None)
+            raise ReminderTimeSelectionMenuTimeOut()
+
+        assert (view.value is not None
+                and view.return_interaction is not None)
+        await itx.edit_original_response(view=None)
+        distance = options[view.value]
+        itx = view.return_interaction
+
+    # New itx is returned in case the response is used by the view.
+    return distance, itx
+
+
+def _validate_timestamp_format(reminder_datetime: str) -> TimestampFormats:
+    """
+    Validate a date time string and parse the timestamp string format.
+    :param reminder_datetime: The string to validate and identify the
+     format for.
+    :return: An expected format of the given timestamp string.
+    :raise ValueError: If the given timestamp string is not in a
+     recognised format.
+    """
     # note: "t" here is lowercase because the reminder_datetime string
     #  gets lowercased...
     has_timezone = False
@@ -217,7 +301,6 @@ async def _handle_reminder_timestamp_parsing(
             raise ValueError("Incorrect date given! Please format the date "
                              "as YYYY-MM-DD, like 2023-12-31")
         mode = TimestampFormats.DateNoTime
-
     # error for unimplemented: giving date and time format without a timezone
     if not has_timezone and mode != TimestampFormats.DateNoTime:
         raise ValueError(
@@ -225,58 +308,13 @@ async def _handle_reminder_timestamp_parsing(
             "sent at the right time. Please add the timezone like so "
             "'-0100' or '+0900'."
         )
-
-    # convert given time string to valid datetime
-    timestamp_format = [
-        "%Y-%m-%dt%H:%M:%S%z",
-        "%Y-%m-%dt%H:%M%z",
-        "%Y-%m-%d"
-    ][mode.value]
-    try:
-        timestamp = datetime.strptime(reminder_datetime, timestamp_format)
-    except ValueError:
-        raise ValueError(
-            f"Incorrect format given! I could not convert "
-            f"{reminder_datetime} to format {timestamp_format}"
-        )
-
-    # todo: move the above code to a new function
-
-    distance = timestamp
-    # clarify datetime timezone if necessary
-    if mode == TimestampFormats.DateNoTime:
-        options = {
-            "1": (timestamp - timedelta(hours=12)),
-            "2": (timestamp - timedelta(hours=12)),
-            "3": timestamp,
-            "4": (timestamp + timedelta(hours=6)),
-            "5": (timestamp + timedelta(hours=12)),
-            "6": (timestamp + timedelta(hours=18)),
-            "7": (timestamp + timedelta(hours=24)),
-        }
-        query = ("Since a date format doesn't tell me what time you want "
-                 "the reminder, you can pick a time yourself:")
-        for option in options:
-            timestamp_str = f""
-            query += f"\n  `{option}.` {timestamp_str}"
-        view = TimeOfDaySelection(list(options))
-        await itx.response.send_message(query, view=view, ephemeral=True)
-        await view.wait()
-        if view.value is None:
-            await itx.edit_original_response(
-                content="Reminder creation menu timed out.", view=None)
-            raise ReminderTimeSelectionMenuTimeOut()
-        distance = options[view.value]
-        itx = view.return_interaction
-
-    # New itx is returned in case the response is used by the view.
-    return distance, itx
+    return mode
 
 
 async def _parse_reminder_time(
         itx: discord.Interaction[Bot],
         reminder_datetime: str
-) -> tuple[datetime, datetime]:
+) -> tuple[datetime, datetime, discord.Interaction[Bot]]:
     # todo: make followup message a separate method, and initiate it by
     #  making this function raise a specific exception.
     """
@@ -342,7 +380,7 @@ async def _parse_reminder_time(
         except ValueError as ex:
             raise TimestampParseException(ex)
 
-    return distance, creation_time
+    return distance, creation_time, itx
 
 
 async def _create_reminder(
@@ -426,7 +464,7 @@ async def parse_and_create_reminder(
         )
         raise OverflowError(message)
 
-    distance, creation_time = await _parse_reminder_time(
+    distance, creation_time, itx = await _parse_reminder_time(
         itx, reminder_datetime)
 
     await _create_reminder(
diff --git a/extensions/reminders/views/timeofdayselection.py b/extensions/reminders/views/timeofdayselection.py
index 2618524..e0dc179 100644
--- a/extensions/reminders/views/timeofdayselection.py
+++ b/extensions/reminders/views/timeofdayselection.py
@@ -12,16 +12,24 @@ def __init__(self, options: list[str], timeout=180):
         self.timeout = timeout
 
         for option in options:
-            def callback(itx):  # pass the button label to the callback
-                return self.callback(itx, option)
+            def callback(itx, label=option):
+                """Helper to pass the button label to the callback"""
+                # Needs label= to create a copy of the `option` value,
+                #  otherwise the parameter is overwritten. (always
+                #  returns 7).
+                return self.callback(itx, label)
 
-            self.add_item(create_simple_button(
-                option, discord.ButtonStyle.green, callback))
+            button = create_simple_button(
+                label=option,
+                style=discord.ButtonStyle.green,
+                callback=callback,
+            )
+            self.add_item(button)
 
     async def callback(
             self,
             interaction: discord.Interaction[Bot],
-            label: str
+            label: str,
     ):
         self.value = label
         self.return_interaction = interaction

From 618451e3d2d7802d80f853074baec45625856dbc Mon Sep 17 00:00:00 2001
From: MysticMia <65396931+MysticMia@users.noreply.github.com>
Date: Sat, 24 May 2025 10:37:14 +0200
Subject: [PATCH 24/26] Fix remindme with yyyy-mm-dd time format, and split
 function.

---
 .../reminders/objects/reminderobject.py       | 29 +++-------
 .../reminders/objects/test_reminderobject.py  | 57 +++++++++++--------
 2 files changed, 41 insertions(+), 45 deletions(-)

diff --git a/extensions/reminders/objects/reminderobject.py b/extensions/reminders/objects/reminderobject.py
index 59f1306..c2eb550 100644
--- a/extensions/reminders/objects/reminderobject.py
+++ b/extensions/reminders/objects/reminderobject.py
@@ -186,35 +186,20 @@ async def _handle_reminder_timestamp_parsing(
             f"Incorrect format given! I could not convert "
             f"{reminder_datetime} to format {timestamp_format}"
         )
-    if timestamp < datetime.now() - timedelta(hours=48):
-        raise ValueError(
-            "The given date is too far in the past! Please pick a date "
-            "that is later than.. yesterday. This guard is mainly to "
-            "prevent an error that happens when you try to set a timezone "
-            "to things from before the year 1970."
-        )
-    if timestamp > datetime.now() + timedelta(days=365 * 1000):
+
+    try:
+        timestamp = timestamp.astimezone(timezone.utc)
+    except OSError:
         raise ValueError(
-            "The given date is too far in the future! Please pick a date "
-            "that is less than 1000 years into the future... This "
-            "is an Operating System error, because it simply doesn't "
-            "let me add a timezone to things from beyond the year 3001."
+            "Couldn't set timezone for this time object. I assume you "
+            "probably filled in something unrealistic, like a year before "
+            "1970 or after 3001."
         )
 
     distance = timestamp
 
     # clarify datetime timezone if necessary
     if mode == TimestampFormats.DateNoTime:
-        assert timestamp.tzinfo is None
-        try:
-            timestamp = timestamp.astimezone(timezone.utc)
-        except OSError:
-            raise ValueError(
-                "Couldn't set timezone for this time object. I assume you "
-                "probably filled in something unrealistic."
-            )
-        assert timestamp.tzinfo is not None
-
         options = {
             "1": (timestamp - timedelta(hours=12)),
             "2": (timestamp - timedelta(hours=12)),
diff --git a/unit_tests/extensions/reminders/objects/test_reminderobject.py b/unit_tests/extensions/reminders/objects/test_reminderobject.py
index a894c8d..353ff0a 100644
--- a/unit_tests/extensions/reminders/objects/test_reminderobject.py
+++ b/unit_tests/extensions/reminders/objects/test_reminderobject.py
@@ -45,9 +45,10 @@ def test_output_nochange_match():
     func = _parse_reminder_time(itx, "0d")
 
     # Act
-    reminder_time, now = asyncio.run(func)
+    reminder_time, now, itx2 = asyncio.run(func)
 
     # Assert
+    assert itx is itx2
     assert current_time == now
     assert current_time == reminder_time
 
@@ -59,10 +60,11 @@ def test_output_timezones_match():
     func = _parse_reminder_time(itx, "0d")
 
     # Act
-    reminder_time, now = asyncio.run(func)
+    reminder_time, now, itx2 = asyncio.run(func)
     reminder_time = reminder_time.astimezone(timezone.utc)
 
     # Assert
+    assert itx is itx2
     assert current_time == now
     assert current_time == reminder_time
 
@@ -74,12 +76,13 @@ def test_relative_offset():
     func = _parse_reminder_time(itx, "500d,1h,1m,1s")
 
     # Act
-    reminder_time, _ = asyncio.run(func)
+    reminder_time, _, itx2 = asyncio.run(func)
     current_time = current_time.replace(tzinfo=timezone.utc)
     expected_time = (current_time + timedelta(
         days=500, hours=1, minutes=1, seconds=1))
 
     # Assert
+    assert itx is itx2
     assert expected_time == reminder_time
 
 
@@ -93,7 +96,7 @@ def test_offset_overflows():
     func = _parse_reminder_time(itx, "1y 20mo 50w 500d 30h 100m 100s")
 
     # Act
-    reminder_time, _ = asyncio.run(func)
+    reminder_time, _, itx2 = asyncio.run(func)
     intermediate_expected_time = datetime(
         year=2003, month=7, day=30, hour=23, minute=59, second=59,
         tzinfo=timezone.utc
@@ -102,6 +105,7 @@ def test_offset_overflows():
         days=500 + 50 * 7, hours=30, minutes=100, seconds=100)
 
     # Assert
+    assert itx is itx2
     assert expected_time == reminder_time
 
 
@@ -115,11 +119,12 @@ def test_discord_timestamp():
     func = _parse_reminder_time(itx, discord_format)
 
     # Act
-    reminder_time, _ = asyncio.run(func)
+    reminder_time, _, itx2 = asyncio.run(func)
     current_time = current_time.astimezone()
     expected_time = current_time + timedelta(days=1)
 
     # Assert
+    assert itx is itx2
     assert expected_time == reminder_time
 
 
@@ -140,10 +145,11 @@ def test_iso_date_timeshort_timezone():
     func2 = _parse_reminder_time(itx, date_string2)
 
     # Act
-    reminder_time1, _ = asyncio.run(func1)
-    reminder_time2, _ = asyncio.run(func2)
+    reminder_time1, _, itx2 = asyncio.run(func1)
+    reminder_time2, _, itx3 = asyncio.run(func2)
 
     # Assert
+    assert itx is itx2 is itx3
     assert reminder_time1.astimezone() \
            == reminder_time2.astimezone() + timedelta(hours=1)
 
@@ -161,12 +167,13 @@ def test_iso_date_timelong_timezone():
     func2 = _parse_reminder_time(itx, date_string2)
 
     # Act
-    reminder_time1, _ = asyncio.run(func1)
-    reminder_time2, _ = asyncio.run(func2)
+    reminder_time1, _, itx2 = asyncio.run(func1)
+    reminder_time2, _, itx3 = asyncio.run(func2)
 
     # Assert
     # same as test_..._timeshort(), but using %H instead of %I. This
     #  means the time will be padded to 2 characters.
+    assert itx is itx2 is itx3
     assert reminder_time1.astimezone() \
            == reminder_time2.astimezone() + timedelta(hours=1)
 
@@ -183,11 +190,12 @@ def test_iso_date_matches_unix_timestamp():
     func2 = _parse_reminder_time(itx, date_string2)
 
     # Act
-    reminder_time1, _ = asyncio.run(func1)
-    reminder_time2, _ = asyncio.run(func2)
+    reminder_time1, _, itx2 = asyncio.run(func1)
+    reminder_time2, _, itx3 = asyncio.run(func2)
     timezone_correction1 = reminder_time1
 
     # Assert
+    assert itx is itx2 is itx3
     assert timezone_correction1 == reminder_time2
 
 # endregion Correct functionality
@@ -204,7 +212,7 @@ def test_exception_iso_time_timezone():
 
     # Assert
     with pytest.raises(TimestampParseException):
-        reminder_time, _ = asyncio.run(func)
+        reminder_time, _, _ = asyncio.run(func)
 
 
 def test_exception_american_format_ymd():
@@ -216,7 +224,7 @@ def test_exception_american_format_ymd():
 
     # Assert
     with pytest.raises(TimestampParseException):
-        reminder_time, _ = asyncio.run(func)
+        reminder_time, _, _ = asyncio.run(func)
 
 
 def test_exception_american_format_dmy():
@@ -228,7 +236,7 @@ def test_exception_american_format_dmy():
 
     # Assert
     with pytest.raises(TimestampParseException):
-        reminder_time, _ = asyncio.run(func)
+        reminder_time, _, _ = asyncio.run(func)
 
 
 def test_exception_american_format_with_t_ymd():
@@ -240,7 +248,7 @@ def test_exception_american_format_with_t_ymd():
 
     # Assert
     with pytest.raises(TimestampParseException):
-        reminder_time, _ = asyncio.run(func)
+        reminder_time, _, _ = asyncio.run(func)
 
 
 def test_exception_american_format_with_t_dmy():
@@ -252,7 +260,7 @@ def test_exception_american_format_with_t_dmy():
 
     # Assert
     with pytest.raises(TimestampParseException):
-        reminder_time, _ = asyncio.run(func)
+        reminder_time, _, _ = asyncio.run(func)
 
 
 def test_malformed_year():
@@ -264,7 +272,7 @@ def test_malformed_year():
 
     # Assert
     with pytest.raises(TimestampParseException):
-        reminder_time, _ = asyncio.run(func)
+        reminder_time, _, _ = asyncio.run(func)
 
 
 def test_malformed_year_month():
@@ -276,7 +284,7 @@ def test_malformed_year_month():
 
     # Assert
     with pytest.raises(TimestampParseException):
-        reminder_time, _ = asyncio.run(func)
+        reminder_time, _, _ = asyncio.run(func)
 
 
 def test_exception_iso_date_timeshort():  # no timezone
@@ -294,7 +302,7 @@ def test_exception_iso_date_timeshort():  # no timezone
         #  the strftime, and as such the code should run into an
         #  exception.
         # This exception must be handled by the command in the Cog instead.
-        reminder_time, _ = asyncio.run(func)
+        reminder_time, _, _ = asyncio.run(func)
 
 
 def test_exception_iso_date_timelong():  # no timezone
@@ -309,7 +317,7 @@ def test_exception_iso_date_timelong():  # no timezone
     with pytest.raises(TimestampParseException):
         # same as test_..._timeshort(), but using %H instead of %I. This
         #  means the time will be padded to 2 characters.
-        reminder_time, _ = asyncio.run(func)
+        reminder_time, _, _ = asyncio.run(func)
 
 
 def test_exception_iso_date():
@@ -325,7 +333,10 @@ def test_exception_iso_date():
         #  of day when you want to be reminded. This would be directly
         #  handled using itx.response, which the test doesn't provide,
         #  resulting in an Attribute error.
-        reminder_time, _ = asyncio.run(func)
+        # Todo: handle this nicer, make these tests parameter-based,
+        #  and add tests for _handle_reminder_timestamp_parsing and
+        #  _validate_timestamp_format.
+        reminder_time, _, _ = asyncio.run(func)
 
 
 def test_exception_12_hour_clock():
@@ -337,7 +348,7 @@ def test_exception_12_hour_clock():
 
     # Assert
     with pytest.raises(TimestampParseException):
-        reminder_time, _ = asyncio.run(func)
+        reminder_time, _, _ = asyncio.run(func)
 
 
 def test_exception_12_hour_clock_timezone():
@@ -349,6 +360,6 @@ def test_exception_12_hour_clock_timezone():
 
     # Assert
     with pytest.raises(TimestampParseException):
-        reminder_time, _ = asyncio.run(func)
+        reminder_time, _, _ = asyncio.run(func)
 
 # endregion Malformed input

From e1e1050320605bda110092255d3db3b7ef38dac1 Mon Sep 17 00:00:00 2001
From: MysticMia <65396931+MysticMia@users.noreply.github.com>
Date: Sat, 24 May 2025 13:03:36 +0200
Subject: [PATCH 25/26] Fix circular import for permission_checks and move
 is_staff to .checks

is_staff was imported from resources.utils before, but it had been in the resources/checks/ directory, so it makes more sense to have it be imported from resources.checks.__init__ as well.
---
 extensions/crashhandling/cogs/crashhandling.py | 3 ++-
 extensions/help/cogs/helpcommand.py            | 2 +-
 resources/checks/__init__.py                   | 6 ++++--
 resources/checks/permission_checks.py          | 9 +++++++--
 resources/utils/__init__.py                    | 3 ---
 5 files changed, 14 insertions(+), 9 deletions(-)

diff --git a/extensions/crashhandling/cogs/crashhandling.py b/extensions/crashhandling/cogs/crashhandling.py
index 7a6be01..5d2f44f 100644
--- a/extensions/crashhandling/cogs/crashhandling.py
+++ b/extensions/crashhandling/cogs/crashhandling.py
@@ -15,7 +15,8 @@
     CommandDoesNotSupportDMsCheckFailure,
     ModuleNotEnabledCheckFailure, MissingAttributesCheckFailure
 )
-from resources.utils import is_admin, log_to_guild, debug
+from resources.checks import is_admin
+from resources.utils import debug, log_to_guild
 
 
 appcommanderror_cooldown: datetime = datetime.fromtimestamp(0, timezone.utc)
diff --git a/extensions/help/cogs/helpcommand.py b/extensions/help/cogs/helpcommand.py
index a3d24e9..b3b2e93 100644
--- a/extensions/help/cogs/helpcommand.py
+++ b/extensions/help/cogs/helpcommand.py
@@ -3,7 +3,7 @@
 import discord.ext.commands as commands
 
 from resources.customs import Bot
-from resources.utils import is_admin
+from resources.checks import is_admin
 from resources.utils.stringhelper import replace_string_command_mentions
 
 from extensions.help.helppages import help_pages, aliases, FIRST_PAGE
diff --git a/resources/checks/__init__.py b/resources/checks/__init__.py
index 5508138..2f26c93 100644
--- a/resources/checks/__init__.py
+++ b/resources/checks/__init__.py
@@ -6,10 +6,12 @@
     'InsufficientPermissionsCheckFailure',
     'CommandDoesNotSupportDMsCheckFailure',
     'ModuleNotEnabledCheckFailure',
-    'MissingAttributesCheckFailure'
+    'MissingAttributesCheckFailure',
+    'is_staff',
+    'is_admin',
 ]
 
-
+from .permissions import is_staff, is_admin
 from .permission_checks import is_admin_check, is_staff_check
 from .command_checks import not_in_dms_check, module_enabled_check
 from .errors import (
diff --git a/resources/checks/permission_checks.py b/resources/checks/permission_checks.py
index e66b5e4..47e0885 100644
--- a/resources/checks/permission_checks.py
+++ b/resources/checks/permission_checks.py
@@ -1,13 +1,18 @@
+from __future__ import annotations
+from typing import TYPE_CHECKING
+
 import discord
 
-from resources.checks.permissions import is_staff, is_admin
-from resources.customs import Bot
+from .permissions import is_staff, is_admin
 from .errors import (
     CommandDoesNotSupportDMsCheckFailure,
     InsufficientPermissionsCheckFailure
 )
 from .command_checks import is_in_dms
 
+if TYPE_CHECKING:
+    from resources.customs import Bot
+
 
 def is_staff_check(itx: discord.Interaction[Bot]):
     """
diff --git a/resources/utils/__init__.py b/resources/utils/__init__.py
index 8935af5..4e18692 100644
--- a/resources/utils/__init__.py
+++ b/resources/utils/__init__.py
@@ -1,6 +1,4 @@
 __all__ = [
-    'is_staff',
-    'is_admin',
     'replace_string_command_mentions',
     'TimeParser',
     'MissingQuantityException',
@@ -13,7 +11,6 @@
     'codec_options',
 ]
 
-from resources.checks.permissions import is_staff, is_admin
 from .stringhelper import replace_string_command_mentions
 from .timeparser import (
     TimeParser,

From 9f25ff5b29ccc4bb82820d6512c55680c861e235 Mon Sep 17 00:00:00 2001
From: MysticMia <65396931+MysticMia@users.noreply.github.com>
Date: Sat, 24 May 2025 13:04:26 +0200
Subject: [PATCH 26/26] Make flake8 by default ignore the rina_docs directory

---
 .github/workflows/python-app.yml | 2 +-
 setup.cfg                        | 6 ++++--
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml
index dce5685..786c8e3 100644
--- a/.github/workflows/python-app.yml
+++ b/.github/workflows/python-app.yml
@@ -33,7 +33,7 @@ jobs:
           # stop the build if there are Python syntax errors or undefined names
           flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
           # can also be more harsh :D
-          # flake8 . --count --ignore=W503,W504 --show-source --statistics --exclude venv,rina_docs
+          # flake8 . --count --ignore=W503,W504 --show-source --statistics
           # exit-zero treats all errors as warnings.
           flake8 . --count --exit-zero --max-complexity=10 --statistics
       - name: Test with pytest
diff --git a/setup.cfg b/setup.cfg
index dc96c11..a071bb4 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -3,7 +3,9 @@ disable_error_code = import-untyped
 
 [flake8]
 max-line-length = 79
-exclude = venv
+exclude =
+    venv
+    rina_docs
 
 [tool:pytest]
 pythonpath = .
@@ -12,4 +14,4 @@ filterwarnings =
 ; Only necessary in python 3.12. discord.py imports `audioop-lts` in python 3.13 resolving this DeprecationWarning.
 
 [coverage:run]
-omit = */unit_tests/*,*/__init__.py,*/module.py
\ No newline at end of file
+omit = */unit_tests/*,*/__init__.py,*/module.py