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/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 636af37..291679b 100644 --- a/extensions/addons/cogs/funaddons.py +++ b/extensions/addons/cogs/funaddons.py @@ -1,10 +1,13 @@ -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 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 @@ -30,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: @@ -145,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 by the first item in the tuple. + roll_db = sorted([x for x in roll_db.items()]) + details = "You rolled " + for roll, count in roll_db: + details += f"'{roll}'x{count}, " + return details + + class FunAddons(commands.Cog): def __init__(self, client: Bot): self.client = client @@ -167,39 +148,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 +204,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 +216,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( @@ -228,80 +227,54 @@ 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, + 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, ): - hide = False + itx.response: discord.InteractionResponse[Bot] # type: ignore + 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, too_long = await _get_dice_roll_output( + dice, faces, mod) + 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_mention = itx.client.get_command_mention("help") - 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.") + cmd_help = itx.client.get_command_mention_with_args( + "help", page="112") + 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." + ) for char in advanced: 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] @@ -311,32 +284,43 @@ 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" + 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. 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}") + 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 2202c57..d89c72b 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 @@ -157,22 +168,24 @@ 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. 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] -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 @@ -185,11 +198,15 @@ async def _role_autocomplete(itx: discord.Interaction, 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): @@ -204,45 +221,76 @@ async def on_message(self, message: discord.Message): if "celcius" in message.content.lower(): 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. :)") + "['Anders Celsius']" + "(https://en.wikipedia.org/wiki/Anders_Celsius). " + "'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') @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: - 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 - api_key = itx.client.api_tokens['Open Exchange Rates'] + # 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 + "https://openexchangerates.org/api/latest.json", + params=params + ).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() @@ -250,9 +298,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 @@ -264,20 +314,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): @@ -293,35 +357,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: @@ -330,39 +405,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!") - cmd_mention = itx.client.get_command_mention("add_poll_reactions") + await itx.edit_original_response( + content=":warning: Adding emojis failed!" + ) + 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} on message {message.jump_url}") + await log_to_guild( + itx.client, + itx.guild, + f"{itx.user.name} ({itx.user.id}) used {cmd_poll} " + 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, 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) @@ -379,7 +479,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..800f69b 100644 --- a/extensions/addons/cogs/searchaddons.py +++ b/extensions/addons/cogs/searchaddons.py @@ -1,13 +1,21 @@ import json # to read API json responses +import urllib.parse + import requests # to read api calls import discord 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 +from extensions.addons.views.equaldex_additionalinfo import ( + EqualDexAdditionalInfo +) +from extensions.addons.views.math_sendpublicbutton import ( + SendPublicButtonMath +) STAFF_CONTACT_CHECK_WAIT_MIN = 5000 @@ -18,9 +26,15 @@ 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.)") - async def equaldex(self, itx: discord.Interaction, country_id: str): + @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(): if char not in "abcdefghijklmnopqrstuvwxyz": @@ -28,15 +42,25 @@ async def equaldex(self, itx: discord.Interaction, 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", "
": "",
@@ -51,95 +75,137 @@ async def equaldex(self, itx: discord.Interaction, 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'])
 
         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',
-                #                                                        'Legal, '
-                #                                                        'surgery not required',
-                #                                                        "Sexual orientation and gender identity",
-                #                                                        "Recognized"]:
+                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"
+                # ]:
                 #     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
-                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 = \
+                    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")
-    async def math(self, itx: discord.Interaction, query: str):
-        # todo: shorten function / split, and re-investigate the API docs to see if I can parse stuff better
+    @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
         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
+        # 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": urllib.parse.quote(query),  # slashes are safe
+            "output": "json",
+        }
         try:
             data = requests.get(
-                f"https://api.wolframalpha.com/v2/query?appid={api_key}&input={query}&output=json").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 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"]
@@ -152,74 +218,104 @@ async def math(self, itx: discord.Interaction, 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"]
+                        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["values"][value_index]["word"]
+                            assumption_data["${word" + word_id + "}"] \
+                                = 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)
@@ -227,7 +323,8 @@ async def math(self, itx: discord.Interaction, 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"]:
@@ -235,11 +332,18 @@ async def math(self, itx: discord.Interaction, 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()
@@ -257,9 +361,13 @@ async def math(self, itx: discord.Interaction, 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 = {}
@@ -267,39 +375,68 @@ async def math(self, itx: discord.Interaction, 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/equaldexregion.py b/extensions/addons/equaldexregion.py
index 4e90c01..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,4 +26,7 @@ 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']
+        self.issues: dict[
+            str,
+            list[str] | dict[str, dict[str, str]]
+        ] = data['issues']
diff --git a/extensions/addons/roll.py b/extensions/addons/roll.py
new file mode 100644
index 0000000..5840b10
--- /dev/null
+++ b/extensions/addons/roll.py
@@ -0,0 +1,87 @@
+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!")
+    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
+
+
+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/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/addons/views/math_sendpublicbutton.py b/extensions/addons/views/math_sendpublicbutton.py
index b19bad8..087df02 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())
+        cmd_math = itx.client.get_command_mention("math")
+        await itx.followup.send(
+            f"**{itx.user.mention} shared a {cmd_math} output:**\n"
+            + itx.message.content,
+            ephemeral=False,
+            allowed_mentions=discord.AllowedMentions.none()
+        )
diff --git a/extensions/changechannel/cogs/changechannel.py b/extensions/changechannel/cogs/changechannel.py
index ee9c6bb..e126fed 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,11 +16,11 @@ 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
-        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/compliments/cogs/compliments.py b/extensions/compliments/cogs/compliments.py
index 9b8d7b8..6365f18 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,79 @@ 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
+    # 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 "
+    #     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
         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, 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 +217,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 +244,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 +255,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 +275,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())
+            "https://cdn.discordapp.com/emojis/902351699182780468.gif?size=56&quality=lossless",  # noqa
+            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 +302,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 +314,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 +331,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)
+            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 +373,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)
+                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_help}\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 +463,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_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 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_blacklist}.",
                     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_blacklist = 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_blacklist} "
+                    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 94b6e50..cbc4778 100644
--- a/extensions/compliments/views/confirmpronouns.py
+++ b/extensions/compliments/views/confirmpronouns.py
@@ -1,41 +1,77 @@
 import discord
 
+from resources.customs import Bot
+
 
 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, _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, _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, _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, _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, _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 67ccea6..5d2f44f 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
