diff --git a/didier/cogs/currency.py b/didier/cogs/currency.py index 709a461..f7204a8 100644 --- a/didier/cogs/currency.py +++ b/didier/cogs/currency.py @@ -17,7 +17,7 @@ from didier.utils.types.string import pluralize class Currency(commands.Cog): - """Everything Dinks-related.""" + """Everything Dinks-related""" client: Didier @@ -25,7 +25,7 @@ class Currency(commands.Cog): super().__init__() self.client = client - @commands.command(name="award") + @commands.command(name="Award") @commands.check(is_owner) async def award( self, @@ -33,18 +33,18 @@ class Currency(commands.Cog): user: discord.User, amount: typing.Annotated[int, abbreviated_number], ): - """Award a user `amount` Didier Dinks.""" + """Award a user a given amount of Didier Dinks""" async with self.client.postgres_session as session: await crud.add_dinks(session, user.id, amount) plural = pluralize("Didier Dink", amount) await ctx.reply( - f"**{ctx.author.display_name}** has awarded **{user.display_name}** **{amount}** {plural}.", + f"**{ctx.author.display_name}** heeft **{user.display_name}** **{amount}** {plural} geschonken.", mention_author=False, ) - @commands.group(name="bank", aliases=["b"], case_insensitive=True, invoke_without_command=True) + @commands.group(name="bank", aliases=["B"], case_insensitive=True, invoke_without_command=True) async def bank(self, ctx: commands.Context): - """Show your Didier Bank information.""" + """Show your Didier Bank information""" async with self.client.postgres_session as session: bank = await crud.get_bank(session, ctx.author.id) @@ -57,9 +57,9 @@ class Currency(commands.Cog): await ctx.reply(embed=embed, mention_author=False) - @bank.group(name="upgrade", aliases=["u", "upgrades"], case_insensitive=True, invoke_without_command=True) + @bank.group(name="Upgrade", aliases=["U", "Upgrades"], case_insensitive=True, invoke_without_command=True) async def bank_upgrades(self, ctx: commands.Context): - """List the upgrades you can buy & their prices.""" + """List the upgrades you can buy & their prices""" async with self.client.postgres_session as session: bank = await crud.get_bank(session, ctx.author.id) @@ -77,9 +77,9 @@ class Currency(commands.Cog): await ctx.reply(embed=embed, mention_author=False) - @bank_upgrades.command(name="capacity", aliases=["c"]) + @bank_upgrades.command(name="Capacity", aliases=["C"]) async def bank_upgrade_capacity(self, ctx: commands.Context): - """Upgrade the capacity level of your bank.""" + """Upgrade the capacity level of your bank""" async with self.client.postgres_session as session: try: await crud.upgrade_capacity(session, ctx.author.id) @@ -88,9 +88,9 @@ class Currency(commands.Cog): await ctx.reply("You don't have enough Didier Dinks to do this.", mention_author=False) await self.client.reject_message(ctx.message) - @bank_upgrades.command(name="interest", aliases=["i"]) + @bank_upgrades.command(name="Interest", aliases=["I"]) async def bank_upgrade_interest(self, ctx: commands.Context): - """Upgrade the interest level of your bank.""" + """Upgrade the interest level of your bank""" async with self.client.postgres_session as session: try: await crud.upgrade_interest(session, ctx.author.id) @@ -99,9 +99,9 @@ class Currency(commands.Cog): await ctx.reply("You don't have enough Didier Dinks to do this.", mention_author=False) await self.client.reject_message(ctx.message) - @bank_upgrades.command(name="rob", aliases=["r"]) + @bank_upgrades.command(name="Rob", aliases=["R"]) async def bank_upgrade_rob(self, ctx: commands.Context): - """Upgrade the rob level of your bank.""" + """Upgrade the rob level of your bank""" async with self.client.postgres_session as session: try: await crud.upgrade_rob(session, ctx.author.id) @@ -112,27 +112,15 @@ class Currency(commands.Cog): @commands.hybrid_command(name="dinks") async def dinks(self, ctx: commands.Context): - """Check your Didier Dinks.""" + """Check your Didier Dinks""" async with self.client.postgres_session as session: bank = await crud.get_bank(session, ctx.author.id) plural = pluralize("Didier Dink", bank.dinks) await ctx.reply(f"**{ctx.author.display_name}** has **{bank.dinks}** {plural}.", mention_author=False) - @commands.command(name="invest", aliases=["deposit", "dep"]) + @commands.command(name="Invest", aliases=["Deposit", "Dep"]) async def invest(self, ctx: commands.Context, amount: typing.Annotated[typing.Union[str, int], abbreviated_number]): - """Invest `amount` Didier Dinks into your bank. - - The `amount`-argument can take both raw numbers, and abbreviations of big numbers. Additionally, passing - `all` as the value will invest all of your Didier Dinks. - - Example usage: - ``` - didier invest all - didier invest 500 - didier invest 25k - didier invest 5.3b - ``` - """ + """Invest a given amount of Didier Dinks""" async with self.client.postgres_session as session: invested = await crud.invest(session, ctx.author.id, amount) plural = pluralize("Didier Dink", invested) @@ -146,7 +134,7 @@ class Currency(commands.Cog): @commands.hybrid_command(name="nightly") async def nightly(self, ctx: commands.Context): - """Claim nightly Didier Dinks.""" + """Claim nightly Didier Dinks""" async with self.client.postgres_session as session: try: await crud.claim_nightly(session, ctx.author.id) diff --git a/didier/cogs/discord.py b/didier/cogs/discord.py index c13c782..d90752a 100644 --- a/didier/cogs/discord.py +++ b/didier/cogs/discord.py @@ -22,7 +22,7 @@ from didier.views.modals import CreateBookmark class Discord(commands.Cog): - """Commands related to Discord itself, which work with resources like servers and members.""" + """Cog for commands related to Discord, servers, and members""" client: Didier @@ -43,57 +43,43 @@ class Discord(commands.Cog): self.client.tree.remove_command(self._bookmark_ctx_menu.name, type=self._bookmark_ctx_menu.type) self.client.tree.remove_command(self._pin_ctx_menu.name, type=self._pin_ctx_menu.type) - @commands.group(name="birthday", aliases=["bd", "birthdays"], case_insensitive=True, invoke_without_command=True) + @commands.group(name="Birthday", aliases=["Bd", "Birthdays"], case_insensitive=True, invoke_without_command=True) async def birthday(self, ctx: commands.Context, user: discord.User = None): - """Command to check the birthday of `user`. - - Not passing an argument for `user` will show yours instead. - """ + """Command to check the birthday of a user""" user_id = (user and user.id) or ctx.author.id async with self.client.postgres_session as session: birthday = await birthdays.get_birthday_for_user(session, user_id) - name: Optional[str] = f"{user.display_name}'s" if user is not None else None + name = "Your" if user is None else f"{user.display_name}'s" if birthday is None: - return await ctx.reply(f"I don't know {name or 'your'} birthday.", mention_author=False) + return await ctx.reply(f"I don't know {name} birthday.", mention_author=False) day, month = leading("0", str(birthday.birthday.day)), leading("0", str(birthday.birthday.month)) - return await ctx.reply(f"{name or 'Your'} birthday is set to **{day}/{month}**.", mention_author=False) - @birthday.command(name="set", aliases=["config"]) - async def birthday_set(self, ctx: commands.Context, day: str): - """Set your birthday to `day`. + return await ctx.reply(f"{name} birthday is set to **{day}/{month}**.", mention_author=False) - Parsing of the `day`-argument happens in the following order: `DD/MM/YYYY`, `DD/MM/YY`, `DD/MM`. - Other formats will not be accepted. - """ + @birthday.command(name="Set", aliases=["Config"]) + async def birthday_set(self, ctx: commands.Context, date_str: str): + """Command to set your birthday""" try: default_year = 2001 - date = str_to_date(day, formats=["%d/%m/%Y", "%d/%m/%y", "%d/%m"]) + date = str_to_date(date_str, formats=["%d/%m/%Y", "%d/%m/%y", "%d/%m"]) # If no year was passed, make it 2001 by default - if day.count("/") == 1: + if date_str.count("/") == 1: date.replace(year=default_year) except ValueError: - return await ctx.reply(f"`{day}` is not a valid date.", mention_author=False) + return await ctx.reply(f"`{date_str}` is not a valid date.", mention_author=False) async with self.client.postgres_session as session: await birthdays.add_birthday(session, ctx.author.id, date) await self.client.confirm_message(ctx.message) - @commands.group(name="bookmark", aliases=["bm", "bookmarks"], case_insensitive=True, invoke_without_command=True) + @commands.group(name="Bookmark", aliases=["Bm", "Bookmarks"], case_insensitive=True, invoke_without_command=True) async def bookmark(self, ctx: commands.Context, *, label: Optional[str] = None): - """Post the message bookmarked with `label`. - - The `label`-argument can contain spaces and does not require quotes around it. For example: - ``` - didier bookmark some label with multiple words - ``` - - When no value for `label` is provided, this is a shortcut to `bookmark search`. - """ + """Post a bookmarked message""" # No label: shortcut to display bookmarks if label is None: return await self.bookmark_search(ctx, query=None) @@ -106,16 +92,9 @@ class Discord(commands.Cog): ) await ctx.reply(result.jump_url, mention_author=False) - @bookmark.command(name="create", aliases=["new"]) + @bookmark.command(name="Create", aliases=["New"]) async def bookmark_create(self, ctx: commands.Context, label: str, message: Optional[discord.Message]): - """Create a new bookmark for message `message` with label `label`. - - Instead of the link to a message, you can also reply to the message you wish to bookmark. In this case, - the `message`-argument can be left out. - - `label` can not be names (or aliases) of subcommands. However, names with spaces are allowed. If you wish - to use a name with spaces, it must be wrapped in "quotes". - """ + """Create a new bookmark""" # If no message was passed, allow replying to the message that should be bookmarked if message is None and ctx.message.reference is not None: message = await self.client.resolve_message(ctx.message.reference) @@ -137,12 +116,9 @@ class Discord(commands.Cog): # Label isn't allowed return await ctx.reply(f"Bookmarks cannot be named `{label}`.", mention_author=False) - @bookmark.command(name="delete", aliases=["rm"]) + @bookmark.command(name="Delete", aliases=["Rm"]) async def bookmark_delete(self, ctx: commands.Context, bookmark_id: str): - """Delete the bookmark with id `bookmark_id`. - - You can only delete your own bookmarks. - """ + """Delete a bookmark by its id""" # The bookmarks are displayed with a hashtag in front of the id # so strip it out in case people want to try and use this bookmark_id = bookmark_id.removeprefix("#") @@ -162,18 +138,9 @@ class Discord(commands.Cog): return await ctx.reply(f"Successfully deleted bookmark `#{bookmark_id_int}`.", mention_author=False) - @bookmark.command(name="search", aliases=["list", "ls"]) + @bookmark.command(name="Search", aliases=["List", "Ls"]) async def bookmark_search(self, ctx: commands.Context, *, query: Optional[str] = None): - """Search through the list of bookmarks. - - If a value for `query` was provided, results will be filtered down to only labels that include `query`. - Otherwise, all bookmarks are displayed. - - The `query`-argument can contain spaces and does not require quotes around it. For example: - ``` - didier bookmark search some query with multiple words - ``` - """ + """Search through the list of bookmarks""" async with self.client.postgres_session as session: results = await bookmarks.get_bookmarks(session, ctx.author.id, query=query) @@ -193,25 +160,15 @@ class Discord(commands.Cog): modal = CreateBookmark(self.client, message.jump_url) await interaction.response.send_modal(modal) - @commands.command(name="join") + @commands.command(name="Join", usage="[Thread]") async def join(self, ctx: commands.Context, thread: discord.Thread): - """Make Didier join `thread`. - - This command should generally not be necessary, as Didier automatically joins threads. However, it's possible - that Didier is offline at the moment of a thread being created. - - Alternatively, you can also `@mention` Didier to pull him into the thread instead. - """ + """Make Didier join a thread""" if thread.me is not None: return await ctx.reply() - @commands.command(name="pin") + @commands.command(name="Pin", usage="[Message]") async def pin(self, ctx: commands.Context, message: Optional[discord.Message] = None): - """Pin `message` in the current channel. - - Instead of the link to a message, you can also reply to the message you wish to pin. In this case, - the `message`-argument can be left out. - """ + """Pin a message in the current channel""" # If no message was passed, allow replying to the message that should be pinned if message is None and ctx.message.reference is not None: message = await self.client.resolve_message(ctx.message.reference) diff --git a/didier/cogs/fun.py b/didier/cogs/fun.py index f320ac4..d604b36 100644 --- a/didier/cogs/fun.py +++ b/didier/cogs/fun.py @@ -18,7 +18,7 @@ class Fun(commands.Cog): client: Didier # Slash groups - memes_slash = app_commands.Group(name="meme", description="Commands to generate memes.", guild_only=False) + memes_slash = app_commands.Group(name="meme", description="Commands to generate memes", guild_only=False) def __init__(self, client: Didier): self.client = client @@ -31,62 +31,57 @@ class Fun(commands.Cog): @commands.hybrid_command( name="dadjoke", - aliases=["dad", "dj"], + aliases=["Dad", "Dj"], + description="Why does Yoda's code always crash? Because there is no try.", ) async def dad_joke(self, ctx: commands.Context): - """Why does Yoda's code always crash? Because there is no try.""" + """Get a random dad joke""" async with self.client.postgres_session as session: joke = await get_random_dad_joke(session) return await ctx.reply(joke.joke, mention_author=False) - @commands.group(name="memegen", aliases=["meme", "memes"], invoke_without_command=True, case_insensitive=True) - async def memegen_msg(self, ctx: commands.Context, template: str, *, fields: str): - """Generate a meme with template `template` and fields `fields`. - - The arguments are parsed based on spaces. Arguments that contain spaces should be wrapped in "quotes". - - Example: `memegen a b c d` will be parsed as `template: "a"`, `fields: ["b", "c", "d"]` - - Example: `memegen "a b" "c d"` will be parsed as `template: "a b"`, `fields: ["c d"]` - """ + @commands.group(name="Memegen", aliases=["Meme", "Memes"], invoke_without_command=True, case_insensitive=True) + async def memegen_msg(self, ctx: commands.Context, meme_name: str, *, fields: str): + """Command group for meme-related commands""" async with ctx.typing(): - meme = await self._do_generate_meme(template, shlex.split(fields)) + meme = await self._do_generate_meme(meme_name, shlex.split(fields)) return await ctx.reply(meme, mention_author=False) - @memegen_msg.command(name="preview", aliases=["p"]) - async def memegen_preview_msg(self, ctx: commands.Context, template: str): - """Generate a preview for the meme template `template`, to see how the fields are structured.""" + @memegen_msg.command(name="Preview", aliases=["P"]) + async def memegen_preview_msg(self, ctx: commands.Context, meme_name: str): + """Generate a preview for a meme, to see how the fields are structured""" async with ctx.typing(): fields = [f"Field #{i + 1}" for i in range(20)] - meme = await self._do_generate_meme(template, fields) + meme = await self._do_generate_meme(meme_name, fields) return await ctx.reply(meme, mention_author=False) - @memes_slash.command(name="generate") - async def memegen_slash(self, interaction: discord.Interaction, template: str): - """Generate a meme with template `template`.""" + @memes_slash.command(name="generate", description="Generate a meme") + async def memegen_slash(self, interaction: discord.Interaction, meme: str): + """Slash command to generate a meme""" async with self.client.postgres_session as session: - result = expect(await get_meme_by_name(session, template), entity_type="meme", argument=template) + result = expect(await get_meme_by_name(session, meme), entity_type="meme", argument=meme) modal = GenerateMeme(self.client, result) await interaction.response.send_modal(modal) - @memes_slash.command(name="preview") - @app_commands.describe(template="The meme template to use in the preview.") - async def memegen_preview_slash(self, interaction: discord.Interaction, template: str): - """Generate a preview for a meme, to see how the fields are structured.""" + @memes_slash.command( + name="preview", description="Generate a preview for a meme, to see how the fields are structured" + ) + async def memegen_preview_slash(self, interaction: discord.Interaction, meme: str): + """Slash command to generate a meme preview""" await interaction.response.defer() fields = [f"Field #{i + 1}" for i in range(20)] - meme_url = await self._do_generate_meme(template, fields) + meme_url = await self._do_generate_meme(meme, fields) await interaction.followup.send(meme_url, ephemeral=True) - @memegen_slash.autocomplete("template") - @memegen_preview_slash.autocomplete("template") - async def _memegen_slash_autocomplete_template( + @memegen_slash.autocomplete("meme") + @memegen_preview_slash.autocomplete("meme") + async def _memegen_slash_autocomplete_meme( self, _: discord.Interaction, current: str ) -> list[app_commands.Choice[str]]: - """Autocompletion for the 'template'-parameter""" + """Autocompletion for the 'meme'-parameter""" return self.client.database_caches.memes.get_autocomplete_suggestions(current) diff --git a/didier/cogs/help.py b/didier/cogs/help.py index c6d88d6..73c66e5 100644 --- a/didier/cogs/help.py +++ b/didier/cogs/help.py @@ -1,267 +1,44 @@ -import inspect -import re -from typing import Mapping, Optional, Type +from typing import List, Mapping, Optional import discord from discord.ext import commands from overrides import overrides from didier import Didier -from didier.utils.discord.colours import error_red -from didier.utils.discord.flags import PosixFlags -from didier.utils.types.string import re_find_all, re_replace_with_list class CustomHelpCommand(commands.MinimalHelpCommand): - """Customised Help command that overrides the default implementation + """Customised Help command to override the default implementation - The default is ugly as hell, so we do some fiddling with it and put everything - in fancy embeds + The default is ugly as hell, so we do some fiddling with it """ - @overrides - async def command_callback(self, ctx: commands.Context, /, *, command: Optional[str] = None): - """Slightly modify the original command_callback to better suit my needs""" - - # No argument provided: send a list of all cogs - if command is None: - mapping = self.get_bot_mapping() - return await self.send_bot_help(mapping) - - command = command.lower() - - # Hide cogs the user is not allowed to see - cogs = list(ctx.bot.cogs.values()) - cogs = await self._filter_cogs(cogs) - # Allow fetching cogs case-insensitively - cog = self._get_cog(cogs, command) - if cog is not None: - return await self.send_cog_help(cog) - - # Traverse tree of commands - keys = command.split(" ") - current_command = ctx.bot.all_commands.get(keys[0]) - - # No command found - if current_command is None: - return await self.send_error_message(self.command_not_found(keys[0])) - - # Look for subcommands - for key in keys[1:]: - try: - found = current_command.all_commands.get(key) # type: ignore - except AttributeError: - return await self.send_error_message(self.subcommand_not_found(current_command, key)) - else: - if found is None: - return await self.send_error_message(self.subcommand_not_found(current_command, key)) - - current_command = found - - if isinstance(current_command, commands.Group): - return await self.send_group_help(current_command) - else: - return await self.send_command_help(current_command) - - @overrides - def command_not_found(self, string: str, /) -> str: - return f"Found no command named `{string}`." - - @overrides - def get_command_signature(self, command: commands.Command, /) -> str: - signature_list = [command.name] - - # Perform renaming for hybrid commands - if hasattr(command.callback, "__discord_app_commands_param_rename__"): - renames = command.callback.__discord_app_commands_param_rename__ - else: - renames = {} - - sig = command.params - - for name, param in sig.items(): - name = renames.get(name, name) - is_optional = param.default is not param.empty - - # Wrap optional arguments in square brackets - if is_optional: - name = f"[{name}]" - - # If there are options/flags, add them - # The hardcoded name-check is done for performance reasons - if name == "flags" and inspect.isclass(param.annotation) and issubclass(param.annotation, PosixFlags): - signature_list.append("[--OPTIONS]") - else: - signature_list.append(name) - - return " ".join(signature_list) - - @overrides - async def send_bot_help(self, mapping: Mapping[Optional[commands.Cog], list[commands.Command]], /): - embed = self._help_embed_base("Categories") - filtered_cogs = await self._filter_cogs(list(mapping.keys())) - embed.description = "\n".join(list(map(lambda cog: cog.qualified_name, filtered_cogs))) - await self.context.reply(embed=embed, mention_author=False) - - @overrides - async def send_cog_help(self, cog: commands.Cog, /): - embed = self._help_embed_base(cog.qualified_name) - embed.description = cog.description - - commands_names = list(map(lambda c: c.qualified_name, cog.get_commands())) - commands_names.sort() - - embed.add_field(name="Commands", value=", ".join(commands_names), inline=False) - - return await self.context.reply(embed=embed, mention_author=False) - - @overrides - async def send_command_help(self, command: commands.Command, /): - embed = self._help_embed_base(command.qualified_name) - self._add_command_help(embed, command) - - return await self.context.reply(embed=embed, mention_author=False) - - @overrides - async def send_group_help(self, group: commands.Group, /): - embed = self._help_embed_base(group.qualified_name) - - if group.invoke_without_command: - self._add_command_help(embed, group) - - subcommand_names = list(map(lambda c: c.name, group.commands)) - subcommand_names.sort() - - embed.add_field(name="Subcommands", value=", ".join(subcommand_names)) - - return await self.context.reply(embed=embed, mention_author=False) - - @overrides - async def send_error_message(self, error: str, /): - embed = discord.Embed(colour=error_red(), title="Help", description=error) - return await self.context.reply(embed=embed, mention_author=False) - - @overrides - def subcommand_not_found(self, command: commands.Command, string: str, /) -> str: - return f"Found no subcommand named `{string}` for command `{command.qualified_name}`." - def _help_embed_base(self, title: str) -> discord.Embed: """Create the base structure for the embeds that get sent with the Help commands""" - embed = discord.Embed(title=title.title(), colour=discord.Colour.blue()) + embed = discord.Embed(title=title, colour=discord.Colour.blue()) + embed.set_footer(text="Syntax: Didier Help [Categorie] of Didier Help [Commando]") return embed - def _clean_command_doc(self, command: commands.Command) -> str: - """Clean up a help docstring - - This will strip out single newlines, because these are only there for readability and line length. - These are instead replaced with spaces. - - Code in codeblocks is ignored, as it is used to create examples. - """ - description = command.help - codeblocks = re_find_all(r"\n?```.*?```", description, flags=re.DOTALL) - - # Regex borrowed from https://stackoverflow.com/a/59843498/13568999 - description = re.sub( - r"([^\S\n]*\n(?:[^\S\n]*\n)+[^\S\n]*)|[^\S\n]*\n[^\S\n]*", lambda x: x.group(1) or " ", description - ) - - # Replace codeblocks with their original form - if codeblocks: - description = re_replace_with_list(r"```.*?```", description, codeblocks) - - # Add flag help in - flags_class = self._get_flags_class(command) - if flags_class is not None: - description += f"\n\n{self.get_flags_help(flags_class)}" - - return description - - def _add_command_help(self, embed: discord.Embed, command: commands.Command): - """Add command-related information to an embed - - This allows re-using this logic for Group commands that can be invoked by themselves. - """ - embed.description = self._clean_command_doc(command) - - signature = self.get_command_signature(command) - embed.add_field(name="Signature", value=signature, inline=False) - - if command.aliases: - embed.add_field(name="Aliases", value=", ".join(command.aliases), inline=False) - - def _get_cog(self, cogs: list[commands.Cog], name: str) -> Optional[commands.Cog]: - """Try to find a cog, case-insensitively""" - for cog in cogs: - if cog.qualified_name.lower() == name: - return cog - - return None - - async def _filter_cogs(self, cogs: list[commands.Cog]) -> list[commands.Cog]: + async def _filter_cogs(self, cogs: List[commands.Cog]) -> List[commands.Cog]: """Filter the list of cogs down to all those that the user can see""" + # Remove cogs that we never want to see in the help page because they + # don't contain commands + filtered_cogs = list(filter(lambda cog: cog is not None and cog.qualified_name.lower() not in ("tasks",), cogs)) - async def _predicate(cog: Optional[commands.Cog]) -> bool: - if cog is None: - return False + # Remove owner-only cogs + if not await self.context.bot.is_owner(self.context.author): + filtered_cogs = list(filter(lambda cog: cog.qualified_name.lower() not in ("owner",), filtered_cogs)) - # Remove cogs that we never want to see in the help page because they - # don't contain commands, or shouldn't be visible at all - if not cog.get_commands(): - return False - - if cog.qualified_name.lower() in ("tasks", "debugcog"): - return False - - # Hide owner-only cogs if you're not the owner - if not await self.context.bot.is_owner(self.context.author): - return cog.qualified_name.lower() not in ("owner",) - - return True - - # Filter list of cogs down - filtered_cogs = [cog for cog in cogs if await _predicate(cog)] return list(sorted(filtered_cogs, key=lambda cog: cog.qualified_name)) - def _get_flags_class(self, command: commands.Command) -> Optional[Type[PosixFlags]]: - """Check if a command has flags""" - flag_param = command.params.get("flags", None) - if flag_param is None: - return None - - if issubclass(flag_param.annotation, PosixFlags): - return flag_param.annotation - - return None - - def get_flags_help(self, flags_class: Type[PosixFlags]) -> str: - """Get the description for flag arguments""" - help_data = [] - - # Present flags in alphabetical order, as dicts have no set ordering - flag_mapping = flags_class.__commands_flags__ - flags = list(flag_mapping.items()) - flags.sort(key=lambda f: f[0]) - - for name, flag in flags: - flag_names = [name, *flag.aliases] - # Add the --prefix in front of all flags - flag_names = list(map(lambda n: f"--{n}", flag_names)) - help_data.append(f"{', '.join(flag_names)} [default `{flag.default}`]") - - return "Options:\n" + "\n".join(help_data) + @overrides + async def send_bot_help(self, mapping: Mapping[Optional[commands.Cog], List[commands.Command]], /): + embed = self._help_embed_base("Categorieën") + filtered_cogs = await self._filter_cogs(list(mapping.keys())) + embed.description = "\n".join(list(map(lambda cog: cog.qualified_name, filtered_cogs))) + await self.get_destination().send(embed=embed) async def setup(client: Didier): """Load the cog""" - help_str = ( - "Shows the help page for a category or command. " - "`/commands` are not included, as they already have built-in descriptions in the UI." - "\n\nThe command signatures follow the POSIX-standard format for help messages:" - "\n- `required_positional_argument`" - "\n- `[optional_positional_argument]`" - ) - - attributes = {"aliases": ["h", "man"], "usage": "[category or command]", "help": help_str} - - client.help_command = CustomHelpCommand(command_attrs=attributes) + client.help_command = CustomHelpCommand() diff --git a/didier/cogs/meta.py b/didier/cogs/meta.py index 7b7a732..69a3805 100644 --- a/didier/cogs/meta.py +++ b/didier/cogs/meta.py @@ -8,31 +8,21 @@ from didier import Didier class Meta(commands.Cog): - """Commands related to Didier himself.""" + """Cog for Didier-related commands""" client: Didier def __init__(self, client: Didier): self.client = client - @commands.command(name="marco") + @commands.command(name="Marco") async def marco(self, ctx: commands.Context): - """Get Didier's latency.""" + """Ping command to get the delay of the bot""" return await ctx.reply(f"Polo! {round(self.client.latency * 1000)}ms", mention_author=False) - @commands.command(name="source", aliases=["src"]) + @commands.command(name="Source", aliases=["Src"]) async def source(self, ctx: commands.Context, *, command_name: Optional[str] = None): - """Get a link to the source code of Didier. - - If a value for `command_name` is passed, the source for `command_name` is shown instead. - - Example usage: - ``` - didier source - didier source dinks - didier source source - ``` - """ + """Command to get links to the source code of Didier""" repo_home = "https://github.com/stijndcl/didier" if command_name is None: @@ -48,12 +38,12 @@ class Meta(commands.Cog): filename = src.co_filename if command is None: - return await ctx.reply(f"Found no command named `{command_name}`.", mention_author=False) + return await ctx.reply(f"Geen commando gevonden voor ``{command_name}``.", mention_author=False) lines, first_line = inspect.getsourcelines(src) if filename is None: - return await ctx.reply(f"Found no source file for `{command_name}`.", mention_author=False) + return await ctx.reply(f"Geen code gevonden voor ``{command_name}``.", mention_author=False) file_location = os.path.relpath(filename).replace("\\", "/") diff --git a/didier/cogs/other.py b/didier/cogs/other.py index 06ec2a4..dea9cd2 100644 --- a/didier/cogs/other.py +++ b/didier/cogs/other.py @@ -13,18 +13,16 @@ from didier.data.scrapers import google class Other(commands.Cog): - """Commands that don't really belong anywhere else.""" + """Cog for commands that don't really belong anywhere else""" client: Didier def __init__(self, client: Didier): self.client = client - @commands.hybrid_command( - name="define", aliases=["ud", "urban"], description="Look up the definition of a word on the Urban Dictionary" - ) + @commands.hybrid_command(name="define", description="Urban Dictionary", aliases=["Ud", "Urban"], usage="[Term]") async def define(self, ctx: commands.Context, *, query: str): - """Look up the definition of `query` on the Urban Dictionary.""" + """Look up the definition of a word on the Urban Dictionary""" async with ctx.typing(): status_code, definitions = await urban_dictionary.lookup(self.client.http_session, query) if not definitions: @@ -32,17 +30,10 @@ class Other(commands.Cog): await ctx.reply(embed=definitions[0].to_embed(), mention_author=False) - @commands.hybrid_command(name="google", description="Google search") + @commands.hybrid_command(name="google", description="Google search", usage="[Query]") @app_commands.describe(query="Search query") async def google(self, ctx: commands.Context, *, query: str): - """Show the Google search results for `query`. - - The `query`-argument can contain spaces and does not require quotes around it. For example: - ``` - didier query didier source github - didier query "didier source github" - ``` - """ + """Google something""" async with ctx.typing(): results = await google.google_search(self.client.http_session, query) embed = GoogleSearch(results).to_embed() @@ -52,9 +43,9 @@ class Other(commands.Cog): async with self.client.postgres_session as session: return await get_link_by_name(session, name.lower()) - @commands.command(name="Link", aliases=["Links"]) + @commands.command(name="Link", aliases=["Links"], usage="[Name]") async def link_msg(self, ctx: commands.Context, name: str): - """Get the link to the resource named `name`.""" + """Message command to get the link to something""" link = await self._get_link(name) if link is None: return await ctx.reply(f"Found no links matching `{name}`.", mention_author=False) @@ -62,10 +53,10 @@ class Other(commands.Cog): target_message = await self.client.get_reply_target(ctx) await target_message.reply(link.url, mention_author=False) - @app_commands.command(name="link") - @app_commands.describe(name="The name of the resource") + @app_commands.command(name="link", description="Get the link to something") + @app_commands.describe(name="The name of the link") async def link_slash(self, interaction: discord.Interaction, name: str): - """Get the link to something.""" + """Slash command to get the link to something""" link = await self._get_link(name) if link is None: return await interaction.response.send_message(f"Found no links matching `{name}`.", ephemeral=True) diff --git a/didier/cogs/school.py b/didier/cogs/school.py index 877cc3f..6b2d835 100644 --- a/didier/cogs/school.py +++ b/didier/cogs/school.py @@ -27,26 +27,21 @@ class School(commands.Cog): def __init__(self, client: Didier): self.client = client - @commands.hybrid_command(name="deadlines") + @commands.hybrid_command(name="deadlines", description="Show upcoming deadlines") async def deadlines(self, ctx: commands.Context): - """Show upcoming deadlines.""" + """Show upcoming deadlines""" async with self.client.postgres_session as session: deadlines = await get_deadlines(session) embed = Deadlines(deadlines).to_embed() await ctx.reply(embed=embed, mention_author=False, ephemeral=False) - @commands.hybrid_command(name="les", aliases=["sched", "schedule"]) + @commands.hybrid_command( + name="les", description="Show your personalized schedule for a given day.", aliases=["Sched", "Schedule"] + ) @app_commands.rename(day_dt="date") async def les(self, ctx: commands.Context, day_dt: Optional[app_commands.Transform[date, DateTransformer]] = None): - """Show your personalized schedule for a given day. - - If no day is provided, this defaults to the schedule for the current day. When invoked during a weekend, - it will skip forward to the next weekday instead. - - Schedules are personalized based on your roles in the server. If your schedule doesn't look right, make sure - that you've got the correct roles selected. In case you do, ping D STIJN. - """ + """Show your personalized schedule for a given day.""" if day_dt is None: day_dt = date.today() @@ -67,14 +62,14 @@ class School(commands.Cog): @commands.hybrid_command( name="menu", - aliases=["eten", "food"], + description="Show the menu in the Ghent University restaurants.", + aliases=["Eten", "Food"], ) @app_commands.rename(day_dt="date") async def menu(self, ctx: commands.Context, day_dt: Optional[app_commands.Transform[date, DateTransformer]] = None): - """Show the menu in the Ghent University restaurants on `date`. + """Show the menu in the Ghent University restaurants. - If no value for `date` is provided, this defaults to the schedule for the current day. - Menus are shown in Dutch by default, as a lot of dishes have very weird translations. + Menus are Dutch, as a lot of dishes have very weird translations """ if day_dt is None: day_dt = date.today() @@ -88,22 +83,11 @@ class School(commands.Cog): await ctx.reply(embed=embed, mention_author=False) @commands.hybrid_command( - name="fiche", description="Sends the link to study guides", aliases=["guide", "studiefiche"] + name="fiche", description="Sends the link to the study guide for [Course]", aliases=["guide", "studiefiche"] ) @app_commands.describe(course="The name of the course to fetch the study guide for (aliases work too)") async def study_guide(self, ctx: commands.Context, course: str, *, flags: StudyGuideFlags): - """Sends the link to the study guide for `course`. - - The value for `course` can contain spaces, but must be wrapped in "quotes". - - Aliases (nicknames) for courses are also accepted, given that they are known and in the database. - - Example usage: - ``` - didier fiche ad2 - didier fiche "algoritmen en datastructuren 2" - ``` - """ + """Create links to study guides""" async with self.client.postgres_session as session: ufora_course = await ufora_courses.get_course_by_name(session, course) diff --git a/didier/cogs/tasks.py b/didier/cogs/tasks.py index eaeaebe..518f3f5 100644 --- a/didier/cogs/tasks.py +++ b/didier/cogs/tasks.py @@ -190,10 +190,8 @@ class Tasks(commands.Cog): await self.client.wait_until_ready() @check_birthdays.error - @pull_schedules.error @pull_ufora_announcements.error @remove_old_ufora_announcements.error - @reset_wordle_word.error async def _on_tasks_error(self, error: BaseException): """Error handler for all tasks""" print("".join(traceback.format_exception(type(error), error, error.__traceback__))) diff --git a/didier/utils/types/string.py b/didier/utils/types/string.py index 5cc5f93..4e02573 100644 --- a/didier/utils/types/string.py +++ b/didier/utils/types/string.py @@ -1,8 +1,7 @@ import math -import re -from typing import Optional, Union +from typing import Optional -__all__ = ["abbreviate", "leading", "pluralize", "re_find_all", "re_replace_with_list", "get_edu_year_name"] +__all__ = ["abbreviate", "leading", "pluralize", "get_edu_year_name"] def abbreviate(text: str, max_length: int) -> str: @@ -46,29 +45,6 @@ def pluralize(word: str, amount: int, plural_form: Optional[str] = None) -> str: return plural_form or (word + "s") -def re_find_all(pattern: str, string: str, flags: Union[int, re.RegexFlag] = 0) -> list[str]: - """Find all matches of a regex in a string""" - matches = [] - - while True: - match = re.search(pattern, string, flags=flags) - if not match: - break - - matches.append(match.group(0)) - string = string[match.end() :] - - return matches - - -def re_replace_with_list(pattern: str, string: str, replacements: list[str]) -> str: - """Replace all matches of a pattern one by one using a list of replacements""" - for replacement in replacements: - string = re.sub(pattern, replacement, string, count=1) - - return string - - def get_edu_year_name(year: int) -> str: # pragma: no cover """Get the string representation of a university year""" years = ["1st Bachelor", "2nd Bachelor", "3rd Bachelor", "1st Master", "2nd Master"]