diff --git a/techsupport_bot/bot.py b/techsupport_bot/bot.py index 33c626fa2..f43a8ffb7 100644 --- a/techsupport_bot/bot.py +++ b/techsupport_bot/bot.py @@ -37,6 +37,13 @@ class TechSupportBot(commands.Bot): the bot needs to request from discord allowed_mentions (discord.AllowedMentions): What the bot is, or is not, allowed to mention + + Attrs: + CONFIG_PATH (str): The hard coded path to the yaml config file + EXTENSIONS_DIR_NAME (str): The hardcoded folder for commands + EXTENSIONS_DIR (str): The list of all files in the EXTENSIONS_DIR_NAME folder + FUNCTIONS_DIR_NAME (str):The hardcoded folder for functions + FUNCTIONS_DIR (str):The list of all files in the FUNCTIONS_DIR_NAME folder """ CONFIG_PATH: str = "./config.yml" @@ -284,6 +291,9 @@ async def create_new_context_config(self: Self, guild_id: str) -> munch.Munch: Args: guild_id (str): The guild ID the config will be for. Only used for storing the config + + Returns: + munch.Munch: The new config object ready to use """ extensions_config = munch.DefaultMunch(None) @@ -385,6 +395,10 @@ async def get_log_channel_from_guild( Args: guild (discord.Guild): the guild object to reference key (str): the key to use when looking up the channel + + Returns: + str | None: If the log channel exists, this will be the string of the ID + Otherwise it will be None """ if not guild: return None @@ -562,9 +576,12 @@ async def on_command_error( # Postgres setup function - async def get_postgres_ref(self: Self) -> None: + async def get_postgres_ref(self: Self) -> gino.GinoEngine: """Connects to postgres based on the database login defined the in the file_config Adds self.db to be the database reference + + Returns: + gino.GinoEngine: The database connection as a gino object """ await self.logger.send_log( message="Obtaining and binding to Gino instance", @@ -602,7 +619,13 @@ async def get_postgres_ref(self: Self) -> None: async def get_potential_extensions(self: Self) -> list[str]: """Gets the current list of extensions in the defined directory. - This ONLY gets commands, not functions""" + This ONLY gets commands, not functions + + Returns: + list[str]: Gets a list of the string names of every python file + in the commands folder + """ + self.logger.console.info(f"Searching {self.EXTENSIONS_DIR} for extensions") extensions_list = [ os.path.basename(f)[:-3] @@ -613,7 +636,12 @@ async def get_potential_extensions(self: Self) -> list[str]: async def get_potential_function_extensions(self: Self) -> list[str]: """Gets the current list of extensions in the defined directory. - This ONLY gets functions, not commands""" + This ONLY gets functions, not commands + + Returns: + list[str]: Gets a list of the string names of every python file + in the functions folder + """ self.logger.console.info(f"Searching {self.FUNCTIONS_DIR} for extensions") extensions_list = [ os.path.basename(f)[:-3] @@ -678,6 +706,9 @@ def get_command_extension_name(self: Self, command: commands.Command) -> str: Args: command (commands.Command): the command to reference + + Returns: + str: The name of the extension name that houses a prefix command. """ if not command.module.startswith(f"{self.EXTENSIONS_DIR_NAME}."): return None @@ -713,13 +744,14 @@ async def register_file_extension( async def is_bot_admin(self: Self, member: discord.Member) -> bool: """Processes command context against admin/owner data. - Command checks are disabled if the context author is the owner. - They are also ignored if the author is bot admin in the config. Args: member (discord.Member): the context associated with the command + + Returns: + bool: True if the member is a bot admin. False if it isn't """ await self.logger.send_log( message="Checking context against bot admins", @@ -746,7 +778,11 @@ async def is_bot_admin(self: Self, member: discord.Member) -> bool: return False async def get_owner(self: Self) -> discord.User | None: - """Gets the owner object from the bot application.""" + """Gets the owner object from the bot application. + + Returns: + discord.User | None: The User object of the owner of the application on discords side + """ if not self.owner: try: # If this isn't console only, it is a forever recursion @@ -764,10 +800,13 @@ async def get_owner(self: Self) -> discord.User | None: async def get_prefix(self: Self, message: discord.Message) -> str: """Gets the appropriate prefix for a command. - This is called by discord.py and must be async + This is called by discord.py and MUST be async Args: message (discord.Message): the message to check against + + Returns: + str: The string of the command prefix by the bot, for the given guild """ guild_config = self.guild_configs[str(message.guild.id)] return getattr( diff --git a/techsupport_bot/botlogging/common.py b/techsupport_bot/botlogging/common.py index 7de7b6d3c..3e4e25b9c 100644 --- a/techsupport_bot/botlogging/common.py +++ b/techsupport_bot/botlogging/common.py @@ -10,7 +10,14 @@ class LogLevel(Enum): """This is a way to map log levels to strings, and have the easy ability - to dynamically add or remove log levels""" + to dynamically add or remove log levels + + Attrs: + DEBUG (str): Representation of debug + INFO (str): Representation of info + WARNING (str): Representation of warning + ERROR (str): Representation of error + """ DEBUG = "debug" INFO = "info" @@ -23,7 +30,7 @@ class LogContext: """A very simple class to store a few contextual items about the log This is used to determine if some guild settings means the log shouldn't be logged - Args: + Attrs: guild (discord.Guild | None): The guild the log occured with. Optional channel (discord.abc.Messageble | None): The channel, DM, thread, or other messagable the log occured in diff --git a/techsupport_bot/botlogging/delayed.py b/techsupport_bot/botlogging/delayed.py index 73ba48036..8709377b2 100644 --- a/techsupport_bot/botlogging/delayed.py +++ b/techsupport_bot/botlogging/delayed.py @@ -11,13 +11,13 @@ class DelayedLogger(logger.BotLogger): """Logging interface that queues log events to be sent over time. + wait_time (float): the time to wait between log sends + queue_size (int): the max number of queue events Args: - bot (bot.TechSupportBot): the bot object - name (str): the name of the logging channel - send (bool): Whether or not to allow sending of logs to discord - wait_time (float): the time to wait between log sends - queue_size (int): the max number of queue events + *args (tuple): The args dict passed to this, for use passing to the main logger + **kwargs (dict[str, Any]): The kwargs dict passed to this, + for use passing to the main logger """ def __init__(self: Self, *args: tuple, **kwargs: dict[str, Any]) -> None: @@ -30,6 +30,11 @@ async def send_log(self: Self, *args: tuple, **kwargs: dict[str, Any]) -> None: """Adds a log to the queue Does nothing different than the Logger send_log function() Will disregard debug logs if debug is off + + Args: + *args (tuple): The args dict passed to this, for use passing to the main logger + **kwargs (dict[str, Any]): The kwargs dict passed to this, + for use passing to the main logger """ if kwargs.get("level", None) == logger.LogLevel.DEBUG and not bool( diff --git a/techsupport_bot/botlogging/embed.py b/techsupport_bot/botlogging/embed.py index d60532b05..c24122bc7 100644 --- a/techsupport_bot/botlogging/embed.py +++ b/techsupport_bot/botlogging/embed.py @@ -9,7 +9,16 @@ class LogEmbed(discord.Embed): - """Base log event embed.""" + """Base log event embed. + Do not create this directly + + Args: + message (str): The message to log. Will become the description of an embed + + Attrs: + title (str): The title of the embed + color (discord.Color): The color of the embed + """ title = None color = None @@ -38,28 +47,48 @@ def modify_embed(self: Self, embed: discord.Embed) -> discord.Embed: class InfoEmbed(LogEmbed): - """Embed for info level log events.""" + """Embed for info level log events. + + Attrs: + title (str): The title of the embed + color (discord.Color): The color of the embed + """ title = "info" color = discord.Color.green() class DebugEmbed(LogEmbed): - """Embed for debug level log events.""" + """Embed for debug level log events. + + Attrs: + title (str): The title of the embed + color (discord.Color): The color of the embed + """ title = "debug" color = discord.Color.dark_green() class WarningEmbed(LogEmbed): - """Embed for warning level log events.""" + """Embed for warning level log events. + + Attrs: + title (str): The title of the embed + color (discord.Color): The color of the embed + """ title = "warning" color = discord.Color.gold() class ErrorEmbed(LogEmbed): - """Embed for error level log events.""" + """Embed for error level log events. + + Attrs: + title (str): The title of the embed + color (discord.Color): The color of the embed + """ title = "error" color = discord.Color.red() diff --git a/techsupport_bot/botlogging/logger.py b/techsupport_bot/botlogging/logger.py index 9a21ef5c6..b2dd32829 100644 --- a/techsupport_bot/botlogging/logger.py +++ b/techsupport_bot/botlogging/logger.py @@ -20,7 +20,7 @@ class BotLogger: """Logging interface for Discord bots. Args: - bot (bot.TechSupportBot): the bot object + discord_bot (bot.TechSupportBot): the bot object name (str): the name of the logging channel send (bool): Whether or not to allow sending of logs to discord """ @@ -40,6 +40,9 @@ def __init__(self: Self) -> None: class DebugLogLevel(GenericLogLevel): """This is class defining special parameters for debug logs This should never be used manually. Use the LogLevel Enum instead + + Args: + main_console (logging.Logger): The console object to print logs to """ def __init__(self: Self, main_console: logging.Logger) -> None: @@ -50,6 +53,9 @@ def __init__(self: Self, main_console: logging.Logger) -> None: class InfoLogLevel(GenericLogLevel): """This is class defining special parameters for info logs This should never be used manually. Use the LogLevel Enum instead + + Args: + main_console (logging.Logger): The console object to print logs to """ def __init__(self: Self, main_console: logging.Logger) -> None: @@ -60,6 +66,9 @@ def __init__(self: Self, main_console: logging.Logger) -> None: class WarningLogLevel(GenericLogLevel): """This is class defining special parameters for warning logs This should never be used manually. Use the LogLevel Enum instead + + Args: + main_console (logging.Logger): The console object to print logs to """ def __init__(self: Self, main_console: logging.Logger) -> None: @@ -70,6 +79,9 @@ def __init__(self: Self, main_console: logging.Logger) -> None: class ErrorLogLevel(GenericLogLevel): """This is class defining special parameters for error logs This should never be used manually. Use the LogLevel Enum instead + + Args: + main_console (logging.Logger): The console object to print logs to """ def __init__(self: Self, main_console: logging.Logger) -> None: diff --git a/techsupport_bot/commands/animal.py b/techsupport_bot/commands/animal.py index 128605fce..9e288b236 100644 --- a/techsupport_bot/commands/animal.py +++ b/techsupport_bot/commands/animal.py @@ -22,7 +22,14 @@ async def setup(bot: bot.TechSupportBot) -> None: class Animals(cogs.BaseCog): - """The class for the animals commands""" + """The class for the animals commands + + Attrs: + CAT_API_URL (str): The URL for the cat API + DOG_API_URL (str): The URL for the dog API + FOX_API_URL (str): The URL for the fox API + FROG_API_URL (str): The URL for the frog API + """ CAT_API_URL = "https://api.thecatapi.com/v1/images/search?limit=1&api_key={}" DOG_API_URL = "https://dog.ceo/api/breeds/image/random" diff --git a/techsupport_bot/commands/application.py b/techsupport_bot/commands/application.py index a56e3e306..325ebc624 100644 --- a/techsupport_bot/commands/application.py +++ b/techsupport_bot/commands/application.py @@ -18,7 +18,14 @@ class ApplicationStatus(Enum): """Static string mapping of all status - This is so the database can always be consistent""" + This is so the database can always be consistent + + Attrs: + PENDING (str): The string representation for pending + APPROVED (str): The string representation for approved + DENIED (str): The string representation for denied + REJECTED (str): The string representation for rejected + """ PENDING = "pending" APPROVED = "approved" @@ -189,7 +196,11 @@ async def wait(self: Self, config: munch.Munch, guild: discord.Guild) -> None: class ApplicationManager(cogs.LoopCog): - """This cog is responsible for the majority of functions in the application system""" + """This cog is responsible for the majority of functions in the application system + + Attrs: + application_group (app_commands.Group): The group for the /application commands + """ application_group = app_commands.Group( name="application", description="...", extras={"module": "application"} diff --git a/techsupport_bot/commands/burn.py b/techsupport_bot/commands/burn.py index 7be90b001..fceed45b5 100644 --- a/techsupport_bot/commands/burn.py +++ b/techsupport_bot/commands/burn.py @@ -27,7 +27,11 @@ async def setup(bot: bot.TechSupportBot) -> None: class Burn(cogs.BaseCog): - """Class for Burn command on the discord bot.""" + """Class for Burn command on the discord bot. + + Attrs: + PHRASES (list[str]): The list of phrases to pick from + """ PHRASES = [ "Sick BURN!", diff --git a/techsupport_bot/commands/chatgpt.py b/techsupport_bot/commands/chatgpt.py index 6ddc23eb4..55e35a4ee 100644 --- a/techsupport_bot/commands/chatgpt.py +++ b/techsupport_bot/commands/chatgpt.py @@ -46,7 +46,12 @@ async def setup(bot: bot.TechSupportBot) -> None: class ChatGPT(cogs.BaseCog): - """Main extension class""" + """Main extension class + + Attrs: + API_URL (str): The URL for the openai API + SYSTEM_PROMPT (dict[str, str]): The default starting prompt for chatGPT + """ API_URL = "https://api.openai.com/v1/chat/completions" diff --git a/techsupport_bot/commands/conch.py b/techsupport_bot/commands/conch.py index be25c6a6a..7256e3f41 100644 --- a/techsupport_bot/commands/conch.py +++ b/techsupport_bot/commands/conch.py @@ -27,7 +27,13 @@ async def setup(bot: bot.TechSupportBot) -> None: class MagicConch(cogs.BaseCog): - """Class to create the conch command for discord bot.""" + """Class to create the conch command for discord bot. + + Attrs: + RESPONSES (list[str]): The list of random responses for the 8 ball + PIC_URL (str): The direct URL for the picture to put in embeds + + """ RESPONSES = [ "As I see it, yes.", @@ -35,7 +41,7 @@ class MagicConch(cogs.BaseCog): "Better not tell you now.", "Cannot predict now.", "Concentrate and ask again.", - "Don’t count on it.", + "Don't count on it.", "It is certain.", "It is decidedly so.", "Most likely.", @@ -48,7 +54,7 @@ class MagicConch(cogs.BaseCog): "Very doubtful.", "Without a doubt.", "Yes.", - "Yes – definitely.", + "Yes - definitely.", "You may rely on it.", ] PIC_URL = "https://i.imgur.com/vdvGrsR.png" diff --git a/techsupport_bot/commands/duck.py b/techsupport_bot/commands/duck.py index 6aa3f5bfc..8c0844208 100644 --- a/techsupport_bot/commands/duck.py +++ b/techsupport_bot/commands/duck.py @@ -91,7 +91,15 @@ async def setup(bot: bot.TechSupportBot) -> None: class DuckHunt(cogs.LoopCog): - """Class for the actual duck commands""" + """Class for the actual duck commands + + Attrs: + DUCK_PIC_URL (str): The picture for the duck + BEFRIEND_URL (str): The picture for the befriend target + KILL_URL (str): The picture for the kill target + ON_START (bool): ??? + CHANNELS_KEY (str): The config item for the channels that the duck hunt should run + """ DUCK_PIC_URL = "https://cdn.icon-icons.com/icons2/1446/PNG/512/22276duck_98782.png" BEFRIEND_URL = ( @@ -289,7 +297,10 @@ async def handle_winner( await channel.send(embed=embed) def pick_quote(self: Self) -> str: - """Method for picking a random quote for the miss message""" + """Picks a random quote from the duckQuotes.txt file + + Returns: + str: The quote picked randomly from the file, ready to use""" QUOTES_FILE = "resources/duckQuotes.txt" with open(QUOTES_FILE, "r", encoding="utf-8") as file: lines = file.readlines() diff --git a/techsupport_bot/commands/echo.py b/techsupport_bot/commands/echo.py index bcdbd8dca..c212542dd 100644 --- a/techsupport_bot/commands/echo.py +++ b/techsupport_bot/commands/echo.py @@ -61,7 +61,7 @@ async def echo_channel( This is a command and should be accessed via Discord. Args: - ctx (discord.ext.Context): the context object for the calling message + ctx (commands.Context): the context object for the calling message channel_id (int): the ID of the channel to send the echoed message message (str): the message to echo """ diff --git a/techsupport_bot/commands/emoji.py b/techsupport_bot/commands/emoji.py index 332cbdd99..8e8153834 100644 --- a/techsupport_bot/commands/emoji.py +++ b/techsupport_bot/commands/emoji.py @@ -29,7 +29,11 @@ async def setup(bot: bot.TechSupportBot) -> None: class Emojis(cogs.BaseCog): - """Class for all the emoji commands""" + """Class for all the emoji commands + + Attrs: + KEY_MAP (dict[str,str]): Some manual mappings from character to emoji + """ KEY_MAP = {"?": "question", "!": "exclamation"} diff --git a/techsupport_bot/commands/factoids.py b/techsupport_bot/commands/factoids.py index b8f88c13d..bd31442dc 100644 --- a/techsupport_bot/commands/factoids.py +++ b/techsupport_bot/commands/factoids.py @@ -158,6 +158,10 @@ async def has_given_factoids_role( class CalledFactoid: """A class to allow keeping the original factoid name in tact Without having to call the database lookup function every time + + Attrs: + original_call_str (str): The original name the user provided for a factoid + factoid_db_entry (bot.models.Factoid): The database entry for the original factoid """ original_call_str: str @@ -167,6 +171,9 @@ class CalledFactoid: class FactoidManager(cogs.MatchCog): """ Manages all factoid features + + Attrs: + CRON_REGEX: The regex to check if a cronjob is correct """ CRON_REGEX = ( @@ -308,7 +315,7 @@ async def confirm_factoid_deletion( fmt (str): Formatting for the returned message Returns: - (bool): Whether the factoid was deleted/modified + bool: Whether the factoid was deleted/modified """ view = ui.Confirm() @@ -670,7 +677,7 @@ async def delete_factoid( called_factoid (CalledFactoid): The factoid to remove Returns: - (bool): Whether the factoid was deleted + bool: Whether the factoid was deleted """ factoid = await self.get_raw_factoid_entry( called_factoid.factoid_db_entry.name, str(ctx.guild.id) @@ -739,7 +746,6 @@ async def response( message_content (str): Content of the call Raises: - FactoidNotFoundError: Raised if a broken alias is present in the DB TooLongFactoidMessageError: Raised when the raw message content is over discords 2000 char limit """ @@ -839,7 +845,7 @@ async def send_to_irc( # Don't attempt to send a message if irc if irc is disabled irc_config = self.bot.file_config.api.irc if not irc_config.enable_irc: - return None + return await self.bot.irc.irc_cog.handle_factoid( channel=channel, @@ -1604,7 +1610,7 @@ async def generate_html( list_only_hidden (bool): Whether to list only hidden factoids Returns: - str - The result html file + str: The result html file """ body_contents = "" diff --git a/techsupport_bot/commands/giphy.py b/techsupport_bot/commands/giphy.py index 50dff62f9..47e8c0505 100644 --- a/techsupport_bot/commands/giphy.py +++ b/techsupport_bot/commands/giphy.py @@ -33,7 +33,12 @@ async def setup(bot: bot.TechSupportBot) -> None: class Giphy(cogs.BaseCog): - """Class for the giphy extension.""" + """Class for the giphy extension. + + Attrs: + GIPHY_URL (str): The URL for the giphy API + SEARCH_LIMIT (int): The max amount of gifs to search for + """ GIPHY_URL = "http://api.giphy.com/v1/gifs/search?q={}&api_key={}&limit={}" SEARCH_LIMIT = 10 diff --git a/techsupport_bot/commands/github.py b/techsupport_bot/commands/github.py index 1f592f748..01db10766 100644 --- a/techsupport_bot/commands/github.py +++ b/techsupport_bot/commands/github.py @@ -49,6 +49,10 @@ async def setup(bot: bot.TechSupportBot) -> None: class IssueCreator(cogs.BaseCog): """ The class that holds the issue commands + + Attrs: + GITHUB_API_BASE_URL (str): The URL for the github API + """ GITHUB_API_BASE_URL = "https://api.github.com" @@ -70,8 +74,8 @@ async def issue( Args: ctx (commands.Context): the context object for the calling message - title: the title of the issue - description: the description of the issue + title (str): the title of the issue + description (str): the description of the issue """ if not self.bot.file_config.api.github.api_key: diff --git a/techsupport_bot/commands/google.py b/techsupport_bot/commands/google.py index 1a913312d..580af1922 100644 --- a/techsupport_bot/commands/google.py +++ b/techsupport_bot/commands/google.py @@ -45,7 +45,13 @@ async def setup(bot: bot.TechSupportBot) -> None: class Googler(cogs.BaseCog): - """Class for the google extension for the discord bot.""" + """Class for the google extension for the discord bot. + + Attrs: + GOOGLE_URL (str): The API URL for google search + YOUTUBE_URL (str): The API URL for youtube search + ICON_URL (str): The google icon + """ GOOGLE_URL = "https://www.googleapis.com/customsearch/v1" YOUTUBE_URL = "https://www.googleapis.com/youtube/v3/search?part=id&maxResults=10" diff --git a/techsupport_bot/commands/grab.py b/techsupport_bot/commands/grab.py index 81f86e7de..18a32af12 100644 --- a/techsupport_bot/commands/grab.py +++ b/techsupport_bot/commands/grab.py @@ -69,9 +69,12 @@ async def invalid_channel(ctx: commands.Context) -> bool: class Grabber(cogs.BaseCog): - """Class for the actual commands""" + """Class for the actual commands + + Attrs: + SEARCH_LIMIT (int): The max amount of messages to search when grabbing + """ - HAS_CONFIG = False SEARCH_LIMIT = 20 @auxiliary.with_typing @@ -175,7 +178,6 @@ async def all_grabs( """Discord command to get a paginated list of all grabs from a given user Args: - self (Self): _description_ ctx (commands.Context): The context in which the command was run in user_to_grab (discord.Member): The user to get all the grabs from """ diff --git a/techsupport_bot/commands/hangman.py b/techsupport_bot/commands/hangman.py index 6d7be1925..b79be7feb 100644 --- a/techsupport_bot/commands/hangman.py +++ b/techsupport_bot/commands/hangman.py @@ -37,6 +37,15 @@ async def setup(bot: bot.TechSupportBot) -> None: class HangmanGame: """Class for the game hangman. + Attrs: + HANG_PICS (list[str]): The list of hangman pictures + FINAL_STEP (int): The last step of the hangman game + finished (bool): Determines if the game has been finished or not + failed (bool): Determines if the players failed to guess the word + + Args: + word (str): The word to start the game with + Raises: ValueError: A valid alphabetic word wasn't provided """ @@ -111,7 +120,11 @@ def __init__(self: Self, word: str) -> None: self.id = uuid.uuid4() def draw_word_state(self: Self) -> str: - """Method to draw the word on the embed.""" + """Makes a string with blank spaces and guessed letters + + Returns: + str: The formatted string ready to be printed in the embed + """ state = "" for letter in self.word: value = letter if letter.lower() in self.guesses else "_" @@ -120,7 +133,12 @@ def draw_word_state(self: Self) -> str: return state def draw_hang_state(self: Self) -> str: - """Method to draw the current state of the game.""" + """Gets the pre-programmed string for the hangman state + Based on how many guesses and how close the man is to hanging + + Returns: + str: The str representation of the correct picture + """ return self.HANG_PICS[self.step] def guess(self: Self, letter: str) -> bool: @@ -251,7 +269,13 @@ async def hangman(self: Self, ctx: commands.Context) -> None: usage="[word]", ) async def start_game(self: Self, ctx: commands.Context, word: str) -> None: - """Method to start the hangman game and delete the original message.""" + """Method to start the hangman game and delete the original message. + This is a command and should be access via discord + + Args: + ctx (commands.Context): The context in which the command occured + word (str): The word to state the hangman game with + """ # delete the message so the word is not seen await ctx.message.delete() diff --git a/techsupport_bot/commands/help.py b/techsupport_bot/commands/help.py index 252300e72..edc77f3a0 100644 --- a/techsupport_bot/commands/help.py +++ b/techsupport_bot/commands/help.py @@ -20,6 +20,13 @@ class PrintableCommand: """A custom class to store formatted information about a command With a priority on being sortable and searchable + + Attrs: + prefix (str): The prefix to call the command with + name (str): The command name + usage (str): The usage hints for the command + description (str): The description of the command + """ prefix: str @@ -40,8 +47,6 @@ async def setup(bot: bot.TechSupportBot) -> None: class Helper(cogs.BaseCog): """Cog object for help commands.""" - EXTENSIONS_PER_GENERAL_PAGE = 15 - @commands.command( name="help", brief="Displays helpful infromation", @@ -57,7 +62,7 @@ async def help_command( Args: ctx (commands.Context): the context object for the message - search_term (str, Optional): The term to search command name and descriptions for. + search_term (str, optional): The term to search command name and descriptions for. Will default to empty string """ # Build raw lists of commands diff --git a/techsupport_bot/commands/htd.py b/techsupport_bot/commands/htd.py index f6f468bfb..14d974a80 100644 --- a/techsupport_bot/commands/htd.py +++ b/techsupport_bot/commands/htd.py @@ -26,6 +26,10 @@ async def setup(bot: bot.TechSupportBot) -> None: class Htd(cogs.BaseCog): """ perform calculations on cross-base numbers and convert between them + + Attrs: + OPERATORS (list[str]): The list of operations to process + """ OPERATORS = ["+", "-", "*", "/"] diff --git a/techsupport_bot/commands/hug.py b/techsupport_bot/commands/hug.py index c6cd3058a..6cf5286a9 100644 --- a/techsupport_bot/commands/hug.py +++ b/techsupport_bot/commands/hug.py @@ -23,7 +23,13 @@ async def setup(bot: bot.TechSupportBot) -> None: class Hugger(cogs.BaseCog): - """Class to make the hug command.""" + """Class to make the hug command. + + Attrs: + HUGS_SELECTION (list[str]): The list of hug phrases to display + ICON_URL (str): The icon to use when hugging + + """ HUGS_SELECTION = [ "{user_giving_hug} hugs {user_to_hug} forever and ever and ever", diff --git a/techsupport_bot/commands/ipinfo.py b/techsupport_bot/commands/ipinfo.py index c3372fd22..4d46cc511 100644 --- a/techsupport_bot/commands/ipinfo.py +++ b/techsupport_bot/commands/ipinfo.py @@ -22,7 +22,12 @@ async def setup(bot: bot.TechSupportBot) -> None: class IPInfo(cogs.BaseCog): - """Class to add ipinfo geodata to the bot.""" + """Class to add ipinfo geodata to the bot. + + Attrs: + API_URL (str): The API url for IP info + IP_ICON_URL (str): The URL for the IP info icon + """ API_URL = "https://ipinfo.io" IP_ICON_URL = ( @@ -40,7 +45,6 @@ async def get_info(self: Self, ctx: commands.Context, ip_address: str) -> None: """Entry point and main logic for the IP info command Args: - self (Self): _description_ ctx (commands.Context): The context in which the commmand was run in ip_address (str): The user inputted IP address to lookup """ @@ -65,8 +69,11 @@ def generate_embed(self: Self, ip: str, fields: dict[str, str]) -> discord.Embed Args: ip (str): the ip address - fields (dict): dictionary containing embed field titles and - their contents + fields (dict[str, str]): dictionary containing embed field titles and + their contents + + Returns: + discord.Embed: The formatted embed ready to be sent to the user """ embed = discord.Embed(title=f"IP info for {ip}") embed.set_thumbnail(url=self.IP_ICON_URL) diff --git a/techsupport_bot/commands/iss.py b/techsupport_bot/commands/iss.py index 445df9b69..90d290958 100644 --- a/techsupport_bot/commands/iss.py +++ b/techsupport_bot/commands/iss.py @@ -22,7 +22,13 @@ async def setup(bot: bot.TechSupportBot) -> None: class ISSLocator(cogs.BaseCog): - """Class to locate the ISS at its current position.""" + """Class to locate the ISS at its current position. + + Attrs: + ISS_URL (str): The API URL to get the location of the ISS + GEO_URL (str): The API URL to turn lat/lon to location + + """ ISS_URL = "http://api.open-notify.org/iss-now.json" GEO_URL = "https://geocode.xyz/{},{}?geoit=json" @@ -38,7 +44,6 @@ async def iss(self: Self, ctx: commands.Context) -> None: Will call the API, format and send an embed Args: - self (Self): _description_ ctx (commands.Context): The context in which the command was run in """ # get ISS coordinates diff --git a/techsupport_bot/commands/joke.py b/techsupport_bot/commands/joke.py index 1d7569540..4137bf8e3 100644 --- a/techsupport_bot/commands/joke.py +++ b/techsupport_bot/commands/joke.py @@ -35,7 +35,12 @@ async def setup(bot: bot.TechSupportBot) -> None: class Joker(cogs.BaseCog): - """Class to make up the joke extension.""" + """Class to make up the joke extension. + + Attrs: + API_URL (str): The joke API URL + + """ API_URL = "https://v2.jokeapi.dev/joke/Any" @@ -45,7 +50,6 @@ async def call_api( """Calls the joke API and returns the raw response Args: - self (Self): _description_ ctx (commands.Context): The context in which the joke command was run in config (munch.Munch): The guild config for the guild where the joke command was run diff --git a/techsupport_bot/commands/kanye.py b/techsupport_bot/commands/kanye.py index 9e53a5014..ca8f5fa20 100644 --- a/techsupport_bot/commands/kanye.py +++ b/techsupport_bot/commands/kanye.py @@ -49,7 +49,12 @@ async def setup(bot: bot.TechSupportBot) -> None: class KanyeQuotes(cogs.LoopCog): - """Class to get the Kanye quotes from the api.""" + """Class to get the Kanye quotes from the api. + + Attrs: + API_URL (str): The Kanye API URL + KANYE_PICS (list[str]): The list of Kanye pics to pick from randomly + """ API_URL = "https://api.kanye.rest" KANYE_PICS = [ diff --git a/techsupport_bot/commands/lenny.py b/techsupport_bot/commands/lenny.py index ad572ca5e..c38efc9fa 100644 --- a/techsupport_bot/commands/lenny.py +++ b/techsupport_bot/commands/lenny.py @@ -23,7 +23,12 @@ async def setup(bot: bot.TechSupportBot) -> None: class Lenny(cogs.BaseCog): - """Class for lenny extension.""" + """Class for lenny extension. + + Attrs: + LENNYS_SELECTION (list[str]): The list of lenny faces to pick one randomly + + """ LENNYS_SELECTION = [ "( ͡° ͜ʖ ͡°)", diff --git a/techsupport_bot/commands/linter.py b/techsupport_bot/commands/linter.py index 4c770bc28..964141727 100644 --- a/techsupport_bot/commands/linter.py +++ b/techsupport_bot/commands/linter.py @@ -92,7 +92,7 @@ def check_valid_attachments( """A command to check if the attachments on a message are valid for linter Args: - attachments (list): A list of discord.Attachment + attachments (list[discord.Attachment]): A list of discord.Attachment Returns: bool: True if valid, False if invalid diff --git a/techsupport_bot/commands/listen.py b/techsupport_bot/commands/listen.py index 3b1127f8d..30b218ac0 100644 --- a/techsupport_bot/commands/listen.py +++ b/techsupport_bot/commands/listen.py @@ -29,12 +29,18 @@ class ListenChannel(commands.Converter): This avoids the limitation set by the builtin channel converters. """ - async def convert(self: Self, ctx: commands.Context, argument: int) -> None: + async def convert( + self: Self, ctx: commands.Context, argument: int + ) -> discord.abc.GuildChannel | discord.abc.PrivateChannel | discord.Thread: """Convert method for the converter. Args: ctx (commands.Context): the context object argument (int): the channel ID to convert + + Returns: + discord.abc.GuildChannel | discord.abc.PrivateChannel | discord.Thread: + The channel object that is associated with the ID """ channel = await ctx.bot.fetch_channel(argument) return channel @@ -43,9 +49,6 @@ async def convert(self: Self, ctx: commands.Context, argument: int) -> None: class Listener(cogs.BaseCog): """Cog object for listening to channels.""" - MAX_DESTINATIONS = 10 - CACHE_TIME = 60 - def format_message_in_embed(self: Self, message: discord.Message) -> discord.Embed: """Formats a listened message into a pretty embed @@ -81,11 +84,16 @@ async def preconfig(self: Self) -> None: max_age_seconds=1200, ) - async def get_destinations(self: Self, src: discord.TextChannel) -> None: + async def get_destinations( + self: Self, src: discord.TextChannel + ) -> list[discord.abc.Messageable]: """Gets channel object destinations for a given source channel. Args: src (discord.TextChannel): the source channel to build for + + Returns: + list[discord.abc.Messageable]: The list of destinations to send the listened message to """ destinations = self.destination_cache.get(src.id) @@ -95,11 +103,16 @@ async def get_destinations(self: Self, src: discord.TextChannel) -> None: return destinations - async def build_destinations_from_src(self: Self, src: discord.TextChannel) -> None: + async def build_destinations_from_src( + self: Self, src: discord.TextChannel + ) -> list[discord.abc.Messageable]: """Builds channel objects for a given src. Args: src (discord.TextChannel): the source channel to build for + + Returns: + list[discord.abc.Messageable]: The list of destinations to send the listened message to """ destination_data = await self.get_destination_data(src) if not destination_data: @@ -114,6 +127,9 @@ async def build_destinations( Args: destination_ids (list[int]): the destination ID's to reference + + Returns: + list[discord.abc.Messageable]: The list of destinations to send the listened message to """ destinations = set() for did in destination_ids: @@ -136,6 +152,9 @@ async def get_destination_data(self: Self, src: discord.TextChannel) -> list[str Args: src (discord.TextChannel): the source channel to build for + + Returns: + list[str]: The list of channel IDs that should have the listened message sent to """ destination_data = await self.bot.models.Listener.query.where( self.bot.models.Listener.src_id == str(src.id) @@ -181,10 +200,16 @@ async def get_specific_listener( ) return listener - async def get_all_sources(self: Self) -> None: + async def get_all_sources( + self: Self, + ) -> dict[discord.abc.Messageable, list[discord.abc.Messageable]]: """Gets all source data. This is kind of expensive, so use lightly. + + Returns: + dict[discord.abc.Messageable, list[discord.abc.Messageable]]: A dict of all current + listen jobs from and to every channel """ source_objects = [] all_listens = await self.bot.models.Listener.query.gino.all() @@ -236,6 +261,9 @@ async def listen(self: Self, ctx: commands.Context) -> None: """Command group for listen commands. This is a command and should be accessed via Discord. + + Args: + ctx (commands.Context): the context object for the message """ # Executed if there are no/invalid args supplied diff --git a/techsupport_bot/commands/modmail.py b/techsupport_bot/commands/modmail.py index da758c818..7d6b87955 100644 --- a/techsupport_bot/commands/modmail.py +++ b/techsupport_bot/commands/modmail.py @@ -814,6 +814,9 @@ async def setup(bot: bot.TechSupportBot) -> None: Args: bot (bot.TechSupportBot): The bot object to register the cogs to + + Raises: + AttributeError: Raised if modmail is disabled """ # Only runs if modmail is enabled @@ -870,8 +873,8 @@ async def setup(bot: bot.TechSupportBot) -> None: class Modmail(cogs.BaseCog): """The modmail cog class - Raises: - AttributeError: Modmail aborting loading due to being disabled + Args: + bot (bot.TechSupportBot): The main TS bot object to be stored in modmail """ def __init__(self: Self, bot: bot.TechSupportBot) -> None: @@ -1269,6 +1272,10 @@ def modmail_commands_list(self: Self) -> list[tuple[str, str, str, str]]: [1] - command name [2] - command usage [3] - command description + + Returns: + list[tuple[str, str, str, str]]: The list of commands, + formatted to be added to the help menu """ prefix = self.bot.file_config.modmail_config.modmail_prefix commands_list = [ @@ -1428,19 +1435,21 @@ async def modmail_ban( pass case ui.ConfirmResponse.DENIED: - return await auxiliary.send_deny_embed( + await auxiliary.send_deny_embed( message=f"{user.mention} was NOT banned from creating modmail threads.", channel=ctx.channel, ) + return case ui.ConfirmResponse.CONFIRMED: await self.bot.models.ModmailBan(user_id=str(user.id)).create() - return await auxiliary.send_confirm_embed( + await auxiliary.send_confirm_embed( message=f"{user.mention} was successfully banned from creating future modmail" + " threads.", channel=ctx.channel, ) + return @auxiliary.with_typing @commands.check(has_modmail_management_role) @@ -1463,14 +1472,15 @@ async def modmail_unban( ).gino.first() if not ban_entry: - return await auxiliary.send_deny_embed( + await auxiliary.send_deny_embed( message=f"{user.mention} is not currently banned from making modmail threads!", channel=ctx.channel, ) + return await ban_entry.delete() - return await auxiliary.send_confirm_embed( + await auxiliary.send_confirm_embed( message=f"{user.mention} was successfully unbanned from creating modmail threads!", channel=ctx.channel, ) diff --git a/techsupport_bot/commands/news.py b/techsupport_bot/commands/news.py index 2cf92cdac..9488e7417 100644 --- a/techsupport_bot/commands/news.py +++ b/techsupport_bot/commands/news.py @@ -69,7 +69,18 @@ async def setup(bot: bot.TechSupportBot) -> None: class Category(enum.Enum): - """Class to set up categories for the news.""" + """Class to set up categories for the news. + + Attrs: + BUSINESS (str): The string representation for business + ENTERTAINMENT (str): The string representation for entertainment + GENERAL (str): The string representation for general + HEALTH (str): The string representation for health + SCIENCE (str): The string representation for science + SPORTS (str): The string representation for sports + TECH (str): The string representation for technology + + """ BUSINESS = "business" ENTERTAINMENT = "entertainment" @@ -81,7 +92,12 @@ class Category(enum.Enum): class News(cogs.LoopCog): - """Class to set up the news extension for the discord bot.""" + """Class to set up the news extension for the discord bot. + + Attrs: + API_URL (str): The news API URL + + """ API_URL = "http://newsapi.org/v2/top-headlines?apiKey={}&country={}" @@ -97,7 +113,6 @@ async def get_headlines( """Calls the API to get the list of headlines based on the category and country Args: - self (Self): _description_ country_code (str): The country code to get headlines from category (str, optional): The category of headlines to get. Defaults to None. @@ -124,7 +139,6 @@ async def get_random_headline( """Gets a single article object from the news API Args: - self (Self): _description_ country_code (str): The country code of the headliens to get category (str, optional): The category of headlines to get. Defaults to None. @@ -197,7 +211,6 @@ async def random(self: Self, ctx: commands.Context, category: str = None) -> Non """Discord command entry point for getting a news article Args: - self (Self): _description_ ctx (commands.Context): The context in which the command was run category (str, optional): The category to get news headlines from. Defaults to None. """ diff --git a/techsupport_bot/commands/poll.py b/techsupport_bot/commands/poll.py index 6da3f79bf..23b7c78ed 100644 --- a/techsupport_bot/commands/poll.py +++ b/techsupport_bot/commands/poll.py @@ -36,7 +36,19 @@ async def validate_data( request_body: munch.Munch, strawpoll: bool = False, ) -> munch.Munch: - """Method to validate data from the poll.""" + """Validates the uploaded json to ensure that the poll is valid. + Will potentially make changes to some aspects to ensure that everything works + such as timeout + + Args: + ctx (commands.Context): The context in which the command was run + request_body (munch.Munch): The uploaded json of the created poll + strawpoll (bool, optional): If this should be a strawpoll or a reaction poll. + Defaults to False. + + Returns: + munch.Munch: The validated and updated (if needed) config for the poll. + """ # probably shouldn't touch this max_options = len(self.OPTION_EMOJIS) if not strawpoll else 10 @@ -89,7 +101,14 @@ async def validate_data( class ReactionPoller(PollGenerator): - """Class to add reactions to the poll generator.""" + """Class to add reactions to the poll generator. + + Attrs: + OPTION_EMOJIS (list[str]): The list of emojis to react to the message with + STOP_EMOJI (str): The stop emoji to reaction to the message with + EXAMPLE_DATA (dict[str, str | list[str] | int]): The example poll that the bot can use + + """ OPTION_EMOJIS = ["one", "two", "three", "four", "five"] STOP_EMOJI = "\u26d4" @@ -126,7 +145,11 @@ async def poll(self: Self, ctx: commands.Context) -> None: description="Shows what JSON to upload to generate a poll", ) async def example(self: Self, ctx: commands.Context) -> None: - """Method to show an example of a poll.""" + """Method to show an example of a poll. + + Args: + ctx (commands.Context): The context in which the command was run in + """ json_file = discord.File( io.StringIO(json.dumps(self.EXAMPLE_DATA, indent=4)), filename="poll_example.json", @@ -144,7 +167,11 @@ async def example(self: Self, ctx: commands.Context) -> None: usage="|json-upload|", ) async def generate(self: Self, ctx: commands.Context) -> None: - """Method to generate the poll for discord.""" + """Method to generate the poll for discord. + + Args: + ctx (commands.Context): The context in which the command was run in + """ request_body = await auxiliary.get_json_from_attachments(ctx.message) if not request_body: await auxiliary.send_deny_embed( @@ -232,7 +259,17 @@ async def wait_for_results( timeout: int, options: list[str], ) -> dict[str, int]: - """Method to wait on results from the poll from other users.""" + """Waits for the poll to conclude, and then gets the results + + Args: + ctx (commands.Context): The context in which the command was run in + message (discord.Message): The message containing the reaction poll + timeout (int): The amount of seconds the reaction poll should run for + options (list[str]): The list of the options for the poll + + Returns: + dict[str, int]: The options list with the amount of votes for each option + """ option_emojis = self.option_emojis[: len(options)] await asyncio.sleep(timeout) @@ -274,7 +311,13 @@ async def wait_for_results( class StrawPoller(PollGenerator): - """Class to create a straw poll from discord.""" + """Class to create a straw poll from discord. + + Attrs: + EXAMPLE_DATA (dict[str, str | list[str]]): The example poll that the bot can use + API_URL (str): The strawpoll API URL + + """ EXAMPLE_DATA = { "question": "Best ice cream?", @@ -287,7 +330,11 @@ class StrawPoller(PollGenerator): description="Executes a strawpoll command", ) async def strawpoll(self: Self, ctx: commands.Context) -> None: - """Method to give an exmaple poll with json.""" + """Method to give an exmaple poll with json. + + Args: + ctx (commands.Context): The context in which the command was run in + """ # Executed if there are no/invalid args supplied await auxiliary.extension_help(self, ctx, self.__module__[9:]) @@ -298,7 +345,11 @@ async def strawpoll(self: Self, ctx: commands.Context) -> None: description="Shows what JSON to upload to generate a poll", ) async def example(self: Self, ctx: commands.Context) -> None: - """Method that contains the example file for a poll.""" + """Method that contains the example file for a poll. + + Args: + ctx (commands.Context): The context in which the command was run in + """ json_file = discord.File( io.StringIO(json.dumps(self.EXAMPLE_DATA, indent=4)), filename="poll_example.json", @@ -312,7 +363,11 @@ async def example(self: Self, ctx: commands.Context) -> None: usage="|json-upload|", ) async def generate(self: Self, ctx: commands.Context) -> None: - """Method to generate the poll form the discord command.""" + """Method to generate the poll form the discord command. + + Args: + ctx (commands.Context): The context in which the command was run in + """ request_body = await auxiliary.get_json_from_attachments(ctx.message) if not request_body: await auxiliary.send_deny_embed( diff --git a/techsupport_bot/commands/protect.py b/techsupport_bot/commands/protect.py index d3f40d7a3..504b2d312 100644 --- a/techsupport_bot/commands/protect.py +++ b/techsupport_bot/commands/protect.py @@ -145,7 +145,14 @@ async def setup(bot: bot.TechSupportBot) -> None: class Protector(cogs.MatchCog): - """Class for the protector command.""" + """Class for the protector command. + + Attrs: + ALERT_ICON_URL (str): The icon for the alert messages + CLIPBOARD_ICON_URL (str): The icon for the paste messages + CHARS_PER_NEWLINE (int): The arbitrary length of a line + + """ ALERT_ICON_URL = ( "https://cdn.icon-icons.com/icons2/2063/PNG/512/" @@ -165,7 +172,17 @@ async def preconfig(self: Self) -> None: async def match( self: Self, config: munch.Munch, ctx: commands.Context, content: str ) -> bool: - """Method to match roles for the protect command.""" + """Checks if the message could be triggered by any protect rules + Checks for channel and that the user isn't exempt + + Args: + config (munch.Munch): The guild config where the message was sent + ctx (commands.Context): The context in which the command was run in + content (str): The string content of the message sent + + Returns: + bool: False if the message shouldn't be checked, True if it should + """ # exit the match based on exclusion parameters if not str(ctx.channel.id) in config.extensions.protect.channels.value: await self.bot.logger.send_log( @@ -192,7 +209,12 @@ async def match( async def on_raw_message_edit( self: Self, payload: discord.RawMessageUpdateEvent ) -> None: - """Method to edit the raw message.""" + """This is called when any message is edited in any guild the bot is in. + There is no guarantee that the message exists or is used + + Args: + payload (discord.RawMessageUpdateEvent): The raw event that the edit generated + """ guild = self.bot.get_guild(payload.guild_id) if not guild: return @@ -223,8 +245,15 @@ async def on_raw_message_edit( def search_by_text_regex( self: Self, config: munch.Munch, content: str ) -> munch.Munch: - """Function to search given input by all - text and regex rules from the config""" + """Searches a given message for static text and regex rule violations + + Args: + config (munch.Munch): The guild config where the message was sent + content (str): The string contents of the message that might be filtered + + Returns: + munch.Munch: The most aggressive filter that is triggered + """ triggered_config = None for ( keyword, @@ -259,7 +288,13 @@ def search_by_text_regex( async def response( self: Self, config: munch.Munch, ctx: commands.Context, content: str, _: bool ) -> None: - """Method to define the response for the protect extension.""" + """Checks if a message does violate any set automod rules + + Args: + config (munch.Munch): The guild config where the message was sent + ctx (commands.Context): The context of the original message + content (str): The string content of the message sent + """ # check mass mentions first - return after handling if len(ctx.message.mentions) > config.extensions.protect.max_mentions.value: await self.handle_mass_mention_alert(config, ctx, content) @@ -289,13 +324,26 @@ async def response( await self.handle_length_alert(config, ctx, content) def max_newlines(self: Self, max_length: int) -> int: - """Method to set up the number of max lines.""" + """Gets a theoretical maximum number of new lines in a given message + + Args: + max_length (int): The max length of characters per theoretical line + + Returns: + int: The maximum number of new lines based on config + """ return int(max_length / self.CHARS_PER_NEWLINE) + 1 async def handle_length_alert( self: Self, config: munch.Munch, ctx: commands.Context, content: str ) -> None: - """Method to handle alert for the protect extension.""" + """Moves message into a linx paste if it's too long + + Args: + config (munch.Munch): The guild config where the too long message was sent + ctx (commands.Context): The context where the original message was sent + content (str): The string content of the flagged message + """ attachments: list[discord.File] = [] if ctx.message.attachments: total_attachment_size = 0 @@ -335,7 +383,13 @@ async def handle_length_alert( async def handle_mass_mention_alert( self: Self, config: munch.Munch, ctx: commands.Context, content: str ) -> None: - """Method for handling mass mentions in an alert.""" + """Handles a mass mention alert from automod + + Args: + config (munch.Munch): The guild config where the message was sent + ctx (commands.Context): The context where the message was sent + content (str): The string content of the message + """ await ctx.message.delete() await self.handle_warn(ctx, ctx.author, "mass mention", bypass=True) await self.send_alert(config, ctx, f"Mass mentions from {ctx.author}") @@ -343,7 +397,13 @@ async def handle_mass_mention_alert( async def handle_file_extension_alert( self: Self, config: munch.Munch, ctx: commands.Context, filename: str ) -> None: - """Method for handling suspicious file extensions.""" + """Handles a suspicous file extension flag from automod + + Args: + config (munch.Munch): The guild config from where the message was sent + ctx (commands.Context): The context where the message was sent + filename (str): The filename of the suspicious file that was uploaded + """ await ctx.message.delete() await self.handle_warn( ctx, ctx.author, "Suspicious file extension", bypass=True @@ -359,7 +419,14 @@ async def handle_string_alert( content: str, filter_config: munch.Munch, ) -> None: - """Method to handle a string alert for the protect extension.""" + """Handles a static string alert. Is given a rule that was violated + + Args: + config (munch.Munch): The guild config where the message was sent + ctx (commands.Context): The context where the original message was sent + content (str): The string content of the message + filter_config (munch.Munch): The rule that was triggered by the message + """ # If needed, delete the message if filter_config.delete: await ctx.message.delete() @@ -398,7 +465,15 @@ async def handle_warn( reason: str, bypass: bool = False, ) -> None: - """Method to handle the warn of a user.""" + """Handles the logic of a warning + + Args: + ctx (commands.Context): The context that generated the warning + user (discord.Member): The member to warn + reason (str): The reason for warning + bypass (bool, optional): If this should bypass the confirmation check. + Defaults to False. + """ if not bypass: can_execute = await self.can_execute(ctx, user) if not can_execute: @@ -485,7 +560,15 @@ async def handle_unwarn( reason: str, bypass: bool = False, ) -> None: - """Method to handle an unwarn of a user.""" + """Handles the logic of clearing all warnings + + Args: + ctx (commands.Context): The context that generated theis unwarn + user (discord.Member): The member to remove warnings from + reason (str): The reason for clearing warnings + bypass (bool, optional): If this should bypass the confirmation check. + Defaults to False. + """ # Always allow admins to unwarn other admins if not bypass and not ctx.message.author.guild_permissions.administrator: can_execute = await self.can_execute(ctx, user) @@ -511,7 +594,14 @@ async def handle_ban( reason: str, bypass: bool = False, ) -> None: - """Method to handle the ban of a user.""" + """Handles the logic of banning a user. Is not a discord command + + Args: + ctx (commands.Context): The context that generated the need for a bad + user (discord.User | discord.Member): The user or member to be banned + reason (str): The ban reason to be stored in discord + bypass (bool, optional): True will ignore permission chekcks. Defaults to False. + """ if not bypass: can_execute = await self.can_execute(ctx, user) if not can_execute: @@ -542,7 +632,14 @@ async def handle_unban( reason: str, bypass: bool = False, ) -> None: - """Method to handle an unban of a user.""" + """Handles the logic of unbanning a user. Is not a discord command + + Args: + ctx (commands.Context): The context that generated the need for the unban + user (discord.User): The user to be unbanned + reason (str): The unban reason to be saved in the audit log + bypass (bool, optional): True will ignore permission chekcks. Defaults to False. + """ if not bypass: can_execute = await self.can_execute(ctx, user) if not can_execute: @@ -568,7 +665,14 @@ async def handle_kick( reason: str, bypass: bool = False, ) -> None: - """Method to handle the kicking from the discord of a user.""" + """Handles the logic of kicking a user. Is not a discord command + + Args: + ctx (commands.Context): The context that generated the need for the kick + user (discord.Member): The user to be kicked + reason (str): The kick reason to be saved in the audit log + bypass (bool, optional): True will ignore permission chekcks. Defaults to False. + """ if not bypass: can_execute = await self.can_execute(ctx, user) if not can_execute: @@ -583,7 +687,12 @@ async def handle_kick( async def clear_warnings( self: Self, user: discord.User | discord.Member, guild: discord.Guild ) -> None: - """Method to clear warnings of a user in discord.""" + """This clears all warnings for a given user + + Args: + user (discord.User | discord.Member): The user or member to wipe all warnings for + guild (discord.Guild): The guild to clear warning from + """ await self.bot.models.Warning.delete.where( self.bot.models.Warning.user_id == str(user.id) ).where(self.bot.models.Warning.guild_id == str(guild.id)).gino.status() @@ -591,7 +700,16 @@ async def clear_warnings( async def generate_user_modified_embed( self: Self, user: discord.User | discord.Member, action: str, reason: str ) -> discord.Embed: - """Method to generate the user embed with the reason.""" + """This generates an embed to be shown to the user on why their message was actioned + + Args: + user (discord.User | discord.Member): The user or member who was punished + action (str): The action that was taken against the person + reason (str): The reason for the action taken + + Returns: + discord.Embed: The prepared embed ready to be sent + """ embed = discord.Embed( title="Chat Protection", description=f"{action.upper()} `{user}`" ) @@ -604,13 +722,36 @@ async def generate_user_modified_embed( def get_cache_key( self: Self, guild: discord.Guild, user: discord.Member, trigger: str ) -> str: - """Method to get the cache key of the user.""" + """Gets the cache key for repeated automod triggers + + Args: + guild (discord.Guild): The guild where the trigger has occured + user (discord.Member): The member that triggered the automod + trigger (str): The string representation of the automod rule that triggered + + Returns: + str: The key to lookup the cache entry, if it exists + """ return f"{guild.id}_{user.id}_{trigger}" async def can_execute( - self: Self, ctx: commands.Context, target: discord.User + self: Self, ctx: commands.Context, target: discord.User | discord.Member ) -> bool: - """Method to not execute on admin users.""" + """Checks permissions to determine if the protect command should execute. + This checks: + - If the executer is the same as the target + - If the target is a bot + - If the member is immune to protect + - If the bot doesn't have permissions + - If the user wouldn't have permissions based on their roles + + Args: + ctx (commands.Context): The context that required the need for moderative action + target (discord.User | discord.Member): The target of the moderative action + + Returns: + bool: True if the executer can execute this command, False if they can't + """ action = ctx.command.name or "do that to" config = self.bot.guild_configs[str(ctx.guild.id)] @@ -660,7 +801,13 @@ async def can_execute( async def send_alert( self: Self, config: munch.Munch, ctx: commands.Context, message: str ) -> None: - """Method to send an alert to the channel about a protect command.""" + """Sends a protect alert to the protect events channel to alert the mods + + Args: + config (munch.Munch): The guild config in the guild where the event occured + ctx (commands.Context): The context that generated this alert + message (str): The message to send to the mods about the alert + """ try: alert_channel = ctx.guild.get_channel( int(config.extensions.protect.alert_channel.value) @@ -695,7 +842,14 @@ async def send_default_delete_response( content: str, reason: str, ) -> None: - """Method for the default delete of a message.""" + """Sends a DM to a user containing a message that was deleted + + Args: + config (munch.Munch): The config of the guild where the message was sent + ctx (commands.Context): The context of the deleted message + content (str): The context of the deleted message + reason (str): The reason the message was deleted + """ embed = auxiliary.generate_basic_embed( title="Chat Protection", description=f"Message deleted. Reason: *{reason}*", @@ -707,7 +861,16 @@ async def send_default_delete_response( async def get_warnings( self: Self, user: discord.Member | discord.User, guild: discord.Guild ) -> list[bot.models.Warning]: - """Method to get the warnings of a user.""" + """Gets a list of every warning for a given user + + Args: + user (discord.Member | discord.User): The user or member object to lookup warnings for + guild (discord.Guild): The guild to get the warnings for + + Returns: + list[bot.models.Warning]: The list of all warnings that + user or member has in the given guild + """ warnings = ( await self.bot.models.Warning.query.where( self.bot.models.Warning.user_id == str(user.id) @@ -720,7 +883,17 @@ async def get_warnings( async def create_linx_embed( self: Self, config: munch.Munch, ctx: commands.Context, content: str ) -> discord.Embed | None: - """Method to create a link for long messages.""" + """This function sends a message to the linx url and puts the result in + an embed to be sent to the user + + Args: + config (munch.Munch): The guild config where the message was sent + ctx (commands.Context): The context that generated the need for a paste + content (str): The context of the message to be pasted + + Returns: + discord.Embed | None: The formatted embed, or None if there was an API error + """ if not content: return None @@ -764,7 +937,13 @@ async def create_linx_embed( async def ban_user( self: Self, ctx: commands.Context, user: discord.User, *, reason: str = None ) -> None: - """Method to ban a user from discord.""" + """The ban discord command, starts the process of banning a user + + Args: + ctx (commands.Context): The context that called this command + user (discord.User): The user that is going to be banned + reason (str, optional): The reason for the ban. Defaults to None. + """ # Uses the discord.Member class to get the top role attribute if the # user is a part of the target guild @@ -787,7 +966,13 @@ async def ban_user( async def unban_user( self: Self, ctx: commands.Context, user: discord.User, *, reason: str = None ) -> None: - """Method to unban a user from discord.""" + """The unban discord command, starts the process of unbanning a user + + Args: + ctx (commands.Context): The context that called this command + user (discord.User): The user that is going to be unbanned + reason (str, optional): The reason for the unban. Defaults to None. + """ # Uses the discord.Member class to get the top role attribute if the # user is a part of the target guild @@ -807,7 +992,13 @@ async def unban_user( async def kick_user( self: Self, ctx: commands.Context, user: discord.Member, *, reason: str = None ) -> None: - """Method to kick a user from discord.""" + """The kick discord command, starts the process of kicking a user + + Args: + ctx (commands.Context): The context that called this command + user (discord.Member): The user that is going to be kicked + reason (str, optional): The reason for the kick. Defaults to None. + """ await self.handle_kick(ctx, user, reason) config = self.bot.guild_configs[str(ctx.guild.id)] @@ -824,7 +1015,13 @@ async def kick_user( async def warn_user( self: Self, ctx: commands.Context, user: discord.Member, *, reason: str = None ) -> None: - """Method to warn a user of wrongdoing in discord.""" + """The warn discord command, starts the process of warning a user + + Args: + ctx (commands.Context): The context that called this command + user (discord.Member): The user that is going to be warned + reason (str, optional): The reason for the warn. Defaults to None. + """ await self.handle_warn(ctx, user, reason) config = self.bot.guild_configs[str(ctx.guild.id)] @@ -841,7 +1038,14 @@ async def warn_user( async def unwarn_user( self: Self, ctx: commands.Context, user: discord.Member, *, reason: str = None ) -> None: - """Method to unwarn a user on discord.""" + """The unwarn discord command, starts the process of unwarning a user + This clears ALL warnings from a member + + Args: + ctx (commands.Context): The context that called this command + user (discord.Member): The user that is going to be unwarned + reason (str, optional): The reason for the unwarn. Defaults to None. + """ await self.handle_unwarn(ctx, user, reason) @commands.has_permissions(kick_members=True) @@ -855,7 +1059,12 @@ async def unwarn_user( async def get_warnings_command( self: Self, ctx: commands.Context, user: discord.User ) -> None: - """Method to get the warnings of a user in discord.""" + """Displays all warnings that a given user has + + Args: + ctx (commands.Context): The context that called this command + user (discord.User): The user to get warnings for + """ warnings = await self.get_warnings(user, ctx.guild) if not warnings: await auxiliary.send_deny_embed( @@ -889,7 +1098,7 @@ async def mute( This should be run via discord Args: - ctx (commands.Context): _description_ + ctx (commands.Context): The context that was generated by running this command user (discord.Member): The discord.Member to be timed out. duration (str, optional): Max time is 28 days by discord API. Defaults to 1 hour @@ -958,7 +1167,14 @@ async def mute( async def unmute( self: Self, ctx: commands.Context, user: discord.Member, reason: str = None ) -> None: - """Method to unmute a user in discord.""" + """Method to mute a user in discord using the native timeout. + This should be run via discord + + Args: + ctx (commands.Context): The context that was generated by running this command + user (discord.Member): The discord.Member to have mute be cleared. + reason (str, optional): The reason for the unmute. Defaults to None. + """ can_execute = await self.can_execute(ctx, user) if not can_execute: return @@ -982,7 +1198,11 @@ async def unmute( description="Executes a purge command", ) async def purge(self: Self, ctx: commands.Context) -> None: - """Method to purge messages in discord.""" + """The bare .purge command. This does nothing but generate the help message + + Args: + ctx (commands.Context): The context in which the command was run in + """ await auxiliary.extension_help(self, ctx, self.__module__[9:]) @purge.command( @@ -993,7 +1213,12 @@ async def purge(self: Self, ctx: commands.Context) -> None: usage="[amount]", ) async def purge_amount(self: Self, ctx: commands.Context, amount: int = 1) -> None: - """Method to get the amount to purge messages in discord.""" + """Purges the most recent amount+1 messages in the channel the command was run in + + Args: + ctx (commands.Context): The context that called the command + amount (int, optional): The amount of messages to purge. Defaults to 1. + """ config = self.bot.guild_configs[str(ctx.guild.id)] if amount <= 0 or amount > config.extensions.protect.max_purge_amount.value: @@ -1013,7 +1238,13 @@ async def purge_amount(self: Self, ctx: commands.Context, amount: int = 1) -> No async def purge_duration( self: Self, ctx: commands.Context, duration_minutes: int ) -> None: - """Method to purge a channel's message up to a time.""" + """Purges the most recent duration_minutes worth of messages in the + channel the command was run in + + Args: + ctx (commands.Context): The context that called the command + duration_minutes (int): The amount of minutes to purge away + """ if duration_minutes < 0: await auxiliary.send_deny_embed( message="I can't use that input", channel=ctx.channel diff --git a/techsupport_bot/commands/relay.py b/techsupport_bot/commands/relay.py index 69770b7d0..ca1bae4da 100644 --- a/techsupport_bot/commands/relay.py +++ b/techsupport_bot/commands/relay.py @@ -40,7 +40,12 @@ async def setup(bot: bot.TechSupportBot) -> None: class DiscordToIRC(cogs.MatchCog): - """The discord side of the relay""" + """The discord side of the relay + + Attrs: + mapping (bidict): The dict that holds the IRC and discord mappings + + """ mapping = None # bidict - discord:irc @@ -422,7 +427,7 @@ def get_mentions( channel (discord.abc.Messageable): The channel that the IRC message will be sent to Returns: - List[discord.Member]: The potentially duplicated list members found from the message + list[discord.Member]: The potentially duplicated list members found from the message """ mentions = [] for word in message.split(" "): @@ -440,7 +445,7 @@ def generate_sent_message_embed( """Generates an embed to send to discord stating that a message was sent Args: - split_message (Dict[str, str]): The formatted dictionary of the IRC message + split_message (dict[str, str]): The formatted dictionary of the IRC message Returns: discord.Embed: The embed prepared and ready to send @@ -500,7 +505,7 @@ async def on_reaction_add( Args: reaction (discord.Reaction): The reaction added to the message - user (Union[discord.User, discord.Member]): The member who added the reaction + user (discord.User | discord.Member): The member who added the reaction """ channel = reaction.message.channel diff --git a/techsupport_bot/commands/role.py b/techsupport_bot/commands/role.py index 12dc0d168..a04f280be 100644 --- a/techsupport_bot/commands/role.py +++ b/techsupport_bot/commands/role.py @@ -54,7 +54,14 @@ async def setup(bot: bot.TechSupportBot) -> None: class RoleGiver(cogs.BaseCog): - """The main class for the role commands""" + """The main class for the role commands + + Attrs: + role_group (app_commands.Group): The group for the /role commands + + Args: + bot (bot.TechSupportBot): The bot object, is used for registering context menu commands + """ def __init__(self: Self, bot: bot.TechSupportBot) -> None: super().__init__(bot=bot) @@ -241,7 +248,7 @@ def generate_options( roles (list[str]): A list of roles by name to add to the options Returns: - list: A list of SelectOption with defaults set + list[discord.SelectOption]: A list of SelectOption with defaults set """ options = [] diff --git a/techsupport_bot/commands/roll.py b/techsupport_bot/commands/roll.py index f787b16b1..d53a0be4d 100644 --- a/techsupport_bot/commands/roll.py +++ b/techsupport_bot/commands/roll.py @@ -23,7 +23,12 @@ async def setup(bot: bot.TechSupportBot) -> None: class Roller(cogs.BaseCog): - """Class for the roll command for the extension.""" + """Class for the roll command for the extension. + + Attrs: + ICON_URL (str): The URL for the dice icon + + """ ICON_URL = "https://cdn.icon-icons.com/icons2/1465/PNG/512/678gamedice_100992.png" diff --git a/techsupport_bot/commands/rules.py b/techsupport_bot/commands/rules.py index d89f5cec5..d2af74920 100644 --- a/techsupport_bot/commands/rules.py +++ b/techsupport_bot/commands/rules.py @@ -17,12 +17,21 @@ async def setup(bot: bot.TechSupportBot) -> None: - """Adding the rules configuration to the config file.""" + """Loading the Rules plugin into the bot + + Args: + bot (bot.TechSupportBot): The bot object to register the cogs to + """ await bot.add_cog(Rules(bot=bot)) class Rules(cogs.BaseCog): - """Class to define the rules for the extension.""" + """Class to define the rules for the extension. + + Attrs: + RULE_ICON_URL (str): The icon to use for the rules + + """ RULE_ICON_URL = ( "https://cdn.icon-icons.com/icons2/907/PNG" @@ -31,7 +40,11 @@ class Rules(cogs.BaseCog): @commands.group(name="rule") async def rule_group(self: Self, ctx: commands.Context) -> None: - """Method for the rule group.""" + """The bare .rule command. This does nothing but generate the help message + + Args: + ctx (commands.Context): The context in which the command was run in + """ # Executed if there are no/invalid args supplied await auxiliary.extension_help(self, ctx, self.__module__[9:]) @@ -96,7 +109,11 @@ async def write_new_rules( usage="|uploaded-json|", ) async def edit_rules(self: Self, ctx: commands.Context) -> None: - """Method to edit the rules that were set up.""" + """Replaces existing rule with new rules provided by the user + + Args: + ctx (commands.Context): The context that was generated by running this command + """ uploaded_data = await auxiliary.get_json_from_attachments(ctx.message) if uploaded_data: @@ -124,7 +141,12 @@ async def edit_rules(self: Self, ctx: commands.Context) -> None: usage="[number]", ) async def get_rule(self: Self, ctx: commands.Context, content: str) -> None: - """Method to get specified rules from rule number/s specified in content.""" + """Gets the list of rules provided by the invoker + + Args: + ctx (commands.Context): The context generated by running the command + content (str): The string representing what rules to get + """ # A list of all the rule numbers to get. It starts empty numbers = [] @@ -179,7 +201,11 @@ async def get_rule(self: Self, ctx: commands.Context, content: str) -> None: description="Gets all the rules for the current server", ) async def get_all_rules(self: Self, ctx: commands.Context) -> None: - """Method to get all the rules that are set up.""" + """Prints an embed containing all of the guild rules + + Args: + ctx (commands.Context): The context that was generated when the command was run + """ rules_data = await self.get_guild_rules(ctx.guild) if not rules_data or not rules_data.get("rules"): await auxiliary.send_confirm_embed( diff --git a/techsupport_bot/commands/set.py b/techsupport_bot/commands/set.py index 6e583e8c2..dee397730 100644 --- a/techsupport_bot/commands/set.py +++ b/techsupport_bot/commands/set.py @@ -60,7 +60,7 @@ async def set_game(self: Self, ctx: commands.Context, *, game_name: str) -> None This is a command and should be accessed via Discord. Args: - ctx (discord.ext.Context): the context object for the message + ctx (commands.Context): the context object for the message game_name (str): the name of the game """ await ctx.bot.change_presence(activity=discord.Game(name=game_name)) @@ -78,7 +78,7 @@ async def set_nick(self: Self, ctx: commands.Context, *, nick: str) -> None: This is a command and should be accessed via Discord. Args: - ctx (discord.ext.Context): the context object for the message + ctx (commands.Context): the context object for the message nick (str): the bot nickname """ await ctx.message.guild.me.edit(nick=nick) diff --git a/techsupport_bot/commands/spotify.py b/techsupport_bot/commands/spotify.py index 13c9afea4..e121ac842 100644 --- a/techsupport_bot/commands/spotify.py +++ b/techsupport_bot/commands/spotify.py @@ -36,13 +36,24 @@ async def setup(bot: bot.TechSupportBot) -> None: class Spotify(cogs.BaseCog): - """Class for setting up the Spotify extension.""" + """Class for setting up the Spotify extension. + + Attrs: + AUTH_URL: The URL for the authentication API for spotify + API_URL: The URL for the search spotify API + + """ AUTH_URL = "https://accounts.spotify.com/api/token" API_URL = "https://api.spotify.com/v1/search" async def get_oauth_token(self: Self) -> str: - """Method to get an oauth token for the Spotify API.""" + """Make an API call to spotify to get oauth credentials. + These credentials are needed for any calls to the API_URL + + Returns: + str: The oauth token from spotify + """ data = {"grant_type": "client_credentials"} response = await self.bot.http_functions.http_call( "post", @@ -63,7 +74,12 @@ async def get_oauth_token(self: Self) -> str: usage="[query]", ) async def spotify(self: Self, ctx: commands.Context, *, query: str) -> None: - """Method to return a song from the Spotify API.""" + """Searches spotify and retuns a paginated list of links to songs based on the query + + Args: + ctx (commands.Context): The context generated by running this command + query (str): The user inputed query to search spotify for + """ oauth_token = await self.get_oauth_token() if not oauth_token: await auxiliary.send_deny_embed( diff --git a/techsupport_bot/commands/translate.py b/techsupport_bot/commands/translate.py index ac472b58f..be4e01ee7 100644 --- a/techsupport_bot/commands/translate.py +++ b/techsupport_bot/commands/translate.py @@ -21,9 +21,12 @@ async def setup(bot: bot.TechSupportBot) -> None: class Translator(cogs.BaseCog): - """Class to set up the translate extension.""" + """Class to set up the translate extension. - HAS_CONFIG = False + Attrs: + API_URL (str): The translated API URL + + """ API_URL = "https://api.mymemory.translated.net/get?q={}&langpair={}|{}" @@ -38,7 +41,14 @@ class Translator(cogs.BaseCog): async def translate( self: Self, ctx: commands.Context, message: str, src: str, dest: str ) -> None: - """Method to translate a message from one language to another.""" + """Translates user input into another language + + Args: + ctx (commands.Context): The context generated by running this command + message (str): The string to translate + src (str): The language the message is currently in + dest (str): The target language to translate to + """ response = await self.bot.http_functions.http_call( "get", self.API_URL.format(message, src, dest), diff --git a/techsupport_bot/commands/urban.py b/techsupport_bot/commands/urban.py index c4b9f4d54..a5960ecce 100644 --- a/techsupport_bot/commands/urban.py +++ b/techsupport_bot/commands/urban.py @@ -33,7 +33,14 @@ async def setup(bot: bot.TechSupportBot) -> None: class UrbanDictionary(cogs.BaseCog): - """Class for setting up the urban dictionary extension.""" + """Class for setting up the urban dictionary extension. + + Attrs: + BASE_URL (str): The base API URL for urban dict + SEE_MORE_URL (str): The URL to link to search results from the API + ICON_URL (str): The urban dict icon URL + + """ BASE_URL = "http://api.urbandictionary.com/v0/define?term=" SEE_MORE_URL = "https://www.urbandictionary.com/define.php?term=" @@ -48,7 +55,12 @@ class UrbanDictionary(cogs.BaseCog): usage="[query]", ) async def urban(self: Self, ctx: commands.Context, *, query: str) -> None: - """Method to get a call from the urban dictionary API.""" + """Searches urban dictionary and sends a paginated list of results to the user + + Args: + ctx (commands.Context): The context generated by running this command + query (str): The query to urban dictionary + """ response = await self.bot.http_functions.http_call( "get", f"{self.BASE_URL}{query}" ) diff --git a/techsupport_bot/commands/weather.py b/techsupport_bot/commands/weather.py index 0485e3f41..93baf2407 100644 --- a/techsupport_bot/commands/weather.py +++ b/techsupport_bot/commands/weather.py @@ -37,7 +37,14 @@ class Weather(cogs.BaseCog): """Class to set up the weather extension for the discord bot.""" def get_url(self: Self, args: list[str]) -> str: - """Method to get the API for the weather command.""" + """Generates the url to fill in API keys and data + + Args: + args (list[str]): The list of arguments passed by the user + + Returns: + str: The API url formatted and ready to be called + """ filtered_args = filter(bool, args) searches = ",".join(map(str, filtered_args)) url = "http://api.openweathermap.org/data/2.5/weather" @@ -65,7 +72,17 @@ async def weather( state_code: str = None, country_code: str = None, ) -> None: - """Method to define the weather for the command.""" + """This is the main logic for the weather command. This prepares the API data + and sends a message to discord + + Args: + ctx (commands.Context): The context generated by running this command + city_name (str): For the API, the name of the city to get weather for + state_code (str, optional): For the API, if applicable, the state code to search for. + Defaults to None. + country_code (str, optional): For the API, if needed you can add a country code to + search for. Defaults to None. + """ response = await self.bot.http_functions.http_call( "get", self.get_url([city_name, state_code, country_code]) ) @@ -81,7 +98,19 @@ async def weather( await ctx.send(embed=embed) def generate_embed(self: Self, response: munch.Munch) -> discord.Embed | None: - """Method to generate the embed for the weather command.""" + """Creates an embed filled with weather data: + Current Temp + High temp + Low temp + Humidity + Condition + + Args: + response (munch.Munch): The response from the API containing the weather data + + Returns: + discord.Embed | None: Either the formatted embed, or nothing if the API failed + """ try: embed = discord.Embed( title=f"Weather for {response.name} ({response.sys.country})" diff --git a/techsupport_bot/commands/who.py b/techsupport_bot/commands/who.py index fcda7e471..f26d1bd7c 100644 --- a/techsupport_bot/commands/who.py +++ b/techsupport_bot/commands/who.py @@ -62,7 +62,12 @@ async def setup(bot: bot.TechSupportBot) -> None: class Who(cogs.BaseCog): - """Class to set up who for the extension.""" + """Class to set up who for the extension. + + Attrs: + notes (app_commands.Group): The group for the /note commands + + """ notes = app_commands.Group( name="note", description="Command Group for the Notes Extension" @@ -70,8 +75,20 @@ class Who(cogs.BaseCog): @staticmethod async def is_writer(interaction: discord.Interaction) -> bool: - """writes notes""" + """Checks whether invoker can write notes. If at least one writer + role is not set, NO members can write notes + + Args: + interaction (discord.Interaction): The interaction in which the whois command occured + + Raises: + MissingAnyRole: Raised if the user is lacking any writer role, + but there are roles defined + AppCommandError: Raised if there are no note_writers set in the config + Returns: + bool: True if the user can run, False if they cannot + """ config = interaction.client.guild_configs[str(interaction.guild.id)] if reader_roles := config.extensions.who.note_writers.value: roles = ( @@ -94,13 +111,15 @@ async def is_writer(interaction: discord.Interaction) -> bool: @staticmethod async def is_reader(interaction: discord.Interaction) -> bool: """Checks whether invoker can read notes. If at least one reader - role is not set, all members can read notes + role is not set, NO members can read notes Args: interaction (discord.Interaction): The interaction in which the whois command occured Raises: - CommandError: Raised if there are no note_readers set in the config + MissingAnyRole: Raised if the user is lacking any reader role, + but there are roles defined + AppCommandError: Raised if there are no note_readers set in the config Returns: bool: True if the user can run, False if they cannot @@ -134,7 +153,12 @@ async def is_reader(interaction: discord.Interaction) -> bool: async def get_note( self: Self, interaction: discord.Interaction, user: discord.Member ) -> None: - """ "Method to get notes assigned to a user.""" + """This is the base of the /whois command + + Args: + interaction (discord.Interaction): The interaction that called this command + user (discord.Member): The member to lookup. Will not work on discord.User + """ embed = discord.Embed( title=f"User info for `{user}`", description="**Note: this is a bot account!**" if user.bot else "", @@ -237,7 +261,14 @@ async def modify_embed_for_mods( async def set_note( self: Self, interaction: discord.Interaction, user: discord.Member, body: str ) -> None: - """Method to set a note on a user.""" + """Adds a new note to a user + This is the entrance for the /note set command + + Args: + interaction (discord.Interaction): The interaction that called this command + user (discord.Member): The member to add the note to + body (str): The contents of the note being created + """ if interaction.user.id == user.id: embed = auxiliary.prepare_deny_embed( message="You cannot add a note for yourself" @@ -299,7 +330,13 @@ async def set_note( async def clear_notes( self: Self, interaction: discord.Interaction, user: discord.Member ) -> None: - """Method to clear notes on a user.""" + """Clears all notes on a given user + This is the entrace for the /note clear command + + Args: + interaction (discord.Interaction): The interaction that called this command + user (discord.Member): The member to remove all notes from + """ notes = await self.get_notes(user, interaction.guild) if not notes: @@ -358,7 +395,13 @@ async def clear_notes( async def all_notes( self: Self, interaction: discord.Interaction, user: discord.Member ) -> None: - """Method to get all notes for a user.""" + """Gets a file containing every note on a user + This is the entrance for the /note all command + + Args: + interaction (discord.Interaction): The interaction that called this command + user (discord.Member): The member to get all notes for + """ notes = await self.get_notes(user, interaction.guild) if not notes: @@ -388,7 +431,16 @@ async def all_notes( async def get_notes( self: Self, user: discord.Member, guild: discord.Guild ) -> list[bot.models.UserNote]: - """Method to get current notes on the user.""" + """Calls to the database to get a list of note database entries for a given user and guild + + Args: + user (discord.Member): The member to look for notes for + guild (discord.Guild): The guild to fetch the notes from + + Returns: + list[bot.models.UserNote]: The list of notes on the member/guild combo. + Will be an empty list if there are no notes + """ user_notes = ( await self.bot.models.UserNote.query.where( self.bot.models.UserNote.user_id == str(user.id) @@ -403,7 +455,12 @@ async def get_notes( # re-adds note role back to joining users @commands.Cog.listener() async def on_member_join(self: Self, member: discord.Member) -> None: - """Method to get the member on joining the guild.""" + """Automatic listener to look at users when they join the guild. + This is to apply the note role back to joining users + + Args: + member (discord.Member): The member who has just joined + """ config = self.bot.guild_configs[str(member.guild.id)] if not self.extension_enabled(config): return diff --git a/techsupport_bot/commands/winerror.py b/techsupport_bot/commands/winerror.py index aa5dd5da9..5fc940aa1 100644 --- a/techsupport_bot/commands/winerror.py +++ b/techsupport_bot/commands/winerror.py @@ -28,9 +28,11 @@ async def setup(bot: bot.TechSupportBot) -> None: @dataclass class Error: """The data to pull for the error. - name - the name of the error - source - the header file where the error is from - description - (optional) the description of the error + + Attrs: + name (str): the name of the error + source (str): the header file where the error is from + description (str): the description of the error """ name: str @@ -41,7 +43,13 @@ class Error: @dataclass class ErrorCategory: """A category of errors, based on how the error was found - This contains the name of the category and a list of errors""" + This contains the name of the category and a list of errors + + Attrs: + name (str): The name of the category of errors + errors (list[Error]): The list of errors in the category + + """ name: str errors: list[Error] @@ -227,7 +235,15 @@ def handle_hex_errors(self: Self, hex_code: int) -> ErrorCategory: return category def twos_comp(self: Self, original_value: int, bits: int) -> int: - """compute the 2's complement of int value val""" + """Compute the two's complement of an integer value. + + Args: + original_value (int): The original integer value. + bits (int): How many bits need to be shifted. + + Returns: + int: The two's complement of the original integer value. + """ if ( original_value & (1 << (bits - 1)) != 0 ): # if sign bit is set e.g., 8bit: 128-255 diff --git a/techsupport_bot/commands/wolfram.py b/techsupport_bot/commands/wolfram.py index 830c3e60d..f7be3510a 100644 --- a/techsupport_bot/commands/wolfram.py +++ b/techsupport_bot/commands/wolfram.py @@ -33,7 +33,13 @@ async def setup(bot: bot.TechSupportBot) -> None: class Wolfram(cogs.BaseCog): - """Class to set up the wolfram extension.""" + """Class to set up the wolfram extension. + + Attrs: + API_URL (str): The API URL for wolfram + ICON_URL (str): The URL for the wolfram icon + + """ API_URL = "http://api.wolframalpha.com/v1/result?appid={}&i={}" ICON_URL = "https://cdn.icon-icons.com/icons2/2107/PNG/512/file_type_wolfram_icon_130071.png" @@ -47,7 +53,12 @@ class Wolfram(cogs.BaseCog): usage="[query]", ) async def simple_search(self: Self, ctx: commands.Context, *, query: str) -> None: - """Method to search through the wolfram API.""" + """Makes a search on wolframalpha for the user input query + + Args: + ctx (commands.Context): The context which generated the command + query (str): The user inputed query to search wolfram for and output the results + """ url = self.API_URL.format( self.bot.file_config.api.api_keys.wolfram, query, diff --git a/techsupport_bot/commands/wyr.py b/techsupport_bot/commands/wyr.py index 7026b11f6..eeb6bd692 100644 --- a/techsupport_bot/commands/wyr.py +++ b/techsupport_bot/commands/wyr.py @@ -36,7 +36,11 @@ async def preconfig(self: Self) -> None: description="Creates a random Would You Rather question", ) async def wyr(self: Self, ctx: commands.Context) -> None: - """Exists to preserve undecorated wyr_command for testing""" + """Exists to preserve undecorated wyr_command for testing + + Args: + ctx (commands.Context): The context in which the command was run + """ await self.wyr_command(ctx) async def wyr_command(self: Self, ctx: commands.Context) -> None: diff --git a/techsupport_bot/commands/xkcd.py b/techsupport_bot/commands/xkcd.py index 2764fd75f..a27d4072a 100644 --- a/techsupport_bot/commands/xkcd.py +++ b/techsupport_bot/commands/xkcd.py @@ -24,7 +24,13 @@ async def setup(bot: bot.TechSupportBot) -> None: class XKCD(cogs.BaseCog): - """Class to create the xkcd for the extension.""" + """Class to create the xkcd for the extension. + + Attrs: + MOST_RECENT_API_URL (str): The URL for the most recent comic + SPECIFIC_API_URL (str): The URL for a given number comic + + """ MOST_RECENT_API_URL = "https://xkcd.com/info.0.json" SPECIFIC_API_URL = "https://xkcd.com/%s/info.0.json" @@ -37,7 +43,14 @@ class XKCD(cogs.BaseCog): async def xkcd( self: Self, ctx: commands.Context, number: int | None = None ) -> None: - """Method to create the command for xkcd.""" + """Entry point for the xkcd command. This either shows extension help + Or will display a comic if a number is entered + + Args: + ctx (commands.Context): The context genereated by this command + number (int | None, optional): The comic number to lookup and display. + Defaults to None. + """ if number: await self.numbered_comic(ctx, number) else: @@ -50,7 +63,11 @@ async def xkcd( description="Gets a random XKCD comic", ) async def random_comic(self: Self, ctx: commands.Context) -> None: - """Method to get a random xkcd comic.""" + """Gets a completely random comic after looking up the latest comic and displays it + + Args: + ctx (commands.Context): The context generated by running this command + """ most_recent_comic_data = await self.api_call() if most_recent_comic_data.status_code != 200: await auxiliary.send_deny_embed( @@ -92,12 +109,22 @@ async def random_comic(self: Self, ctx: commands.Context) -> None: ) async def shadow_number(self: Self, ctx: commands.Context, *, _: int) -> None: """Method to generate the help entry for the group parent and - prevent direct invocation.""" - ctx.message.content = f"{ctx.message.content[0]}xkcd invalid" + prevent direct invocation. + This exists due to an oversight in the help menu generation. + This command should never be called + + Args: + ctx (commands.Context): The context generate by this command + """ await auxiliary.extension_help(self, ctx, self.__module__[9:]) async def numbered_comic(self: Self, ctx: commands.Context, number: int) -> None: - """Method to get a specific number comic from xkcd.""" + """Gets the comic from XKCD by number + + Args: + ctx (commands.Context): The context that called an XKCD command + number (int): The comic number to get + """ comic_data = await self.api_call(number=number) if comic_data.status_code != 200: await auxiliary.send_deny_embed( @@ -116,14 +143,30 @@ async def numbered_comic(self: Self, ctx: commands.Context, number: int) -> None await ctx.send(embed=embed) async def api_call(self: Self, number: int = None) -> munch.Munch: - """Method for the API call for xkcd.""" + """Makes an API call to xkcd to get the json for a given comic + + Args: + number (int, optional): The comic number to get. + If none is provided it will use the last comic called by the system. + Defaults to None. + + Returns: + munch.Munch: The response from the API + """ url = self.SPECIFIC_API_URL % (number) if number else self.MOST_RECENT_API_URL response = await self.bot.http_functions.http_call("get", url) return response def generate_embed(self: Self, comic_data: munch.Munch) -> discord.Embed: - """Method to generate the embed for the xkcd command.""" + """Turns a comic json into an embed to be sent to disocrd + + Args: + comic_data (munch.Munch): The API response containing the comic + + Returns: + discord.Embed: The formatted embed ready to be sent + """ num = comic_data.get("num") image_url = comic_data.get("img") title = comic_data.get("safe_title") diff --git a/techsupport_bot/core/auxiliary.py b/techsupport_bot/core/auxiliary.py index 9161fd513..91dbf2a44 100644 --- a/techsupport_bot/core/auxiliary.py +++ b/techsupport_bot/core/auxiliary.py @@ -99,7 +99,10 @@ def construct_mention_string(targets: list[discord.User]) -> str: """Builds a string of mentions from a list of users. Args: - targets ([]discord.User): the list of users to mention + targets (list[discord.User]): the list of users to mention + + Returns: + str: A string containing space separated user mention code """ constructed = set() @@ -250,6 +253,10 @@ def config_schema_matches(input_config: dict, current_config: dict) -> list[str] Args: input_config (dict): the config to be added current_config (dict): the current config + + Returns: + list[str] | None: Returns a list of changes to the config, if it was changed. + Otherwise returns nothing, signifying no changes """ if ( any(key not in current_config for key in input_config.keys()) @@ -285,12 +292,21 @@ def with_typing(command: commands.Command) -> commands.Command: Args: command (commands.Command): the command object to modify + + Returns: + commands.Command: The modified command wrapped with the typing call """ original_callback = command.callback @wraps(original_callback) async def typing_wrapper(*args: tuple, **kwargs: dict[str, Any]) -> None: - """The wrapper to add typing to any given function and call the original function""" + """The wrapper to add typing to any given function and call the original function + + Args: + *args (tuple): Used to preserve any and all original arguments to the original command + **kwargs (dict[str, Any]): Used to preserve any and all original arguments + to the original command + """ context = args[1] typing_func = getattr(context, "typing", None) @@ -314,14 +330,17 @@ async def typing_wrapper(*args: tuple, **kwargs: dict[str, Any]) -> None: return command -def get_object_diff( - before: object, after: object, attrs_to_check: list -) -> munch.Munch | dict: +def get_object_diff(before: object, after: object, attrs_to_check: list) -> munch.Munch: """Finds differences in before, after object pairs. - before (obj): the before object - after (obj): the after object - attrs_to_check (list): the attributes to compare + Args: + before (object): the before object + after (object): the after object + attrs_to_check (list): the attributes to compare + + Returns: + munch.Munch: The set of differences, will contain a .before + and a .after index, with everything changed """ result = {} @@ -348,6 +367,9 @@ def add_diff_fields(embed: discord.Embed, diff: dict) -> discord.Embed: Args: embed (discord.Embed): the embed object diff (dict): the diff data for an object + + Returns: + discord.Embed: Shows the difference between two objects in an embed """ for attr, diff_data in diff.items(): attru = attr.upper() @@ -400,30 +422,31 @@ def add_diff_fields(embed: discord.Embed, diff: dict) -> discord.Embed: def get_help_embed_for_extension( - self: cogs.BaseCog, extension_name: str, command_prefix: str + cog: cogs.BaseCog, extension_name: str, command_prefix: str ) -> discord.Embed: """Gets the help embed for an extension. Defined so it doesn't have to be written out twice Args: + cog (cogs.BaseCog): The cog that needs the commands put into a help menu extension_name (str): the name of the extension to show the help for command_prefix (str): passed to the func as it has to be awaited - returns: - embed (discord.Embed): Embed containing all commands with their description + Returns: + discord.Embed: Embed containing all commands with their description """ embed = discord.Embed() embed.title = f"Extension Commands: `{extension_name}`" # Sorts commands alphabetically - command_list = list(self.bot.walk_commands()) + command_list = list(cog.bot.walk_commands()) command_list.sort(key=lambda command: command.name) # Loops through every command in the bots library for command in command_list: # Gets the command name - command_extension_name = self.bot.get_command_extension_name(command) + command_extension_name = cog.bot.get_command_extension_name(command) # Continues the loop if the command isn't a part of the target extension if extension_name != command_extension_name or issubclass( @@ -453,7 +476,7 @@ def get_help_embed_for_extension( async def extension_help( - self: cogs.BaseCog, ctx: commands.Context, extension_name: str + cog: cogs.BaseCog, ctx: commands.Context, extension_name: str ) -> None: """Automatically prompts for help if improper syntax for an extension is called. @@ -462,6 +485,7 @@ async def extension_help( way to get the extension name regardless of aliases Args: + cog (cogs.BaseCog): The cog that needs the commands put into a help menu ctx (commands.Context): context of the message extension_name (str): the name of the extension to show the help for """ @@ -472,7 +496,7 @@ async def extension_help( valid_commands = [] valid_args = [] # Loops through each command for said extension - for command in self.bot.get_cog(self.qualified_name).walk_commands(): + for command in cog.bot.get_cog(cog.qualified_name).walk_commands(): valid_commands.append(command.name) valid_args.append(command.aliases) @@ -494,7 +518,7 @@ async def extension_help( await ctx.send( embed=get_help_embed_for_extension( - self, extension_name, await self.bot.get_prefix(ctx.message) + cog, extension_name, await cog.bot.get_prefix(ctx.message) ) ) @@ -502,7 +526,7 @@ async def extension_help( elif len(ctx.message.content.split()) == 1: await ctx.send( embed=get_help_embed_for_extension( - self, extension_name, await self.bot.get_prefix(ctx.message) + cog, extension_name, await cog.bot.get_prefix(ctx.message) ) ) diff --git a/techsupport_bot/core/cogs.py b/techsupport_bot/core/cogs.py index 1b01b4c44..3f94a6198 100644 --- a/techsupport_bot/core/cogs.py +++ b/techsupport_bot/core/cogs.py @@ -7,7 +7,6 @@ from typing import TYPE_CHECKING, Any, Self import discord -import gino import munch from botlogging import LogContext, LogLevel from discord.ext import commands @@ -19,10 +18,15 @@ class BaseCog(commands.Cog): """The base cog to use when making extensions. + Attrs: + COG_TYPE (str): The string representation for the type of cog + KEEP_COG_ON_FAILURE (bool): Whether or not to keep the cog loaded if there was an error + Args: - bot (Bot): the bot object - models (List[gino.Model]): the Postgres models for the extension + bot (bot.TechSupportBot): the bot object no_guild (bool): True if the extension should run globally + extension_name(str): The name of the extension + if it needs to be different than the file name """ COG_TYPE = "Base" @@ -31,7 +35,6 @@ class BaseCog(commands.Cog): def __init__( self: Self, bot: bot.TechSupportBot, - models: list[gino.Model] = None, no_guild: bool = False, extension_name: str = None, ) -> None: @@ -39,12 +42,6 @@ def __init__( self.no_guild = no_guild self.extension_name = extension_name - if models is None: - models = [] - self.models = munch.Munch() - for model in models: - self.models[model.__name__] = model - asyncio.create_task(self._preconfig()) async def _handle_preconfig( @@ -55,7 +52,7 @@ async def _handle_preconfig( This makes the extension unload when there is an error. Args: - handler (asyncio.coroutine): the preconfig handler + handler (Callable[..., Awaitable[None]]): the preconfig handler """ await self.bot.wait_until_ready() @@ -81,7 +78,11 @@ def extension_enabled(self: Self, config: munch.Munch) -> bool: """Checks if an extension is currently enabled for a given config. Args: - config (dict): the context/guild config + config (munch.Munch): the context/guild config + + Returns: + bool: True if the extension is enabled for the context + False if it isn't """ if config is None: config = {} @@ -95,6 +96,9 @@ class MatchCog(BaseCog): Cog for matching a specific context criteria and responding. This makes the process of handling events simpler for development. + + Attrs: + COG_TYPE (str): The string representation for the type of cog """ COG_TYPE = "Match" @@ -147,8 +151,12 @@ async def match( Args: _config (munch.Munch): the config associated with the context - _ctx (context): the context object + _ctx (commands.Context): the context object _content (str): the message content + + Returns: + bool: Base function to determine if the message should be matched in the extension. + If this is true, response() will be called """ return True @@ -162,9 +170,10 @@ async def response( """Performs a response if the match is valid. Args: - _config (dict): the config associated with the context - _ctx (context): the context object + _config (munch.Munch): the config associated with the context + _ctx (commands.Context): the context object _content (str): the message content + _result (bool): the boolean result from match() """ @@ -173,8 +182,16 @@ class LoopCog(BaseCog): This currently doesn't utilize the tasks library. + Attrs: + COG_TYPE (str): The string representation for the type of cog + DEFAULT_WAIT (int): The default time to sleep for + TRACKER_WAIT (int): The time to wait before looking for new channels + ON_START (bool): Should this loop be only a manual start call + CHANNELS_KEY (str): The config key to use for looping + Args: - bot (Bot): the bot object + *args (tuple): Args to pass to the BaseCog init + **kwargs (dict[str, Any]): Args to pass to the BaseCog init) """ COG_TYPE: str = "Loop" @@ -387,7 +404,7 @@ async def execute( Args: _config (munch.Munch): the config object for the guild _guild (discord.Guild): the guild associated with the execution - _target_channel (discord.Channel): the channel object to use + _target_channel (discord.abc.Messageable): the channel object to use """ async def _default_wait(self: Self) -> None: diff --git a/techsupport_bot/core/custom_errors.py b/techsupport_bot/core/custom_errors.py index 0c3b05778..372c283ca 100644 --- a/techsupport_bot/core/custom_errors.py +++ b/techsupport_bot/core/custom_errors.py @@ -38,7 +38,11 @@ def __init__(self: Self) -> None: class FactoidNotFoundError(commands.errors.CommandError): - """Thrown when a factoid is not found.""" + """Thrown when a factoid is not found. + + Args: + factoid (str): The name of the factoid that couldn't be found + """ def __init__(self: Self, factoid: str) -> None: self.dont_print_trace = True @@ -53,7 +57,11 @@ def __init__(self: Self) -> None: class HTTPRateLimit(commands.errors.CommandError): - """An API call is on rate limit""" + """An API call is on rate limit + + Args: + wait (int): The amount of seconds left until the rate limit expires + """ def __init__(self: Self, wait: int) -> None: self.wait = wait @@ -62,9 +70,13 @@ def __init__(self: Self, wait: int) -> None: class ErrorResponse: """Object for generating a custom error message from an exception. + Attrs: + DEFAULT_MESSAGE (str): The default error message for unclassified errors + Args: message_format (str): the substition formatted (%s) message - lookups (Union[str, list]): the lookup objects to reference + lookups (str | list[Any]): the lookup objects to reference + dont_print_trace (bool): If true, the stack trace generated will not be logged """ DEFAULT_MESSAGE = "I ran into an error processing your command" @@ -96,6 +108,9 @@ def default_message(self: Self, exception: Exception = None) -> str: Args: exception (Exception): the exception to reference + + Returns: + str: The string message to print to the user representing the exception """ return ( f"{self.DEFAULT_MESSAGE}: *{exception}*" @@ -108,6 +123,9 @@ def get_message(self: Self, exception: Exception = None) -> str: Args: exception (Exception): the exception to reference + + Returns: + str: The formatted message filling in any variables from the exception """ if not self.message_format: return self.default_message(exception=exception) diff --git a/techsupport_bot/core/databases.py b/techsupport_bot/core/databases.py index 2293c8bb3..1ad16e0d8 100644 --- a/techsupport_bot/core/databases.py +++ b/techsupport_bot/core/databases.py @@ -21,7 +21,19 @@ def setup_models(bot: bot.TechSupportBot) -> None: class Applications(bot.db.Model): """The postgres table for applications - Currenty used in application.py""" + Currenty used in application.py + + Attrs: + __tablename__ (str): The name of the table in postgres + pk (int): The automatic primary key + guild_id (str): The string of the guild ID the application is in + applicant_name (str): The name of the user who submitted the app + applicant_id (str): The string representation of the ID of the user + application_status (str): The string representation of the status + background (str): The answer to the background question of the application + reason (str): The answer to the reason question of the application + application_time (datetime): The time the application was submitted + """ __tablename__ = "applications" @@ -38,7 +50,14 @@ class Applications(bot.db.Model): class ApplicationBans(bot.db.Model): """The postgres table for users banned from applications - Currently used in application.py and who.py""" + Currently used in application.py and who.py + + Attrs: + __tablename__ (str): The name of the table in postgres + pk (int): The automatic primary key + guild_id (str): The string of the guild ID the applicant is banned in + applicant_id (str): The string representation of the ID of the user + """ __tablename__ = "appbans" @@ -48,7 +67,18 @@ class ApplicationBans(bot.db.Model): class DuckUser(bot.db.Model): """The postgres table for ducks - Currently used in duck.py""" + Currently used in duck.py + + Attrs: + __tablename__ (str): The name of the table in postgres + pk (int): The automatic primary key + author_id (str): The string representation of the ID of the user + guild_id (str): The string of the guild ID the duckuser has participated in + befriend_count (int): The amount of ducks the user has befriended + kill_count (int): The amount of ducks the user has killed + updated (datetime): The last time the duck user interacted with a duck + speed_record (float): The fastest this user has killed or friended a duck + """ __tablename__ = "duckusers" @@ -62,7 +92,22 @@ class DuckUser(bot.db.Model): class Factoid(bot.db.Model): """The postgres table for factoids - Currently used in factoid.py""" + Currently used in factoid.py + + Attrs: + __tablename__ (str): The name of the table in postgres + factoid_id (int): The primary key of the factoid + name (str): The name of the factoid + guild (str): The string guild ID for the guild that the factoid is in + message (str): The string message of the factoid + time (datetime): When the factoid was created NOT edited + embed_config (str): The json of the factoid + hidden (bool): If the factoid should be hidden or not + protected (bool): If the factoid should be protected + disabled (bool): If the factoid should be disabled + restricted (bool): If the factoid should be restricted + alias (str): The string representation of the parent + """ __tablename__ = "factoids" @@ -80,7 +125,15 @@ class Factoid(bot.db.Model): class FactoidJob(bot.db.Model): """The postgres table for factoid loops - Currently used in factoid.py""" + Currently used in factoid.py + + Attrs: + __tablename__ (str): The name of the table in postgres + job_id (int): The primary key, ID of the job + factoid (int): The primary key of the linked factoid + channel (str): The channel this loop needs to run in + cron (str): The frequency this job should run + """ __tablename__ = "factoid_jobs" @@ -93,7 +146,18 @@ class FactoidJob(bot.db.Model): class Grab(bot.db.Model): """The postgres table for grabs - Currently used in grab.py""" + Currently used in grab.py + + Attrs: + __tablename__ (str): The name of the table in postgres + pk (int): The primary key for this database + author_id (str): The ID of the author of the original grab message + channel (str): The channel the message was grabbed from + guild (str): The guild the message was grabbed from + message (str): The string contents of the message + time (datetime): The time the message was grabbed + nsfw (bool): Whether the message was grabbed in an NSFW channel + """ __tablename__ = "grabs" @@ -107,7 +171,15 @@ class Grab(bot.db.Model): class IRCChannelMapping(bot.db.Model): """The postgres table for IRC->discord maps - Currently used in relay.py""" + Currently used in relay.py + + Attrs: + __tablename__ (str): The name of the table in postgres + map_id (int): The primary key for the database + guild_id (str): The guild where the discord channel exists at + discord_channel_id (str): The ID of the discord channel + irc_channel_id (str): The name of the IRC channel + """ __tablename__ = "ircchannelmap" map_id = bot.db.Column(bot.db.Integer, primary_key=True) @@ -117,14 +189,29 @@ class IRCChannelMapping(bot.db.Model): class ModmailBan(bot.db.Model): """The postgres table for modmail bans - Currently used in modmail.py""" + Currently used in modmail.py + + Attrs: + __tablename__ (str): The name of the table in postgres + user_id (str): The ID of the user banned from modmail + """ __tablename__ = "modmail_bans" user_id = bot.db.Column(bot.db.String, default=None, primary_key=True) class UserNote(bot.db.Model): """The postgres table for notes - Currently used in who.py""" + Currently used in who.py + + Attrs: + __tablename__ (str): The name of the table in postgres + pk (int): The primary key for this database + user_id (str): The user ID that has a note + guild_id (str): The guild ID that the note belongs to + updated (datetime): The time the note was created on + author_id (str): The author of the note + body (str): The contents of the note + """ __tablename__ = "usernote" @@ -137,7 +224,16 @@ class UserNote(bot.db.Model): class Warning(bot.db.Model): """The postgres table for warnings - Currently used in protect.py and who.py""" + Currently used in protect.py and who.py + + Attrs: + __tablename__ (str): The name of the table in postgres + pk (int): The primary key for the database + user_id (str): The user who got warned + guild_id (str): The guild this warn occured in + reason (str): The reason for the warn + time (datetime): The time the warning was given + """ __tablename__ = "warnings" pk = bot.db.Column(bot.db.Integer, primary_key=True) @@ -148,7 +244,15 @@ class Warning(bot.db.Model): class Config(bot.db.Model): """The postgres table for guild config - Currently used nearly everywhere""" + Currently used nearly everywhere + + Attrs: + __tablename__ (str): The name of the table in postgres + pk (int): The primary key for the database + guild_id (str): The ID of the guild this config is for + config (str): The config text + update_time (datetime): The time the config was last updated + """ __tablename__ = "guild_config" pk = bot.db.Column(bot.db.Integer, primary_key=True) @@ -158,7 +262,14 @@ class Config(bot.db.Model): class Listener(bot.db.Model): """The postgres table for listeners - Currently used in listen.py""" + Currently used in listen.py + + Attrs: + __tablename__ (str): The name of the table in postgres + pk (int): The primary key for the database + src_id (str): The source channel for the listener + dst_id (str): The destination channel for the listener + """ __tablename__ = "listeners" pk = bot.db.Column(bot.db.Integer, primary_key=True) @@ -167,7 +278,14 @@ class Listener(bot.db.Model): class Rule(bot.db.Model): """The postgres table for rules - Currently used in rules.py""" + Currently used in rules.py + + Attrs: + __tablename__ (str): The name of the table in postgres + pk (int): The primary key for the database + guild_id (str): The ID of the guild that these rules are for + rules (str): The json representation of the rules + """ __tablename__ = "guild_rules" pk = bot.db.Column(bot.db.Integer, primary_key=True) diff --git a/techsupport_bot/core/extensionconfig.py b/techsupport_bot/core/extensionconfig.py index f6b5c4a02..3b667cc71 100644 --- a/techsupport_bot/core/extensionconfig.py +++ b/techsupport_bot/core/extensionconfig.py @@ -30,7 +30,8 @@ def add( datatype (str): the datatype metadata for the entry title (str): the title of the entry description (str): the description of the entry - default (Any): the default value to use for the entry + default (str | bool | int | list[str] | list[int] | dict[str, str]): + the default value to use for the config entry """ self.data[key] = { "datatype": datatype, diff --git a/techsupport_bot/core/http.py b/techsupport_bot/core/http.py index bcefc825e..33bc89ba6 100644 --- a/techsupport_bot/core/http.py +++ b/techsupport_bot/core/http.py @@ -26,6 +26,10 @@ class HTTPCalls: """ This requires a class so it can store the bot variable upon setup This allows access to the config file and logging + + Args: + bot (bot.TechSupportBot): The bot object that will be making http calls. + This is only used for access to file_config and nothing more """ def __init__(self: Self, bot: bot.TechSupportBot) -> None: @@ -85,18 +89,20 @@ async def http_call( """Makes an HTTP request. By default this returns JSON/dict with the status code injected. + use_cache (bool): True if the GET result should be grabbed from cache + get_raw_response (bool): True if the actual response object should be returned Args: method (str): the HTTP method to use url (str): the URL to call - use_cache (bool): True if the GET result should be grabbed from cache - get_raw_response (bool): True if the actual response object should be returned + *args (tuple): Used to allow any combination of parameters to the API + **kwargs (dict[str, Any]): Used to allow any combination of parameters to the API Raises: HTTPRateLimit: Raised if the API is currently on cooldown Returns: - _type_: _description_ + munch.Munch: The munch object containing the response from the API """ # Get the URL not the endpoint being called diff --git a/techsupport_bot/functions/events.py b/techsupport_bot/functions/events.py index 0a593f6ab..459c7f36e 100644 --- a/techsupport_bot/functions/events.py +++ b/techsupport_bot/functions/events.py @@ -34,7 +34,12 @@ class EventLogger(cogs.BaseCog): async def on_message_edit( self: Self, before: discord.Message, after: discord.Message ) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_message_edit""" + """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_message_edit + + Args: + before (discord.Message): The previous version of the message + after (discord.Message): The current version of the message + """ # this seems to spam, not sure why if before.content == after.content: return @@ -71,7 +76,11 @@ async def on_message_edit( @commands.Cog.listener() async def on_message_delete(self: Self, message: discord.Message) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_message_delete""" + """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_message_delete + + Args: + message (discord.Message): The deleted message + """ guild = getattr(message.channel, "guild", None) # Ignore ephemeral slash command messages @@ -111,6 +120,9 @@ async def on_bulk_message_delete( ) -> None: """ See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_bulk_message_delete + + Args: + messages (list[discord.Message]): The messages that have been deleted """ guild = getattr(messages[0].channel, "guild", None) @@ -143,7 +155,12 @@ async def on_bulk_message_delete( async def on_reaction_add( self: Self, reaction: discord.Reaction, user: discord.Member | discord.User ) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_reaction_add""" + """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_reaction_add + + Args: + reaction (discord.Reaction): The current state of the reaction + user (discord.Member | discord.User): The user who added the reaction + """ guild = getattr(reaction.message.channel, "guild", None) if isinstance(reaction.message.channel, discord.DMChannel): @@ -187,7 +204,12 @@ async def on_reaction_add( async def on_reaction_remove( self: Self, reaction: discord.Reaction, user: discord.Member | discord.User ) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_reaction_remove""" + """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_reaction_remove + + Args: + reaction (discord.Reaction): The current state of the reaction + user (discord.Member | discord.User): The user whose reaction was removed + """ guild = getattr(reaction.message.channel, "guild", None) if isinstance(reaction.message.channel, discord.DMChannel): @@ -233,6 +255,10 @@ async def on_reaction_clear( ) -> None: """ See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_reaction_clear + + Args: + message (discord.Message): The message that had its reactions cleared + reactions (list[discord.Reaction]): The reactions that were removed """ guild = getattr(message.channel, "guild", None) @@ -265,6 +291,9 @@ async def on_guild_channel_delete( ) -> None: """ See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_channel_delete + + Args: + channel (discord.abc.GuildChannel): The channel that got deleted """ embed = discord.Embed() embed.add_field(name="Channel Name", value=channel.name) @@ -291,6 +320,9 @@ async def on_guild_channel_create( ) -> None: """ See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_channel_create + + Args: + channel (discord.abc.GuildChannel): The channel that got created """ embed = discord.Embed() embed.add_field(name="Channel Name", value=channel.name) @@ -315,6 +347,10 @@ async def on_guild_channel_update( ) -> None: """ See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_channel_update + + Args: + before (discord.abc.GuildChannel): The updated guild channel's old info + after (discord.abc.GuildChannel): The updated guild channel's new info """ attrs = [ "category", @@ -354,6 +390,12 @@ async def on_guild_channel_pins_update( """ See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_channel_pins_update + + Args: + channel (discord.abc.GuildChannel | discord.Thread): The guild channel + that had its pins updated. + _last_pin (datetime.datetime | None): The latest message that was pinned as an + aware datetime in UTC. Could be None. """ embed = discord.Embed() embed.add_field(name="Channel Name", value=channel.name) @@ -379,6 +421,9 @@ async def on_guild_integrations_update(self: Self, guild: discord.Guild) -> None """ See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_integrations_update + + Args: + guild (discord.Guild): The guild that had its integrations updated. """ embed = discord.Embed() embed.add_field(name="Server", value=guild) @@ -395,7 +440,11 @@ async def on_guild_integrations_update(self: Self, guild: discord.Guild) -> None @commands.Cog.listener() async def on_webhooks_update(self: Self, channel: discord.abc.GuildChannel) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_webhooks_update""" + """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_webhooks_update + + Args: + channel (discord.abc.GuildChannel): The channel that had its webhooks updated. + """ embed = discord.Embed() embed.add_field(name="Channel", value=channel.name) embed.add_field(name="Server", value=channel.guild) @@ -419,7 +468,12 @@ async def on_webhooks_update(self: Self, channel: discord.abc.GuildChannel) -> N async def on_member_update( self: Self, before: discord.Member, after: discord.Member ) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_member_update""" + """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_member_update + + Args: + before (discord.Member): The updated member's old info + after (discord.Member): Teh updated member's new info + """ changed_role = set(before.roles) ^ set(after.roles) if changed_role: if len(before.roles) < len(after.roles): @@ -448,7 +502,11 @@ async def on_member_update( @commands.Cog.listener() async def on_member_remove(self: Self, member: discord.Member) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_member_remove""" + """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_member_remove + + Args: + member (discord.Member): The member who left + """ embed = discord.Embed() embed.add_field(name="Member", value=member) embed.add_field(name="Server", value=member.guild.name) @@ -468,7 +526,11 @@ async def on_member_remove(self: Self, member: discord.Member) -> None: @commands.Cog.listener() async def on_guild_remove(self: Self, guild: discord.Guild) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_remove""" + """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_remove + + Args: + guild (discord.Guild): The guild that got removed + """ embed = discord.Embed() embed.add_field(name="Server", value=guild.name) await self.bot.logger.send_log( @@ -506,6 +568,10 @@ async def on_guild_update( ) -> None: """ See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_update + + Args: + before (discord.Guild): The guild prior to being updated + after (discord.Guild): The guild after being updated """ diff = auxiliary.get_object_diff( before, @@ -551,7 +617,11 @@ async def on_guild_update( @commands.Cog.listener() async def on_guild_role_create(self: Self, role: discord.Role) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_role_create""" + """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_role_create + + Args: + role (discord.Role): The role that was created + """ embed = discord.Embed() embed.add_field(name="Server", value=role.guild.name) log_channel = await self.bot.get_log_channel_from_guild( @@ -570,7 +640,11 @@ async def on_guild_role_create(self: Self, role: discord.Role) -> None: @commands.Cog.listener() async def on_guild_role_delete(self: Self, role: discord.Role) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_role_delete""" + """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_role_delete + + Args: + role (discord.Role): The role that was deleted + """ embed = discord.Embed() embed.add_field(name="Server", value=role.guild.name) log_channel = await self.bot.get_log_channel_from_guild( @@ -590,7 +664,12 @@ async def on_guild_role_delete(self: Self, role: discord.Role) -> None: async def on_guild_role_update( self: Self, before: discord.Role, after: discord.Role ) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_role_update""" + """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_role_update + + Args: + before (discord.Role): The updated role's old info. + after (discord.Role): The updated role's updated info. + """ attrs = ["color", "mentionable", "name", "permissions", "position", "tags"] diff = auxiliary.get_object_diff(before, after, attrs) @@ -617,14 +696,17 @@ async def on_guild_role_update( async def on_guild_emojis_update( self: Self, guild: discord.Guild, - before: Sequence[discord.Emoji], _: Sequence[discord.Emoji], + __: Sequence[discord.Emoji], ) -> None: """ See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_guild_emojis_update + + Args: + guild (discord.Guild): The guild who got their emojis updated. """ embed = discord.Embed() - embed.add_field(name="Server", value=before.name) + embed.add_field(name="Server", value=guild.name) log_channel = await self.bot.get_log_channel_from_guild( guild, key="guild_events_channel" @@ -632,7 +714,7 @@ async def on_guild_emojis_update( await self.bot.logger.send_log( message=f"Emojis updated in guild with ID {guild.id}", level=LogLevel.INFO, - context=LogContext(guild=before), + context=LogContext(guild=guild), channel=log_channel, embed=embed, ) @@ -641,7 +723,13 @@ async def on_guild_emojis_update( async def on_member_ban( self: Self, guild: discord.Guild, user: discord.User | discord.Member ) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_member_ban""" + """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_member_ban + + Args: + guild (discord.Guild): The guild the user got banned from + user (discord.User | discord.Member): The user that got banned. Can be either User + or Member depending if the user was in the guild or not at the time of removal. + """ embed = discord.Embed() embed.add_field(name="User", value=user) embed.add_field(name="Server", value=guild.name) @@ -662,7 +750,12 @@ async def on_member_ban( async def on_member_unban( self: Self, guild: discord.Guild, user: discord.User ) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_member_unban""" + """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_member_unban + + Args: + guild (discord.Guild): The guild the user got unbanned from + user (discord.User): The user that got unbanned + """ embed = discord.Embed() embed.add_field(name="User", value=user) embed.add_field(name="Server", value=guild.name) @@ -681,7 +774,11 @@ async def on_member_unban( @commands.Cog.listener() async def on_member_join(self: Self, member: discord.Member) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_member_join""" + """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_member_join + + Args: + member (discord.Member): The member who joined + """ embed = discord.Embed() embed.add_field(name="Member", value=member) embed.add_field(name="Server", value=member.guild.name) @@ -702,7 +799,12 @@ async def on_member_join(self: Self, member: discord.Member) -> None: @commands.Cog.listener() async def on_command(self: Self, ctx: commands.Context) -> None: """ - See: https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#discord.on_command + See: + https://discordpy.readthedocs.io/en/stable/ext/commands/ + api.html#discord.discord.ext.commands.on_command + + Args: + ctx (commands.Context): The invocation context """ embed = discord.Embed() embed.add_field(name="User", value=ctx.author) diff --git a/techsupport_bot/functions/logger.py b/techsupport_bot/functions/logger.py index df49e56bb..ef3c23822 100644 --- a/techsupport_bot/functions/logger.py +++ b/techsupport_bot/functions/logger.py @@ -40,7 +40,15 @@ class Logger(cogs.MatchCog): async def match( self: Self, config: munch.Munch, ctx: commands.Context, _: str ) -> bool: - """Method to match the logging channel to the map.""" + """Matches any message and checks if it is in a channel with a logger rule + + Args: + config (munch.Munch): The config for the guild where the message was sent + ctx (commands.Context): The context of the original message + + Returns: + bool: Whether the message should be logged or not + """ if isinstance(ctx.channel, discord.Thread): if ( not str(ctx.channel.parent_id) @@ -55,7 +63,12 @@ async def match( async def response( self: Self, config: munch.Munch, ctx: commands.Context, _: str, __: bool ) -> None: - """Method to generate the response from the logger.""" + """If a message should be logged, this logs the message + + Args: + config (munch.Munch): The guild config where the message was sent + ctx (commands.Context): The context that was generated when the message was sent + """ # Get the ID of the channel, or parent channel in the case of threads mapped_id = config.extensions.logger.channel_map.value.get( str(getattr(ctx.channel, "parent_id", ctx.channel.id)) diff --git a/techsupport_bot/functions/nickname.py b/techsupport_bot/functions/nickname.py index 77d493869..fef35d500 100644 --- a/techsupport_bot/functions/nickname.py +++ b/techsupport_bot/functions/nickname.py @@ -83,7 +83,13 @@ class AutoNickName(cogs.BaseCog): @commands.Cog.listener() async def on_member_join(self: Self, member: discord.Member) -> None: - """See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_member_join""" + """ + This starts the running of the auto nickname formatter + See: https://discordpy.readthedocs.io/en/latest/api.html#discord.on_member_join + + Args: + member (discord.Member): The member who joined + """ config = self.bot.guild_configs[str(member.guild.id)] # Don't do anything if the filter is off for the guild diff --git a/techsupport_bot/ircrelay/formatting.py b/techsupport_bot/ircrelay/formatting.py index b714bd37a..0c254dd47 100644 --- a/techsupport_bot/ircrelay/formatting.py +++ b/techsupport_bot/ircrelay/formatting.py @@ -15,7 +15,7 @@ def parse_irc_message(event: irc.client.Event) -> dict[str, str]: event (irc.client.Event): The event object that triggered this function Returns: - Dict[str, str]: The formatted message + dict[str, str]: The formatted message """ # Looking for username, hostmask, action, channel, content username = event.source.split("!")[0] @@ -41,7 +41,7 @@ def parse_ban_message(event: irc.client.Event) -> dict[str, str]: event (irc.client.Event): The event object that triggered this function Returns: - Dict[str, str]: The formatted message + dict[str, str]: The formatted message """ username = event.source.split("!")[0] hostmask = event.source.split("!")[1] @@ -166,7 +166,7 @@ def get_file_links(message_attachments: list[discord.Attachment]) -> str: """Turns a list of attachments into a string containing links to them Args: - message_attachments (List[discord.Attachment]): The list of attachments from a + message_attachments (list[discord.Attachment]): The list of attachments from a discord.Message object Returns: diff --git a/techsupport_bot/ircrelay/irc.py b/techsupport_bot/ircrelay/irc.py index 9e0115a3d..13b6b0f29 100644 --- a/techsupport_bot/ircrelay/irc.py +++ b/techsupport_bot/ircrelay/irc.py @@ -21,13 +21,23 @@ class IRCBot(ib3.auth.SASL, irc.bot.SingleServerIRCBot): """The IRC bot class. This is the class that runs the entire IRC side of the bot The class to start the entire IRC bot - Args: - loop (asyncio.AbstractEventLoop): The running event loop for the discord API. - server (str): The string server domain/IP - port (int): The port the IRC server is running on - channels (List[str]): The list of channels to join - username (str): The username of the IRC bot account - password (str): The password of the IRC bot account + Attrs: + irc_cog (commands.relay.DiscordToIRC): The discord cog for the relay, + to allow communication between + loop (asyncio.AbstractEventLoop): The discord bots event loop + console (logging.Logger): The console to print errors to + IRC_BOLD (str): The bold character for IRC + connection (irc.client.ServerConnection): The IRC connection event + join_thread (threading.Timer): The repeating join channel request thread + ready (bool): Whether the IRC bot is ready to send messages + + Args: + loop (asyncio.AbstractEventLoop): The running event loop for the discord API. + server (str): The string server domain/IP + port (int): The port the IRC server is running on + channels (list[str]): The list of channels to join + username (str): The username of the IRC bot account + password (str): The password of the IRC bot account """ irc_cog = None @@ -169,7 +179,7 @@ def send_message_to_discord(self: Self, split_message: dict[str, str]) -> None: """Sends the given message to discord, using the discord API event loop Args: - split_message (Dict[str, str]): The formatted message to send to discord + split_message (dict[str, str]): The formatted message to send to discord """ asyncio.run_coroutine_threadsafe( self.irc_cog.send_message_from_irc(split_message=split_message), self.loop @@ -180,7 +190,7 @@ def get_irc_status(self: Self) -> dict[str, str]: Returns nicely formatted status, username, and channels Returns: - Dict[str, str]: The dictionary containing the 3 status items as strings + dict[str, str]: The dictionary containing the 3 status items as strings """ status_text = self.generate_status_string() channels = ", ".join(self.channels.keys()) diff --git a/techsupport_bot/tests/commands_tests/test_extensions_conch.py b/techsupport_bot/tests/commands_tests/test_extensions_conch.py index 8971ea310..5161187c7 100644 --- a/techsupport_bot/tests/commands_tests/test_extensions_conch.py +++ b/techsupport_bot/tests/commands_tests/test_extensions_conch.py @@ -18,7 +18,11 @@ class Test_FormatQuestion: @given(text()) def test_format_question(self: Self, question: str) -> None: """Property test to ensure the question is cropped correcty, never altered, - and always ends in a question mark""" + and always ends in a question mark + + Args: + question (str): The randomly generated question to format + """ # Step 1 - Setup env discord_env = config_for_tests.FakeDiscordEnv() diff --git a/techsupport_bot/tests/commands_tests/test_extensions_htd.py b/techsupport_bot/tests/commands_tests/test_extensions_htd.py index afee556b6..6266f575d 100644 --- a/techsupport_bot/tests/commands_tests/test_extensions_htd.py +++ b/techsupport_bot/tests/commands_tests/test_extensions_htd.py @@ -24,7 +24,7 @@ def setup_local_extension(bot: helpers.MockBot = None) -> htd.Htd: fake_discord_env in the test. Defaults to None. Returns: - HTD: The instance of the htd class + htd.Htd: The instance of the htd class """ with patch("asyncio.create_task", return_value=None): return htd.Htd(bot) diff --git a/techsupport_bot/tests/commands_tests/test_extensions_hug.py b/techsupport_bot/tests/commands_tests/test_extensions_hug.py index f8d3154a2..c1084368d 100644 --- a/techsupport_bot/tests/commands_tests/test_extensions_hug.py +++ b/techsupport_bot/tests/commands_tests/test_extensions_hug.py @@ -23,7 +23,7 @@ def setup_local_extension(bot: helpers.MockBot = None) -> hug.Hugger: fake_discord_env in the test. Defaults to None. Returns: - Hugger: The instance of the Hugger class + hug.Hugger: The instance of the Hugger class """ with patch("asyncio.create_task", return_value=None): return hug.Hugger(bot) diff --git a/techsupport_bot/tests/commands_tests/test_extensions_lenny.py b/techsupport_bot/tests/commands_tests/test_extensions_lenny.py index 8126d7035..1625220bc 100644 --- a/techsupport_bot/tests/commands_tests/test_extensions_lenny.py +++ b/techsupport_bot/tests/commands_tests/test_extensions_lenny.py @@ -21,7 +21,7 @@ def setup_local_extension(bot: helpers.MockBot = None) -> lenny.Lenny: fake_discord_env in the test. Defaults to None. Returns: - HTD: The instance of the htd class + lenny.Lenny: The instance of the htd class """ with patch("asyncio.create_task", return_value=None): return lenny.Lenny(bot) diff --git a/techsupport_bot/tests/commands_tests/test_extensions_linter.py b/techsupport_bot/tests/commands_tests/test_extensions_linter.py index b369583e5..ff8efd86e 100644 --- a/techsupport_bot/tests/commands_tests/test_extensions_linter.py +++ b/techsupport_bot/tests/commands_tests/test_extensions_linter.py @@ -24,7 +24,7 @@ def setup_local_extension(bot: helpers.MockBot = None) -> linter.Lint: fake_discord_env in the test. Defaults to None. Returns: - Lint: The instance of the Lint class + linter.Lint: The instance of the Lint class """ with patch("asyncio.create_task", return_value=None): return linter.Lint(bot) diff --git a/techsupport_bot/tests/commands_tests/test_extensions_mock.py b/techsupport_bot/tests/commands_tests/test_extensions_mock.py index 9d0afdc45..6952d5960 100644 --- a/techsupport_bot/tests/commands_tests/test_extensions_mock.py +++ b/techsupport_bot/tests/commands_tests/test_extensions_mock.py @@ -25,7 +25,7 @@ def setup_local_extension(bot: helpers.MockBot = None) -> mock.Mocker: fake_discord_env in the test. Defaults to None. Returns: - Mocker: The instance of the Mocker class + mock.Mocker: The instance of the Mocker class """ with patch("asyncio.create_task", return_value=None): return mock.Mocker(bot) @@ -181,7 +181,11 @@ def test_with_set_string(self: Self) -> None: @given(text()) def test_with_random_string(self: Self, input_message: str) -> None: - """A property test to ensure that mocked message isn't getting smaller""" + """A property test to ensure that mocked message isn't getting smaller + + Args: + input_message (str): A random message to to prepare_mock_message with + """ # Step 1 - Setup env mocker = setup_local_extension() diff --git a/techsupport_bot/tests/commands_tests/test_extensions_roll.py b/techsupport_bot/tests/commands_tests/test_extensions_roll.py index 55d986cf1..456553fa2 100644 --- a/techsupport_bot/tests/commands_tests/test_extensions_roll.py +++ b/techsupport_bot/tests/commands_tests/test_extensions_roll.py @@ -26,7 +26,7 @@ def setup_local_extension(bot: helpers.MockBot = None) -> roll.Roller: fake_discord_env in the test. Defaults to None. Returns: - Roller: The instance of the Roller class + roll.Roller: The instance of the Roller class """ with patch("asyncio.create_task", return_value=None): return roll.Roller(bot) @@ -84,7 +84,12 @@ class Test_RandomNumber: @given(integers(), integers()) def test_random_numbers(self: Self, min_value: int, max_value: int) -> None: - """A property test to ensure that random number doesn't return anything unexpected""" + """A property test to ensure that random number doesn't return anything unexpected + + Args: + min_value (int): A random int to text roll bounds with + max_value (int): Another random int to test roll bounds with + """ # Step 1 - Setup env roller = setup_local_extension() if min_value > max_value: diff --git a/techsupport_bot/tests/commands_tests/test_extensions_wyr.py b/techsupport_bot/tests/commands_tests/test_extensions_wyr.py index cbcad6786..da4117983 100644 --- a/techsupport_bot/tests/commands_tests/test_extensions_wyr.py +++ b/techsupport_bot/tests/commands_tests/test_extensions_wyr.py @@ -25,7 +25,7 @@ def setup_local_extension(bot: helpers.MockBot = None) -> wyr.WouldYouRather: fake_discord_env in the test. Defaults to None. Returns: - WouldYouRather: The instance of the WouldYouRather class + wyr.WouldYouRather: The instance of the WouldYouRather class """ with patch("asyncio.create_task", return_value=None): return wyr.WouldYouRather(bot) @@ -94,7 +94,11 @@ async def test_wyr_command_send(self: Self) -> None: class Test_Get_Question: - """A set of tests to test the get_question function""" + """A set of tests to test the get_question function + + Attrs: + sample_resource (str): A set of same questions for doing unit tests + """ sample_resource = '"q1o1" || "q1o2"\n"q2o1" || "q2o2"' diff --git a/techsupport_bot/tests/config_for_tests.py b/techsupport_bot/tests/config_for_tests.py index 920d1d77d..601dabd8e 100644 --- a/techsupport_bot/tests/config_for_tests.py +++ b/techsupport_bot/tests/config_for_tests.py @@ -45,6 +45,14 @@ def rand_history( """This is a custom strategy to generate a random message history This history, returned as an array, will be 1 to 50 messages of random content Some will be by a bot, some will not + + Args: + draw (Callable[[SearchStrategy[int, int]], int] | Callable[[SearchStrategy[str]], str]): + The strategy used to generate a random history of message. + This generates both strings and ints + + Returns: + list[MockMessage]: The randomly generated list of messages """ hist_length = draw(integers(1, 10)) final_history = [] diff --git a/techsupport_bot/tests/core_tests/test_base_auxiliary.py b/techsupport_bot/tests/core_tests/test_base_auxiliary.py index 558163563..bf1d68f1c 100644 --- a/techsupport_bot/tests/core_tests/test_base_auxiliary.py +++ b/techsupport_bot/tests/core_tests/test_base_auxiliary.py @@ -204,7 +204,11 @@ async def test_find_message_random_history( self: Self, given_history: list[helpers.MockMessage] ) -> None: """Test to ensure that given a random history, - the find message functions always works as expected""" + the find message functions always works as expected + + Args: + given_history (list[helpers.MockMessage]): The random message history + """ # Step 1 - Setup env discord_env = config_for_tests.FakeDiscordEnv() discord_env.channel.message_history = given_history @@ -229,7 +233,12 @@ class Test_GenerateBasicEmbed: @given(text(), text()) def test_generate_embed(self: Self, title: str, description: str) -> None: - """Property test to ensure that embeds are generated correctly""" + """Property test to ensure that embeds are generated correctly + + Args: + title (str): The random string to use as a title + description (str): The random string to use as a description + """ # Step 2 - Call the function embed = auxiliary.generate_basic_embed( title=title, description=description, color=discord.Color.random() diff --git a/techsupport_bot/tests/helpers/asset.py b/techsupport_bot/tests/helpers/asset.py index bc42a5e3d..c7f6b8f30 100644 --- a/techsupport_bot/tests/helpers/asset.py +++ b/techsupport_bot/tests/helpers/asset.py @@ -11,8 +11,8 @@ class MockAsset: """ This is the MockAsset class - Currently implemented variables and methods: - url -> The URL associated with the asset + Args: + url (str): The URL associated with the asset """ def __init__(self: Self, url: str = None) -> None: diff --git a/techsupport_bot/tests/helpers/attachment.py b/techsupport_bot/tests/helpers/attachment.py index 43b29b981..f066ae6da 100644 --- a/techsupport_bot/tests/helpers/attachment.py +++ b/techsupport_bot/tests/helpers/attachment.py @@ -11,8 +11,8 @@ class MockAttachment: """ This is the MockAttachment class - Currently implemented variables and methods: - filename -> The string containing the name of the file + Args: + filename (str): The string containing the name of the file """ def __init__(self: Self, filename: str = None) -> None: diff --git a/techsupport_bot/tests/helpers/bot.py b/techsupport_bot/tests/helpers/bot.py index 4ac83ad42..e8c179be3 100644 --- a/techsupport_bot/tests/helpers/bot.py +++ b/techsupport_bot/tests/helpers/bot.py @@ -14,20 +14,33 @@ class MockBot: """ This is the MockBot class - Currently implemented variables and methods: - id -> An integer containing the ID of the bot + Functions implemented: + get_prefix() -> returns a string of the bot prefix + wait_until_ready() -> always returns true - get_prefix() -> returns a string of the bot prefix - wait_until_ready() -> always returns true + Args: + input_id (int): An integer containing the ID of the bot """ def __init__(self: Self, input_id: int = None) -> None: self.id = input_id async def get_prefix(self: Self, message: helpers.MockMessage = None) -> str: - """A mock function to get the prefix of the bot""" + """A mock function to get the prefix of the bot + + Args: + message (helpers.MockMessage): The message to lookup the prefix for + based on the context (NOT CURRENTLY USED) + + Returns: + str: The prefix, currently always "." + """ return "." def wait_until_ready(self: Self) -> bool: - """A mock wait on ready function""" + """A mock wait on ready function + + Returns: + bool: Always true + """ return True diff --git a/techsupport_bot/tests/helpers/channel.py b/techsupport_bot/tests/helpers/channel.py index 6b5f140cd..50a9dc1b5 100644 --- a/techsupport_bot/tests/helpers/channel.py +++ b/techsupport_bot/tests/helpers/channel.py @@ -15,10 +15,14 @@ class MockChannel: """ This is the MockChannel class - Currently implemented variables and methods: - message_history -> A list of MockMessage objects - history() -> An async function to return history. - A "limit" object may be passed, but is ignored in this implementation + Functions implemented: + history() -> An async function to return history. + A "limit" object may be passed, but is ignored in this implementation + + Args: + history (list[helpers.MockMessage], optional): A list of MockMessage objects. + Defaults to None + """ def __init__(self: Self, history: list[helpers.MockMessage] = None) -> None: diff --git a/techsupport_bot/tests/helpers/context.py b/techsupport_bot/tests/helpers/context.py index 7e8702b11..fdc1ded5b 100644 --- a/techsupport_bot/tests/helpers/context.py +++ b/techsupport_bot/tests/helpers/context.py @@ -14,10 +14,10 @@ class MockContext: """ This is the MockContext class - Currently implemented variables and methods: - channel -> The MockChannel object for the current context - message -> The MockMessage in which the context was called with - author -> The author of the command message + Args: + channel (helpers.MockChannel): The MockChannel object for the current context + message (helpers.MockMessage): The MockMessage in which the context was called with + author (helpers.MockMember): The author of the command message """ def __init__( diff --git a/techsupport_bot/tests/helpers/member.py b/techsupport_bot/tests/helpers/member.py index 7971a8fff..eed1cab49 100644 --- a/techsupport_bot/tests/helpers/member.py +++ b/techsupport_bot/tests/helpers/member.py @@ -14,12 +14,11 @@ class MockMember: """ This is the MockMember class - Currently implemented variables and methods: - id -> An integer containing the ID of the fake user - bot -> Boolean stating if this member is a bot or not - mention -> String that is just <@ID> - name -> The string containing the users username - display_avatar -> The MockAsset object for the avatar + Args: + input_id (int): An integer containing the ID of the fake user + bot (bool): Boolean stating if this member is a bot or not + name (str): The string containing the users username + display_avatar (helpers.MockAsset): The MockAsset object for the avatar """ def __init__( diff --git a/techsupport_bot/tests/helpers/message.py b/techsupport_bot/tests/helpers/message.py index 1016bf7f0..141b415ab 100644 --- a/techsupport_bot/tests/helpers/message.py +++ b/techsupport_bot/tests/helpers/message.py @@ -14,11 +14,11 @@ class MockMessage: """ This is the MockMessage class - Currently implemented variables and methods: - content -> The string containing the content of the message - author -> The MockMember object who create the message - clean_content -> The same as content - attachments -> A list of MockAttacment objects + Args: + content (str): The string containing the content of the message + author (helpers.MockMember): The MockMember object who create the message + attachments (list[helpers.MockAttachment]): A list of MockAttachment objects + reactions (list[helpers.MockReaction]): A list of MockReaction objects """ def __init__( @@ -39,7 +39,7 @@ async def add_reaction(self: Self, reaction: list[helpers.MockReaction]) -> None Adding reactions to a previous message Args: - reaction (list): An array of reactions on the message + reaction (list[helpers.MockReaction]): An array of reactions on the message """ self.reactions.append(reaction) diff --git a/techsupport_bot/tests/helpers/reaction.py b/techsupport_bot/tests/helpers/reaction.py index c9b344e44..4240e6853 100644 --- a/techsupport_bot/tests/helpers/reaction.py +++ b/techsupport_bot/tests/helpers/reaction.py @@ -14,9 +14,9 @@ class MockReaction: """ This is the MockReaction class - Currently implemented variables and methods: - message -> Last message a user had to add reactions to - count -> Number of reactions already on the message + Args: + message (helpers.MockMessage): Last message a user had to add reactions to + count (int): Number of reactions already on the message """ def __init__( diff --git a/techsupport_bot/ui/application.py b/techsupport_bot/ui/application.py index 7931e6b0b..9ba689855 100644 --- a/techsupport_bot/ui/application.py +++ b/techsupport_bot/ui/application.py @@ -11,6 +11,10 @@ class Application(discord.ui.Modal, title="Staff interest form"): """The class contianing the modal and all variables for it This must be sent as a response to an interaction, cannot be from a prefix command + + Attrs: + background (discord.ui.TextInput): The background question for the application + reason (discord.ui.TextInput): The reason question for the application """ background = discord.ui.TextInput( diff --git a/techsupport_bot/ui/appnotice.py b/techsupport_bot/ui/appnotice.py index 1ba9ded81..02df029f4 100644 --- a/techsupport_bot/ui/appnotice.py +++ b/techsupport_bot/ui/appnotice.py @@ -8,7 +8,11 @@ class AppNotice(discord.ui.View): - """The view containing a button and message encouraging users to apply""" + """The view containing a button and message encouraging users to apply + + Attrs: + ICON (str): The Icon for the application reminder + """ ICON = "https://icon-icons.com/downloadimage.php?id=14692&root=80/PNG/256/&file=help_15418.png" diff --git a/techsupport_bot/ui/confirm.py b/techsupport_bot/ui/confirm.py index a449a2a46..6dc317368 100644 --- a/techsupport_bot/ui/confirm.py +++ b/techsupport_bot/ui/confirm.py @@ -11,9 +11,11 @@ class ConfirmResponse(Enum): """A class to define the 3 responses - CONFIRMED - The original author clicked the "confirm" button - DENIED - The original author clicked the "cancel" button - TIMEOUT - No buttons were pressed in the timeout range + + Attrs: + CONFIRMED (int): The original author clicked the "confirm" button + DENIED (int): The original author clicked the "cancel" button + TIMEOUT (int): No buttons were pressed in the timeout range """ CONFIRMED = auto() @@ -78,7 +80,12 @@ async def send( async def confirm( self: Self, interaction: discord.Interaction, button: discord.ui.Button ) -> None: - """Define what happens when the confirm button is pressed""" + """Define what happens when the confirm button is pressed + + Args: + interaction (discord.Interaction): The interaction generated when the button pressed + button (discord.ui.Button): The button object that got pressed + """ await interaction.response.defer() self.value = ConfirmResponse.CONFIRMED await self.message.delete() @@ -88,7 +95,12 @@ async def confirm( async def cancel( self: Self, interaction: discord.Interaction, button: discord.ui.Button ) -> None: - """Define what happens when the cancel button is pressed""" + """Define what happens when the cancel button is pressed + + Args: + interaction (discord.Interaction): The interaction generated when the button pressed + button (discord.ui.Button): The button object that got pressed + """ await interaction.response.defer() self.value = ConfirmResponse.DENIED await self.message.delete() @@ -101,6 +113,13 @@ async def on_timeout(self: Self) -> None: async def interaction_check(self: Self, interaction: discord.Interaction) -> bool: """This checks to ensure that only the original author can press the button If the original author didn't press, it sends an ephemeral message + + Args: + interaction (discord.Interaction): The interaction generated when this + UI is interacted with in any way + + Returns: + bool: If this is False the interaction should NOT be followed through with """ if interaction.user != self.author: await interaction.response.send_message( diff --git a/techsupport_bot/ui/pagination.py b/techsupport_bot/ui/pagination.py index 883df6971..681cf0fd8 100644 --- a/techsupport_bot/ui/pagination.py +++ b/techsupport_bot/ui/pagination.py @@ -13,6 +13,12 @@ class PaginateView(discord.ui.View): This holds all the buttons and how the pages should work. To use this, call the send function. Everything else is automatic + + Attrs: + current_page (int): The current page number the user is on + data (list[str | discord.Embed]): The list of data for the pages + timeout (int): The timeout till the buttons dissapear without interaction + message (discord.Message): The message that has the pagination """ current_page: int = 1 @@ -38,7 +44,7 @@ async def send( Args: channel (discord.abc.Messageable): The channel to send the pages to author (discord.Member): The author of the pages command - data (list[Union[str, discord.Embed]]): A list of pages in order + data (list[str | discord.Embed]): A list of pages in order with [0] being the first page interaction (discord.Interaction | None): The interaction this should followup with (Optional) @@ -88,7 +94,11 @@ def update_buttons(self: Self) -> None: async def prev_button( self: Self, interaction: discord.Interaction, _: discord.ui.Button ) -> None: - """This declares the previous button, and what should happen when it's pressed""" + """This declares the previous button, and what should happen when it's pressed + + Args: + interaction (discord.Interaction): The interaction generated when the button pressed + """ await interaction.response.defer() self.current_page -= 1 await self.update_message() @@ -97,7 +107,11 @@ async def prev_button( async def next_button( self: Self, interaction: discord.Interaction, _: discord.ui.Button ) -> None: - """This declares the next button, and what should happen when it's pressed""" + """This declares the next button, and what should happen when it's pressed + + Args: + interaction (discord.Interaction): The interaction generated when the button pressed + """ await interaction.response.defer() self.current_page += 1 await self.update_message() @@ -106,7 +120,11 @@ async def next_button( async def stop_button( self: Self, interaction: discord.Interaction, _: discord.ui.Button ) -> None: - """This declares the stop button, and what should happen when it's pressed""" + """This declares the stop button, and what should happen when it's pressed + + Args: + interaction (discord.Interaction): The interaction generated when the button pressed + """ await interaction.response.defer() self.clear_items() self.stop() @@ -116,7 +134,11 @@ async def stop_button( async def trash_button( self: Self, interaction: discord.Interaction, _: discord.ui.Button ) -> None: - """This declares the trash button, and what should happen when it's pressed""" + """This declares the trash button, and what should happen when it's pressed + + Args: + interaction (discord.Interaction): The interaction generated when the button pressed + """ await interaction.response.defer() self.stop() await self.message.delete() @@ -124,6 +146,14 @@ async def trash_button( async def interaction_check(self: Self, interaction: discord.Interaction) -> bool: """This checks to ensure that only the original author can press the button If the original author didn't press, it sends an ephemeral message + + Args: + interaction (discord.Interaction): The interaction generated when this + UI is interacted with in any way + + Returns: + bool: If this is False the interaction should NOT be followed through with + """ if interaction.user != self.author: await interaction.response.send_message(