@@ -13,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)
@@ -26,17 +29,21 @@ 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
 
-    :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 +75,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.
@@ -101,27 +110,31 @@ 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.
+    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[Bot]  # 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 +150,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 +160,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 +179,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 +204,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,17 +214,23 @@ 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")
+            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
@@ -216,7 +243,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(
@@ -241,42 +274,67 @@ 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 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_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_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))
+            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_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 "
@@ -290,21 +348,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'):
@@ -312,16 +370,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 +392,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 3b23fc5..dc5cedf 100644
--- a/extensions/customvcs/channel_rename_tracker.py
+++ b/extensions/customvcs/channel_rename_tracker.py
@@ -17,25 +17,43 @@ 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.
 
+    .. 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
         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..4fd4034 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,50 +91,75 @@ 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.
     """
 
     default_name = "Untitled voice chat"
     warning = ""
-    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."
+        )
+        cmd_editvc = client.get_command_mention("editvc")
         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_editvc} 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,82 @@ 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
+                )
+                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"{username}, {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
+                )
+                username = getattr(itx.user, 'nick', None) or itx.user.name
+                await log_to_guild(
+                    itx.client,
+                    itx.guild,
+                    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}"
+                )
+                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 a7e2326..159b42b 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,47 +29,67 @@
 # 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:
-    if guild.guild.default_role not in channel.overwrites:
+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.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:
@@ -70,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.
 
@@ -81,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
-    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,
-        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[Bot] | 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:
@@ -140,30 +186,42 @@ 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! (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_create} 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, 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)
+        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])
@@ -171,12 +229,16 @@ 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 "
-                                            "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,
+            )
+
+        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 = []
@@ -185,19 +247,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_owner}.\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_owner}.\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_owner}.\n"
+                )
                 break
             mention_id = int(mention)
             added_owner_ids.append(mention_id)
@@ -206,9 +277,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_owner}."
+                    )
                     break
 
                 if owner not in added_owners:
@@ -221,32 +295,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:
-                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)
+            if (_is_vc_table_owner(user_vc, target)
+                    and target not in user_vc.category.overwrites):
+                cmd_owner = itx.client.get_command_mention("vctable owner")
+                await itx.followup.send(
+                    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)
                 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
 
@@ -264,7 +348,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(
@@ -273,29 +358,47 @@ async def vctable_create(
 
         # endregion Apply permission overwrites for vctable 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")
+        owner_taglist = ', '.join([f'<@{user_id}>'
+                                   for user_id in added_owners])
+        cmd_owner = itx.client.get_command_mention("vctable about")
+        await user_vc.send(
+            f"CustomVC converted to VcTable\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)",
+            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")
-    async def vctable_disband(self, itx: discord.Interaction):
-        vctable_prefix: str | None = itx.client.get_guild_attribute(itx.guild, AttributeKeys.vctable_prefix)
+    @app_commands.command(
+        name="disband",
+        description="reset permissions and convert vctable back to customvc"
+    )
+    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:
             raise MissingAttributesCheckFailure(
                 ModuleKeys.vc_tables, [AttributeKeys.vctable_prefix])
@@ -303,86 +406,143 @@ 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):
+    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',
-            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"
+        )
+
+        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',
-            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"{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')} `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"{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"{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')} `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 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"{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"{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],
+            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),
         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
+    ):
+        # 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_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 {cmd_owner} "
+                    f"though... If you want to delete the VcTable, you can "
+                    f"use {cmd_disband}.",
+                    ephemeral=True,
+                )
             return
 
         if mode == 1:  # add
@@ -391,20 +551,38 @@ async def edit_vctable_owners(self, itx: discord.Interaction, mode: int, user: d
             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)
 
@@ -413,45 +591,74 @@ async def edit_vctable_owners(self, itx: discord.Interaction, mode: int, user: d
             if channel is None:
                 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)
+                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_owner} "
+                    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, 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
@@ -459,26 +666,47 @@ async def edit_vctable_speakers(self, itx: discord.Interaction, mode: int, user:
             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_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_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 :)",
+                    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_owner = itx.client.get_command_mention(
+                    "vctable make_authorized_only")
+                warning += (f"\nThis has no purpose until you enable "
+                            f"'authorized-only' using {cmd_owner}.")
+            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)
 
@@ -487,33 +715,53 @@ async def edit_vctable_speakers(self, itx: discord.Interaction, mode: int, user:
             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_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_owner} first).\n"
+                    f"To see current VcTable speakers, use {cmd_speaker}.",
+                    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_owner = itx.client.get_command_mention(
+                    "vctable make_authorized_only")
+                warning = (f"\nThis has no purpose until you enable "
+                           f"'authorized-only' using {cmd_owner}.")
+            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)
 
@@ -521,21 +769,39 @@ async def edit_vctable_speakers(self, itx: discord.Interaction, mode: int, user:
             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, 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
@@ -543,70 +809,110 @@ async def edit_vctable_participants(self, itx: discord.Interaction, mode: int, u
             if channel is None:
                 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)
+                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_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 "
+                    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)
+                cmd_owner = itx.client.get_command_mention("vctable lock")
+                warning += (f"\nThis has no purpose until you activate the "
+                            f"'lock' using {cmd_owner}.")
+            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_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 {cmd_owner} "
+                    f"first).\n"
+                    f"To see current VcTable participants, use "
+                    f"{cmd_participant}.",
+                    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)
+                cmd_owner = itx.client.get_command_mention("vctable lock")
+                warning = (f"\nThis has no purpose until you activate the "
+                           f"'lock' using {cmd_owner}.")
+            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)
 
@@ -614,11 +920,16 @@ async def edit_vctable_participants(self, itx: discord.Interaction, mode: int, u
             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),
@@ -627,10 +938,18 @@ 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)
+            await itx.response.send_message(
+                "You can't "
+                + "un" * (mode == 2)
+                + "mute yourself!",
+                ephemeral=True,
+            )
             return
 
         if mode == 1:  # mute
@@ -638,32 +957,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)
 
@@ -672,119 +1014,195 @@ async def edit_vctable_muted_participants(
             if channel is None:
                 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)
+                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_check}\n"
+                    f"Then, use {cmd_mute}\n",
+                    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):
+    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
 
         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))
-        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)
+        view = GenericTwoButtonView(
+            ("Confirm", discord.ButtonStyle.green),
+            ("Cancel", discord.ButtonStyle.red)
+        )
+        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. To whitelist "
+            f"someone, use {cmd_speaker}.",
+            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")
+            cmd_speaker = 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_speaker} "
+                        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):
+    async def vctable_lock(self, itx: discord.Interaction[Bot]):
         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))
-        cmd_mention = itx.client.get_command_mention("vctable participant")
+        view = GenericTwoButtonView(
+            ("Confirm", discord.ButtonStyle.green),
+            ("Cancel", discord.ButtonStyle.red)
+        )
+        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 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: `.",
+            f"To whitelist someone, use {cmd_participant}.",
             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())
-            cmd_mention = itx.client.get_command_mention("vctable participant")
+            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_participant = itx.client.get_command_mention_with_args(
+                "vctable participant", user=" ")
             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 "
+                        f"{cmd_participant} 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..45f67ed 100644
--- a/extensions/customvcs/modals/staffeditor.py
+++ b/extensions/customvcs/modals/staffeditor.py
@@ -1,11 +1,15 @@
 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
 
 
-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,17 +18,27 @@ 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)
 
-    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
@@ -34,9 +48,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 +61,147 @@ 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
+                )
+                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 {username}, "
+                    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>")
+            username = getattr(itx.user, 'nick', None) or itx.user.name
+            await log_to_guild(
+                itx.client,
+                itx.guild,
+                f"Staff: Warning! >> {ex_message} <<"
+                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/emojistats/cogs/emojistats.py b/extensions/emojistats/cogs/emojistats.py
index 545f9af..2c16914 100644
--- a/extensions/emojistats/cogs/emojistats.py
+++ b/extensions/emojistats/cogs/emojistats.py
@@ -16,16 +16,6 @@
 from resources.checks import not_in_dms_check
 
 
-#   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,
@@ -145,7 +135,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, 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 +212,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,
@@ -318,7 +312,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 b8a452f..33fea8c 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, 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, sticker_name: str):
         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, 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):
+    async def get_sticker_top_10(self, itx: discord.Interaction[Bot]):
         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/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/getmemberdata/cogs/memberdata.py b/extensions/getmemberdata/cogs/memberdata.py
index 3f46224..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, 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 ceadb5e..b3b2e93 100644
--- a/extensions/help/cogs/helpcommand.py
+++ b/extensions/help/cogs/helpcommand.py
@@ -2,26 +2,36 @@
 import discord.app_commands as app_commands
 import discord.ext.commands as commands
 
-from resources.utils import is_admin
+from resources.customs import Bot
+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
-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, 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
@@ -29,19 +39,32 @@ async def send_help_menu(itx: discord.Interaction, requested_page: int = FIRST_P
         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, 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 = []
 
@@ -55,18 +78,22 @@ async def _help_page_autocomplete(itx: discord.Interaction, current: str) -> lis
             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:
@@ -77,14 +104,17 @@ async def _help_page_autocomplete(itx: discord.Interaction, current: str) -> lis
 
     # 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
 
@@ -95,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, 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, 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 c68a37d..c2266a1 100644
--- a/extensions/help/views/helppage.py
+++ b/extensions/help/views/helppage.py
@@ -1,18 +1,25 @@
 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
 
-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
 
 
-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)
+        embed = generate_help_page_embed(
+            self.pages[page_key], page_key, itx.client)
         await itx.response.edit_message(
             embed=embed,
             view=view
@@ -20,13 +27,21 @@ 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",
@@ -46,27 +61,40 @@ async def jump_to_page(self, itx: discord.Interaction, _: 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)
@@ -75,7 +103,12 @@ async def jump_to_page(self, itx: discord.Interaction, _: 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)):
@@ -84,5 +117,9 @@ 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
+        )
+        # 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..0a6aa9a 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,26 +66,36 @@ 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")
 
         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/nameusage/cogs/nameusage.py b/extensions/nameusage/cogs/nameusage.py
index 8a84201..1162bf7 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
@@ -6,20 +8,36 @@
 
 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"):
+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, 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:
@@ -58,7 +76,12 @@ async def nameusage_gettop(self, itx: discord.Interaction, 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
 
@@ -74,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])
@@ -106,31 +133,38 @@ 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"
+        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", 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, 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 = ""
@@ -148,11 +182,14 @@ async def nameusage_name(self, itx: discord.Interaction, name: str, search_type:
         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..86943ce 100644
--- a/extensions/nameusage/modals/getnamemodal.py
+++ b/extensions/nameusage/modals/getnamemodal.py
@@ -1,46 +1,45 @@
 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):
-        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.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.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",
+            # 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()
+    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! Perhaps nobody chose this name!",
+                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 37cc598..1cd829a 100644
--- a/extensions/nameusage/views/pageview.py
+++ b/extensions/nameusage/views/pageview.py
@@ -1,54 +1,64 @@
 import discord
 
 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)
         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.
-    @discord.ui.button(label='Previous', style=discord.ButtonStyle.blurple)
-    async def previous(self, itx: discord.Interaction, _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)
+    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='Next', style=discord.ButtonStyle.blurple)
-    async def next(self, itx: discord.Interaction, _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)
-
-    @discord.ui.button(label='Find my name', style=discord.ButtonStyle.blurple)
-    async def find_name(self, itx: discord.Interaction, _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)
diff --git a/extensions/qotw/cogs/devrequest.py b/extensions/qotw/cogs/devrequest.py
index 7f5a022..e2bfe2f 100644
--- a/extensions/qotw/cogs/devrequest.py
+++ b/extensions/qotw/cogs/devrequest.py
@@ -22,31 +22,39 @@ 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]):
-        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)
-        if developer_request_channel is None or developer_role is None:
-            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]
-            raise MissingAttributesCheckFailure(ModuleKeys.dev_requests, missing)
+    async def developer_request(
+            self,
+            itx: discord.Interaction[Bot],
+            suggestion: app_commands.Range[str, 25, 1500]
+    ):
+        developer_request_channel: discord.TextChannel | None = \
+            itx.client.get_guild_attribute(
+                itx.guild,
+                AttributeKeys.developer_request_channel,
+            )
+        if developer_request_channel is None:
+            raise MissingAttributesCheckFailure(
+                ModuleKeys.dev_requests,
+                [AttributeKeys.developer_request_channel],
+            )
 
         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,37 +66,58 @@ 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
         # 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_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}>")
+            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()
         )
+        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
         )
@@ -97,14 +126,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 +157,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 +173,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 +187,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_ping = itx.client.get_command_mention(
+                        "ping_open_dev_requests")
+                    await thread.send(
+                        itx.user.mention
+                        + 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.",
+                        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..a1f7ec0 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,16 +71,23 @@ 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()
         )
+        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
         )
@@ -74,5 +97,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 52566e9..8eb44f6 100644
--- a/extensions/reminders/cogs/reminders.py
+++ b/extensions/reminders/cogs/reminders.py
@@ -2,18 +2,32 @@
 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"):
     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, 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"
@@ -24,21 +38,117 @@ async def remindme(self, itx: discord.Interaction, reminder_datetime: str, remin
         # - ""
         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
-        await parse_and_create_reminder(itx, reminder_datetime, reminder)
 
-    @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):
+        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:
+            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_help}.",
+                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_help}.',
+                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_help}.",
+                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 "
+                f"like \"4 days\".\n"
+                f"For more info, use {cmd_help}.",
+                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 "
+                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_help}.",
+                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)"
+    )
+    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")
+            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!\nUse {cmd_mention} to make a reminder!",
+                f"You don't have any reminders running at the moment!\n"
+                f"Use {cmd_reminders} to make a reminder!",
                 ephemeral=True)
             return
 
@@ -47,79 +157,115 @@ async def reminders(self, itx: discord.Interaction, 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_reminders = itx.client.get_command_mention_with_args(
+                        "reminder reminders", item=" ")
+                    out_msg = (
+                        (f"You have {len(db_data['reminders'])} reminders "
+                         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)
             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")
+            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 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_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.\nUse {cmd_mention} to make a reminder!",
-                ephemeral=True)
+                f"You don't have any reminders running at the moment.\n"
+                f"Use {cmd_reminders} 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)")
-    async def remove(self, itx: discord.Interaction, item: int):
+    @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}
         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 I can't remove any either..)\n"
-                f"Use {cmd_mention} to make a reminder!",
+                f"You don't have any reminders running at the moment! (so "
+                f"I can't remove any either..)\n"
+                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 on the reminder list ({cmd_mention})",
+                f"Look for the \"ID: `0`\" at the beginning of your reminder "
+                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 I can't remove any either..)\n"
-                f"Use {cmd_mention} to make a reminder!",
+                f"You don't have any reminders running at the moment. (so "
+                f"I can't remove any either..)\n"
+                f"Use {cmd_remindme} 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 7003c3e..c2eb550 100644
--- a/extensions/reminders/objects/reminderobject.py
+++ b/extensions/reminders/objects/reminderobject.py
@@ -1,16 +1,17 @@
-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
 
 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
@@ -72,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_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_dev_request}."
+                    # "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:
@@ -97,94 +112,92 @@ 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)
+        db_data: DatabaseData | None = 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)
 
 
 async def _handle_reminder_timestamp_parsing(
-        itx: discord.Interaction,
+        itx: discord.Interaction[Bot],
         reminder_datetime: str
-) -> (datetime, discord.Interaction):
-    # validate format
-    # note: "t" here is lowercase because the reminder_datetime string
-    #  gets lowercased...
-    has_timezone = False
-    if reminder_datetime.count("t") == 1:
-        # 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.")
-
-        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")
-
-        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
-            time = time.split("+")[0]
-            time = time.split("-")[0]
-        if time.count(":") == 1:
-            mode = TimestampFormats.DateTimeToMinutes
-        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")
-
-    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'. :)")
-    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")
-        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'.")
+) -> tuple[datetime, discord.Interaction[Bot]]:  # todo: docstring
+    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]
+    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}"
+        )
+
+    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, like a year before "
+            "1970 or after 3001."
+        )
 
     distance = timestamp
+
     # clarify datetime timezone if necessary
     if mode == TimestampFormats.DateNoTime:
         options = {
@@ -208,6 +221,10 @@ async def _handle_reminder_timestamp_parsing(
             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
 
@@ -215,14 +232,87 @@ async def _handle_reminder_timestamp_parsing(
     return distance, itx
 
 
-async def _parse_reminder_time(itx: discord.Interaction, reminder_datetime: str) -> tuple[datetime, datetime]:
+def _validate_timestamp_format(reminder_datetime: str) -> TimestampFormats:
     """
-    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.
+    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
+    if reminder_datetime.count("t") == 1:
+        # 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.")
 
-    :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.
+        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"
+            )
+
+        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
+            time = time.split("+")[0]
+            time = time.split("-")[0]
+        if time.count(":") == 1:
+            mode = TimestampFormats.DateTimeToMinutes
+        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")
+
+    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'. "
+            ":)"
+        )
+    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")
+        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'."
+        )
+    return mode
+
+
+async def _parse_reminder_time(
+        itx: discord.Interaction[Bot],
+        reminder_datetime: str
+) -> tuple[datetime, datetime, discord.Interaction[Bot]]:
+    # 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.
+
+    :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)
 
@@ -232,15 +322,25 @@ async def _parse_reminder_time(itx: discord.Interaction, reminder_datetime: str)
     :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:
@@ -253,49 +353,71 @@ async def _parse_reminder_time(itx: discord.Interaction, reminder_datetime: str)
             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)
 
-    return distance, creation_time
+    return distance, creation_time, itx
 
 
 async def _create_reminder(
-        itx: discord.Interaction,
+        itx: discord.Interaction[Bot],
         distance: datetime,
         creation_time: datetime, reminder: str,
         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")
+    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  for \"{reminder}\"!\n"
-            f"Use {cmd_mention} to see your list of reminders",
+            f"Successfully created a reminder for you on  "
+            f"for \"{reminder}\"!\n"
+            f"Use {cmd_reminders} 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"Use {cmd_mention} to see your list of reminders",
+            f"Successfully created a reminder for you on  "
+            f"for \"{reminder}\"!\n"
+            f"Use {cmd_reminders} 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,
@@ -305,7 +427,7 @@ async def _create_reminder(
 
 
 async def parse_and_create_reminder(
-        itx: discord.Interaction,
+        itx: discord.Interaction[Bot],
         reminder_datetime: str,
         reminder: str
 ):
@@ -317,81 +439,24 @@ 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")
-        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
+        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_reminders} "
+            f"to see your reminders, and use {cmd_remove} to remove a "
+            f"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, itx = await _parse_reminder_time(
+        itx, reminder_datetime)
+
+    await _create_reminder(
+        itx,
+        distance,
+        creation_time,
+        reminder,
+        user_reminders,
+        False
+    )
diff --git a/extensions/reminders/views/copyreminder.py b/extensions/reminders/views/copyreminder.py
index 9faaa80..0d46da4 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
 
@@ -13,37 +14,52 @@
 
 
 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.return_interaction: discord.Interaction[Bot] | 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))
+    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")
-            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)
+            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 "
+                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/reminders/views/sharereminder.py b/extensions/reminders/views/sharereminder.py
index 9af2b80..bcf15ef 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
 
 
@@ -8,10 +9,14 @@ def __init__(self, timeout=300):
         super().__init__()
         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.return_interaction: discord.Interaction[Bot] | None = None
+        self.add_item(create_simple_button(
+            "Share reminder in chat",
+            discord.ButtonStyle.gray,
+            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/reminders/views/timeofdayselection.py b/extensions/reminders/views/timeofdayselection.py
index b9c5d0b..e0dc179 100644
--- a/extensions/reminders/views/timeofdayselection.py
+++ b/extensions/reminders/views/timeofdayselection.py
@@ -1,22 +1,36 @@
 import discord
 
 from resources.views.generics import create_simple_button
+from resources.customs import Bot
 
 
 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:
-            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, 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..bf050f9 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)
@@ -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,17 +647,20 @@ 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
+    #         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)
-    #     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.")
     #
@@ -723,16 +727,17 @@ 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,
             value: str | None = None
     ):
-        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."
+        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."
 
         try:
             modify_mode: ModeAutocomplete | None = None
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/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 78b5b4d..3b96cb2 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, 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,110 @@ 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
-        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]
+        # noinspection LongLine
+        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])
         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..8787e53 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,7 +106,8 @@ async def _send_starboard_message(
         description=f"{message.content}",
         timestamp=message.created_at  # this, or datetime.now()
     )
-    msg_link = f"https://discord.com/channels/{message.guild.id}/{message.channel.id}/{message.id}"
+    # noinspection LongLine
+    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):
@@ -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
 
-            broken_link = f"https://discord.com/channels/{payload.guild_id}/{payload.channel_id}/{payload.message_id}"
+            # noinspection LongLine
+            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),
-                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:  # noqa
+            # 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 41e9aa5..b3348c5 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
@@ -23,8 +23,9 @@
 )
 
 
-# to prevent excessive spamming when multiple people mention staff. A sorta cooldown
-report_message_reminder_unix = 0  # int(datetime.now().timestamp())
+# To prevent excessive spamming when multiple people mention staff.
+#  A sort of cooldown
+report_message_reminder = datetime.min
 
 
 def _get_enabled_tag_ids(itx) -> set[str]:
@@ -34,7 +35,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")]
@@ -99,8 +100,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 +120,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")
@@ -133,7 +136,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
@@ -186,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:
@@ -217,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 7bd1b92..c109a19 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
 
@@ -52,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 "")
         )
@@ -86,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(
@@ -97,7 +98,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 +140,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:
@@ -152,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)
@@ -188,7 +189,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(
@@ -228,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/__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 6e5b921..7651481 100644
--- a/extensions/termdictionary/cogs/termdictionary.py
+++ b/extensions/termdictionary/cogs/termdictionary.py
@@ -1,560 +1,409 @@
-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 discord
 import discord.app_commands as app_commands
 import discord.ext.commands as commands
 
-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
-
-from extensions.termdictionary.views import DictionaryApi_PageView, UrbanDictionary_PageView
-
-
-del_separators_table = str.maketrans({" ": "", "-": "", "_": ""})
-
-
-async def dictionary_autocomplete(itx: discord.Interaction, 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 == '':
-        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"])
+import aiohttp
+import asyncio
 
-    # 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])
+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
 
-        # 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])
-
-    # 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:
-        response_api = requests.get(f'https://api.urbandictionary.com/v0/define?term={current}').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])")
-
-    # limit choices to the first 7
-    terms = terms[:7]
-
-    return [
-        app_commands.Choice(name=term, value=term.replace(" ([from UD])", ""))
-        for term in terms
-    ]
+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
 
 
 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)")
+        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!")
+    @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='All',
+            value=DictionarySources.All.value
+        ),
+        # discord.app_commands.Choice(
+        #     name='Custom dictionary',
+        #     value=DictionarySources.CustomDictionary.value
+        # ),
+        discord.app_commands.Choice(
+            name='en.pronouns.page',
+            value=DictionarySources.PronounsPage.value
+        ),
+        discord.app_commands.Choice(
+            name='dictionaryapi.dev',
+            value=DictionarySources.DictionaryApi.value
+        ),
+        discord.app_commands.Choice(
+            name='urbandictionary.com',
+            value=DictionarySources.UrbanDictionary.value
+        ),
     ])
-    @app_commands.autocomplete(term=dictionary_autocomplete)
-    async def dictionary(self, itx: discord.Interaction, 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.
-        #  - 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]
-        # 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
+    async def dictionary(
+            self,
+            itx: discord.Interaction[Bot],
+            term: str,
+            public: bool = False,
+            source: DictionarySources = DictionarySources.All,
+    ):
+        itx.response: discord.InteractionResponse[Bot]  # type: ignore
+        await itx.response.defer(ephemeral=not public)
+
+        sources = self._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
+        ])
+
+        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
 
-            # edit definitions to hide links to other pages:
+        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})"
+        )
+
+    @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:
-                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)
+                source = itx.namespace.source
 
-                # 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
+            sources = [
+                dictionary for dictionary in self._dictionary_sources[:]
+                if dictionary[0] == source
+            ]
 
-                # 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"")
-        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
+        # fetch autocompletion results
+        async def fetch_with_timeout(dictionary: DictionaryBase):
             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:
-                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]
-
-                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)
-                return
-        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
-            # 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_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})")
-                else:
-                    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)
-
-                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)
-                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)
-
-    admin = app_commands.Group(name='dictionary_staff', description='Change custom entries in the dictionary')
+                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)
-    @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, 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]
-
+        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}
         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
         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"
+            warnings += (f"You have already given a synonym before "
+                         f"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, 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)
         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}.",
-                ephemeral=True)
+                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}})
 
-        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")
-    async def undefine(self, itx: discord.Interaction, term: str):
+        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, 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_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"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..fad0c5d
--- /dev/null
+++ b/extensions/termdictionary/dictionaries/CustomDictionary.py
@@ -0,0 +1,127 @@
+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_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()}
+        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_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_api_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..f7840b7
--- /dev/null
+++ b/extensions/termdictionary/dictionaries/DictionaryApiDictionary.py
@@ -0,0 +1,163 @@
+import discord
+
+import aiohttp
+import json
+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, session: aiohttp.ClientSession):
+        super().__init__()
+        self._session = session
+        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
+
+    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
+        )
+        async with self._session.get(url) as response:
+            response_api = await response.text()
+
+        try:
+            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
+
+    @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)
+
+        if public:
+            self._view.delete_extra_buttons()
+
+        await itx.followup.send(
+            embed=self._embed,
+            view=self._view
+        )
+        await self._view.wait()
+        try:
+            await itx.edit_original_response(view=None)
+        except discord.NotFound:
+            # message was deleted?
+            pass
+
+    @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..8ac89c9
--- /dev/null
+++ b/extensions/termdictionary/dictionaries/PronounsPageDictionary.py
@@ -0,0 +1,292 @@
+import discord
+
+import aiohttp
+import json
+import re  # to parse and remove https:/pronouns.page/ in-text page linking
+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, session: aiohttp.ClientSession) -> None:
+        super().__init__()
+        self._session = session
+        self._result_str: str | None = None
+        self._result_count = 0
+        self._character_overflow = False
+        self._expand_search = False
+        self._term: str | None = None
+
+    @staticmethod
+    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
+    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
+    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
+    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
+    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
+
+    async def _get_api_response(
+            self,
+            term: str
+    ) -> list[PronounsPageEntry]:
+        http_safe_term = term.lower().replace("/", " ").replace("%", " ")
+        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
+
+    @override
+    async def get_autocomplete(self, current: str) -> set[str]:
+        data = await self._get_api_response(current)
+        # find exact results online (or synonyms)
+        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].capitalize())
+
+        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 = self._clean_pronouns_page_response_links(data)
+        self._result_count = len(search)
+        results = self._select_exact_items(search, term)
+
+        if len(results) > 0:
+            # there are results that match exactly.
+            try:
+                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)
+            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 = self._get_expand_big_search_string(
+                start_string, search)
+            self._expand_search = True
+        elif len(search) > 2:
+            self._result_str = 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
+
+        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(),
+        )
diff --git a/extensions/termdictionary/dictionaries/UrbanDictionary.py b/extensions/termdictionary/dictionaries/UrbanDictionary.py
new file mode 100644
index 0000000..7994fea
--- /dev/null
+++ b/extensions/termdictionary/dictionaries/UrbanDictionary.py
@@ -0,0 +1,166 @@
+
+import discord
+
+import aiohttp
+from datetime import datetime
+import json
+import math
+from typing import override
+
+from extensions.termdictionary.dictionaries import DictionaryBase
+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
+        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
+    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
+            )
+            embed.set_footer(text=f"page: {len(pages) + 1} / {len(data)}")
+            pages.append(embed)
+        return pages
+
+    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":[]}
+
+    @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() + 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
+
+        self._pages = 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)
+
+        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)}")
+
+        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()
+        try:
+            await itx.edit_original_response(view=None)
+        except discord.NotFound:
+            # message was deleted?
+            pass
+
+    @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/dictionaries/objects/CustomDictionaryEntry.py b/extensions/termdictionary/dictionaries/objects/CustomDictionaryEntry.py
new file mode 100644
index 0000000..734ad34
--- /dev/null
+++ b/extensions/termdictionary/dictionaries/objects/CustomDictionaryEntry.py
@@ -0,0 +1,7 @@
+from typing import 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..9800d85
--- /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..bee939a
--- /dev/null
+++ b/extensions/termdictionary/dictionaries/objects/__init__.py
@@ -0,0 +1,19 @@
+__all__ = [
+    "CustomDictionaryEntry",
+    "PronounsPageEntry",
+    "DictionaryApiEntry",
+    "DetailedTermPage",
+    "term_page_to_embed",
+    "get_term_lines",
+    "UrbanDictionaryEntry",
+]
+
+from .CustomDictionaryEntry import CustomDictionaryEntry
+from .PronounsPageEntry import PronounsPageEntry
+from .DictionaryApiEntry import (
+    DictionaryApiEntry,
+    DetailedTermPage,
+    term_page_to_embed,
+    get_term_lines,
+)
+from .UrbanDictionaryEntry import UrbanDictionaryEntry
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
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 2a61e80..5d93c7e 100644
--- a/extensions/termdictionary/modals/dictionaryapi_sendpagemodal.py
+++ b/extensions/termdictionary/modals/dictionaryapi_sendpagemodal.py
@@ -1,36 +1,53 @@
 import discord
 
+from resources.customs import Bot
 
-class DictionaryAPISendPageModal(discord.ui.Modal, title="Share single dictionary entry?"):
-    def __init__(self, entries, timeout=None):
+
+class DictionaryAPISendPageModal(
+        discord.ui.Modal,
+        title="Share single dictionary entry?"
+):
+    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.question_text = discord.ui.TextInput(label='Entry index',
-                                                  placeholder=f"[A number from 0 to {len(entries) - 1} ]",
-                                                  # style=discord.TextStyle.short,
-                                                  # required=True
-                                                  )
+        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.add_item(self.question_text)
 
-    async def on_submit(self, itx: discord.Interaction):
-        self.value = 9  # failed; placeholder
+    async def on_submit(  # type: ignore
+            self,
+            itx: discord.Interaction[Bot]
+    ):
         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
-        await itx.response.send_message("Sending item...", ephemeral=True, delete_after=8)
+        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..58f3774 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 .dictionaryapi import DictionaryapiPageview
+from .urbandictionary import UrbanDictionaryPageView
diff --git a/extensions/termdictionary/views/dictionaryapi.py b/extensions/termdictionary/views/dictionaryapi.py
new file mode 100644
index 0000000..3da0740
--- /dev/null
+++ b/extensions/termdictionary/views/dictionaryapi.py
@@ -0,0 +1,93 @@
+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) - 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(
+            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 7ed0c78..0000000
--- a/extensions/termdictionary/views/dictionaryapi_pageview.py
+++ /dev/null
@@ -1,65 +0,0 @@
-import discord
-
-from extensions.termdictionary.modals import DictionaryAPISendPageModal
-
-
-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, _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, _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, _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, _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..7651e61
--- /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) - 1,
+            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 c31f6bf..0000000
--- a/extensions/termdictionary/views/urbandictionary_pageview.py
+++ /dev/null
@@ -1,34 +0,0 @@
-import discord
-
-
-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, _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, _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)
diff --git a/extensions/testing_commands/cogs/testingcommands.py b/extensions/testing_commands/cogs/testingcommands.py
index 53b297f..db0b504 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, 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, 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, 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),
@@ -151,19 +185,35 @@ async def update_test_page(itx1: discord.Interaction, view1: PageView):
             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!")
+            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, 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[Bot]  # 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 dd4054a..6abf2d3 100644
--- a/extensions/todolist/cogs/todolist.py
+++ b/extensions/todolist/cogs/todolist.py
@@ -2,31 +2,51 @@
 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):
         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, 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)
+                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_todo}!",
+                    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}
@@ -36,59 +56,87 @@ async def todo(self, itx: discord.Interaction, 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")
+                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. 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_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.",
+                    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"]
 
             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 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_todo} "
+                    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)
@@ -97,4 +145,7 @@ async def todo(self, itx: discord.Interaction, 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 a10fde3..d93b819 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"],
@@ -265,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)
 
@@ -311,20 +313,31 @@ 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,
+            itx: discord.Interaction[Bot],
             mode: int,
             string: str,
             public: bool = False
@@ -347,7 +360,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 6b5c877..f482700 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"):
@@ -77,10 +89,17 @@ 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}>")
         await joiner_msg.delete()
@@ -118,20 +137,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 +164,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 +195,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 +243,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 +254,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 +264,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 +284,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 +322,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, 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)
 
@@ -312,12 +357,15 @@ 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(
-        _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)
 
@@ -337,7 +385,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
@@ -348,10 +396,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 +411,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 +438,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 +513,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/__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/extensions/watchlist/modals/watchlistreasonmodal.py b/extensions/watchlist/modals/watchlistreasonmodal.py
index 6a00c25..73748fc 100644
--- a/extensions/watchlist/modals/watchlistreasonmodal.py
+++ b/extensions/watchlist/modals/watchlistreasonmodal.py
@@ -1,16 +1,21 @@
 import discord
 import typing
 
+from resources.customs import Bot
+
 
 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.
@@ -20,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,
@@ -36,14 +41,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):
+    async def on_submit(self, itx: discord.Interaction[Bot]):
         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..bb6719d 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,16 +87,19 @@ 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...")
+    load_progress.begin("Loading api keys...")
     try:
         with open("api_keys.json", "r") as f:
             api_keys = json.loads(f.read())
@@ -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,28 +118,34 @@ 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...")
+    load_progress.begin("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)
+    load_progress.complete("Loaded database clusters", newline=False)
     return bot_token, tokens, rina_db, async_rina_db
 
 
 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.
     """
-    load_progress.progress("Loading version...")
+    load_progress.begin("Loading version...")
     file_version = BOT_VERSION.split(".")
     try:
         os.makedirs("outputs", exist_ok=True)
@@ -152,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
 
 
@@ -162,13 +174,17 @@ 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  # 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,12 +194,13 @@ 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),
     )
-    start_progress.step("Created Bot")
+    start_progress.complete("Created Bot")
     return bot
 
 
@@ -191,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
@@ -200,68 +217,77 @@ 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")
 
-        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)
 
-        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()
         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})")
-        start_progress.progress("Loading server settings...")
+            extension_load_progress.begin(f"Loading {EXTENSIONS[extID]}")
+            await client.load_extension(
+                "extensions." + EXTENSIONS[extID] + ".module")
+        start_progress.complete(
+            f"Loaded extensions successfully (in "
+            f"{datetime.now().astimezone() - extension_loading_start_time})"
+        )
+        start_progress.begin("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).
-        start_progress.step("Loaded server settings")
-        start_progress.progress("Restarting ongoing reminders...")
+        # 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.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
 
@@ -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/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
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/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..47e0885 100644
--- a/resources/checks/permission_checks.py
+++ b/resources/checks/permission_checks.py
@@ -1,33 +1,50 @@
+from __future__ import annotations
+from typing import TYPE_CHECKING
+
 import discord
 
-from resources.checks.permissions import is_staff, is_admin
-from .errors import CommandDoesNotSupportDMsCheckFailure, InsufficientPermissionsCheckFailure
+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):
+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.
+    :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")
 
 
-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.
-    :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.
+    :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")
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..189b7c3 100644
--- a/resources/customs/bot.py
+++ b/resources/customs/bot.py
@@ -1,13 +1,17 @@
 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 typing import TYPE_CHECKING, TypeVar
 
 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 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 +26,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
@@ -62,46 +67,80 @@ 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(
+            self,
+            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}`"
+
+        return command_mention
+
     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.
         :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 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!")
 
-        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 +158,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 +184,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 +221,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..bfbd394 100644
--- a/resources/customs/progressbar.py
+++ b/resources/customs/progressbar.py
@@ -2,29 +2,40 @@
 
 
 class ProgressBar:
-    def __init__(self, max_steps: int, *, fill_char='#', progress_char='+', empty_char=' '):
-        self._max_steps = max_steps
-        self._step = 0
-        self._fill_char = fill_char
-        self._progress_char = progress_char
+    def __init__(
+            self,
+            max_completes: int,
+            *,
+            complete_char: str = '#',
+            begin_char: str = '+',
+            empty_char: str = ' '
+    ):
+        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:
         """
-        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:`_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
@@ -33,41 +44,51 @@ 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.
-        :param string: The text of the upcoming progress message.
-        :return: A string with spaces to overwrite the previous progress message.
+        A helper function to get the amount of spaces necessary to
+        overwrite the previous 'begin' message.
+
+        :param string: The text of the upcoming 'begin' message.
+        :return: A string with spaces to overwrite the previous
+         '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`.
+        Print a debug log, incrementing the progress bar with
+        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.
+        :param newline: Whether to place a newline after the progress
+         bar, or reset the caret to overwrite this line with the next
+         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`.
+        Print a debug log, incrementing the progress bar with
+        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.
+        :param newline: Whether to place a newline after the progress
+         bar, or reset the caret to overwrite this line with the next
+         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/resources/modals/generics.py b/resources/modals/generics.py
index 9f39711..20c04d8 100644
--- a/resources/modals/generics.py
+++ b/resources/modals/generics.py
@@ -1,16 +1,19 @@
 import discord
 
+from resources.customs import Bot
+
 
 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
 
-    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/__init__.py b/resources/utils/__init__.py
index 0b6aebb..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',
@@ -10,12 +8,15 @@
     '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 .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/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..f8ef44f 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,25 +149,31 @@ 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(
-        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.
 
-    :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 b9fdf04..58bf9fe 100644
--- a/resources/views/generics.py
+++ b/resources/views/generics.py
@@ -1,14 +1,19 @@
 from __future__ import annotations
+
+from abc import abstractmethod
+
 import discord
 import typing
 
+from resources.customs import Bot
+
 
 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:
@@ -19,7 +24,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.
     """
@@ -32,73 +38,176 @@ 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] = ("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):
+    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()
 
 
 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]:
         """
-        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
-        return (
-            discord.ButtonStyle.gray if self.page == 0 else discord.ButtonStyle.blurple,
-            self.page == 0 and not self.loop_around_pages
-        )
+        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.
+        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
 
-    async def update_page(self, itx: discord.Interaction, view: PageView):
+    @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.
 
-        :param itx: The interaction gained from the button or modal interaction by the user.
+        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 view: The view class instance.
         """
         pass
@@ -107,20 +216,19 @@ 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,
+            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:
-            # 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 = []
         self.page: int = starting_page
-        self.update_page = page_update_function
         self.loop_around_pages = loop_around_pages
 
         self.max_page_index: int = max_page_index
@@ -128,19 +236,30 @@ 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], 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)
 
         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:
@@ -149,7 +268,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:
@@ -160,11 +279,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/setup.cfg b/setup.cfg
index b2ac6a3..a071bb4 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -2,8 +2,10 @@
 disable_error_code = import-untyped
 
 [flake8]
-max-line-length = 120
-exclude = venv
+max-line-length = 79
+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
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)
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..353ff0a 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
@@ -39,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
 
@@ -53,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
 
@@ -68,28 +76,36 @@ 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)
+    expected_time = (current_time + timedelta(
+        days=500, hours=1, minutes=1, seconds=1))
 
     # Assert
+    assert itx is itx2
     assert expected_time == reminder_time
 
 
 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)
+    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
+    )
+    expected_time = intermediate_expected_time + timedelta(
+        days=500 + 50 * 7, hours=30, minutes=100, seconds=100)
 
     # Assert
+    assert itx is itx2
     assert expected_time == reminder_time
 
 
@@ -103,19 +119,22 @@ 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
 
 
 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")
@@ -126,30 +145,37 @@ 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 reminder_time1.astimezone() == reminder_time2.astimezone() + timedelta(hours=1)
+    assert itx is itx2 is itx3
+    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)
     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 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 itx is itx2 is itx3
+    assert reminder_time1.astimezone() \
+           == reminder_time2.astimezone() + timedelta(hours=1)
 
 
 def test_iso_date_matches_unix_timestamp():
@@ -157,16 +183,19 @@ 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)
 
     # 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
@@ -183,55 +212,55 @@ 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():
     # 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)
 
     # Assert
     with pytest.raises(TimestampParseException):
-        reminder_time, _ = asyncio.run(func)
+        reminder_time, _, _ = asyncio.run(func)
 
 
 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)
 
     # Assert
     with pytest.raises(TimestampParseException):
-        reminder_time, _ = asyncio.run(func)
+        reminder_time, _, _ = asyncio.run(func)
 
 
 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)
 
     # Assert
     with pytest.raises(TimestampParseException):
-        reminder_time, _ = asyncio.run(func)
+        reminder_time, _, _ = asyncio.run(func)
 
 
 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)
 
     # Assert
     with pytest.raises(TimestampParseException):
-        reminder_time, _ = asyncio.run(func)
+        reminder_time, _, _ = asyncio.run(func)
 
 
 def test_malformed_year():
@@ -243,47 +272,52 @@ def test_malformed_year():
 
     # Assert
     with pytest.raises(TimestampParseException):
-        reminder_time, _ = asyncio.run(func)
+        reminder_time, _, _ = asyncio.run(func)
 
 
 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)
 
     # Assert
     with pytest.raises(TimestampParseException):
-        reminder_time, _ = asyncio.run(func)
+        reminder_time, _, _ = asyncio.run(func)
 
 
 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)
+        reminder_time, _, _ = asyncio.run(func)
 
 
 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.
-        reminder_time, _ = asyncio.run(func)
+        # 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)
 
 
 def test_exception_iso_date():
@@ -295,33 +329,37 @@ 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.
-        reminder_time, _ = asyncio.run(func)
+        # 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.
+        # 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():
     # 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)
 
     # Assert
     with pytest.raises(TimestampParseException):
-        reminder_time, _ = asyncio.run(func)
+        reminder_time, _, _ = asyncio.run(func)
 
 
 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)
 
     # Assert
     with pytest.raises(TimestampParseException):
-        reminder_time, _ = asyncio.run(func)
+        reminder_time, _, _ = asyncio.run(func)
 
 # endregion Malformed input
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..427783d 100644
--- a/unit_tests/resources/customs/test_progressbar.py
+++ b/unit_tests/resources/customs/test_progressbar.py
@@ -22,25 +22,28 @@ 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
 
 
-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]
 
     # 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)
@@ -49,19 +52,21 @@ 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]
 
     # 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 (and newline)."
 
     # Act
     message = get_original_message(message)
@@ -70,19 +75,21 @@ 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]
 
     # 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 (and newline)."
 
     # Act
     message = get_original_message(message)
@@ -91,20 +98,22 @@ 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]
 
     # 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)
@@ -113,20 +122,22 @@ 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]
 
     # 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 (and newline)."
 
     # Act
     message = get_original_message(message)
@@ -136,21 +147,23 @@ 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]
 
     # 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)
@@ -160,21 +173,23 @@ 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]
 
     # 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)
@@ -183,20 +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
-    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."
+    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 newline)."
 
     # Act
     message = get_original_message(message)
@@ -208,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])
 
@@ -236,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])
 
@@ -250,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])
 
@@ -267,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("")
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