mirror of https://github.com/stijndcl/didier
				
				
				
			
						commit
						a23ee3671a
					
				| 
						 | 
				
			
			@ -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 a given amount of Didier Dinks"""
 | 
			
		||||
        """Award a user `amount` 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}** heeft **{user.display_name}** **{amount}** {plural} geschonken.",
 | 
			
		||||
                f"**{ctx.author.display_name}** has awarded **{user.display_name}** **{amount}** {plural}.",
 | 
			
		||||
                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,15 +112,27 @@ 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 a given amount of Didier Dinks"""
 | 
			
		||||
        """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
 | 
			
		||||
        ```
 | 
			
		||||
        """
 | 
			
		||||
        async with self.client.postgres_session as session:
 | 
			
		||||
            invested = await crud.invest(session, ctx.author.id, amount)
 | 
			
		||||
            plural = pluralize("Didier Dink", invested)
 | 
			
		||||
| 
						 | 
				
			
			@ -134,7 +146,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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,7 +22,7 @@ from didier.views.modals import CreateBookmark
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
class Discord(commands.Cog):
 | 
			
		||||
    """Cog for commands related to Discord, servers, and members"""
 | 
			
		||||
    """Commands related to Discord itself, which work with resources like servers and members."""
 | 
			
		||||
 | 
			
		||||
    client: Didier
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -43,43 +43,57 @@ 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 a user"""
 | 
			
		||||
        """Command to check the birthday of `user`.
 | 
			
		||||
 | 
			
		||||
        Not passing an argument for `user` will show yours instead.
 | 
			
		||||
        """
 | 
			
		||||
        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 = "Your" if user is None else f"{user.display_name}'s"
 | 
			
		||||
        name: Optional[str] = f"{user.display_name}'s" if user is not None else None
 | 
			
		||||
 | 
			
		||||
        if birthday is None:
 | 
			
		||||
            return await ctx.reply(f"I don't know {name} birthday.", mention_author=False)
 | 
			
		||||
            return await ctx.reply(f"I don't know {name or 'your'} 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)
 | 
			
		||||
 | 
			
		||||
        return await ctx.reply(f"{name} 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`.
 | 
			
		||||
 | 
			
		||||
    @birthday.command(name="Set", aliases=["Config"])
 | 
			
		||||
    async def birthday_set(self, ctx: commands.Context, date_str: str):
 | 
			
		||||
        """Command to set your birthday"""
 | 
			
		||||
        Parsing of the `day`-argument happens in the following order: `DD/MM/YYYY`, `DD/MM/YY`, `DD/MM`.
 | 
			
		||||
        Other formats will not be accepted.
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            default_year = 2001
 | 
			
		||||
            date = str_to_date(date_str, formats=["%d/%m/%Y", "%d/%m/%y", "%d/%m"])
 | 
			
		||||
            date = str_to_date(day, formats=["%d/%m/%Y", "%d/%m/%y", "%d/%m"])
 | 
			
		||||
 | 
			
		||||
            # If no year was passed, make it 2001 by default
 | 
			
		||||
            if date_str.count("/") == 1:
 | 
			
		||||
            if day.count("/") == 1:
 | 
			
		||||
                date.replace(year=default_year)
 | 
			
		||||
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            return await ctx.reply(f"`{date_str}` is not a valid date.", mention_author=False)
 | 
			
		||||
            return await ctx.reply(f"`{day}` 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 a bookmarked message"""
 | 
			
		||||
        """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`.
 | 
			
		||||
        """
 | 
			
		||||
        # No label: shortcut to display bookmarks
 | 
			
		||||
        if label is None:
 | 
			
		||||
            return await self.bookmark_search(ctx, query=None)
 | 
			
		||||
| 
						 | 
				
			
			@ -92,9 +106,16 @@ 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"""
 | 
			
		||||
        """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".
 | 
			
		||||
        """
 | 
			
		||||
        # 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)
 | 
			
		||||
| 
						 | 
				
			
			@ -116,9 +137,12 @@ 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 a bookmark by its id"""
 | 
			
		||||
        """Delete the bookmark with id `bookmark_id`.
 | 
			
		||||
 | 
			
		||||
        You can only delete your own bookmarks.
 | 
			
		||||
        """
 | 
			
		||||
        # 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("#")
 | 
			
		||||
| 
						 | 
				
			
			@ -138,9 +162,18 @@ 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"""
 | 
			
		||||
        """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
 | 
			
		||||
        ```
 | 
			
		||||
        """
 | 
			
		||||
        async with self.client.postgres_session as session:
 | 
			
		||||
            results = await bookmarks.get_bookmarks(session, ctx.author.id, query=query)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -160,15 +193,25 @@ class Discord(commands.Cog):
 | 
			
		|||
        modal = CreateBookmark(self.client, message.jump_url)
 | 
			
		||||
        await interaction.response.send_modal(modal)
 | 
			
		||||
 | 
			
		||||
    @commands.command(name="Join", usage="[Thread]")
 | 
			
		||||
    @commands.command(name="join")
 | 
			
		||||
    async def join(self, ctx: commands.Context, thread: discord.Thread):
 | 
			
		||||
        """Make Didier join a 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.
 | 
			
		||||
        """
 | 
			
		||||
        if thread.me is not None:
 | 
			
		||||
            return await ctx.reply()
 | 
			
		||||
 | 
			
		||||
    @commands.command(name="Pin", usage="[Message]")
 | 
			
		||||
    @commands.command(name="pin")
 | 
			
		||||
    async def pin(self, ctx: commands.Context, message: Optional[discord.Message] = None):
 | 
			
		||||
        """Pin a message in the current channel"""
 | 
			
		||||
        """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.
 | 
			
		||||
        """
 | 
			
		||||
        # 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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,57 +31,62 @@ class Fun(commands.Cog):
 | 
			
		|||
 | 
			
		||||
    @commands.hybrid_command(
 | 
			
		||||
        name="dadjoke",
 | 
			
		||||
        aliases=["Dad", "Dj"],
 | 
			
		||||
        description="Why does Yoda's code always crash? Because there is no try.",
 | 
			
		||||
        aliases=["dad", "dj"],
 | 
			
		||||
    )
 | 
			
		||||
    async def dad_joke(self, ctx: commands.Context):
 | 
			
		||||
        """Get a random dad joke"""
 | 
			
		||||
        """Why does Yoda's code always crash? Because there is no try."""
 | 
			
		||||
        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, meme_name: str, *, fields: str):
 | 
			
		||||
        """Command group for meme-related commands"""
 | 
			
		||||
    @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"]`
 | 
			
		||||
        """
 | 
			
		||||
        async with ctx.typing():
 | 
			
		||||
            meme = await self._do_generate_meme(meme_name, shlex.split(fields))
 | 
			
		||||
            meme = await self._do_generate_meme(template, 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, meme_name: str):
 | 
			
		||||
        """Generate a preview for a meme, to see how the fields are structured"""
 | 
			
		||||
    @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."""
 | 
			
		||||
        async with ctx.typing():
 | 
			
		||||
            fields = [f"Field #{i + 1}" for i in range(20)]
 | 
			
		||||
            meme = await self._do_generate_meme(meme_name, fields)
 | 
			
		||||
            meme = await self._do_generate_meme(template, fields)
 | 
			
		||||
            return await ctx.reply(meme, mention_author=False)
 | 
			
		||||
 | 
			
		||||
    @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"""
 | 
			
		||||
    @memes_slash.command(name="generate")
 | 
			
		||||
    async def memegen_slash(self, interaction: discord.Interaction, template: str):
 | 
			
		||||
        """Generate a meme with template `template`."""
 | 
			
		||||
        async with self.client.postgres_session as session:
 | 
			
		||||
            result = expect(await get_meme_by_name(session, meme), entity_type="meme", argument=meme)
 | 
			
		||||
            result = expect(await get_meme_by_name(session, template), entity_type="meme", argument=template)
 | 
			
		||||
 | 
			
		||||
        modal = GenerateMeme(self.client, result)
 | 
			
		||||
        await interaction.response.send_modal(modal)
 | 
			
		||||
 | 
			
		||||
    @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"""
 | 
			
		||||
    @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."""
 | 
			
		||||
        await interaction.response.defer()
 | 
			
		||||
 | 
			
		||||
        fields = [f"Field #{i + 1}" for i in range(20)]
 | 
			
		||||
        meme_url = await self._do_generate_meme(meme, fields)
 | 
			
		||||
        meme_url = await self._do_generate_meme(template, fields)
 | 
			
		||||
 | 
			
		||||
        await interaction.followup.send(meme_url, ephemeral=True)
 | 
			
		||||
 | 
			
		||||
    @memegen_slash.autocomplete("meme")
 | 
			
		||||
    @memegen_preview_slash.autocomplete("meme")
 | 
			
		||||
    async def _memegen_slash_autocomplete_meme(
 | 
			
		||||
    @memegen_slash.autocomplete("template")
 | 
			
		||||
    @memegen_preview_slash.autocomplete("template")
 | 
			
		||||
    async def _memegen_slash_autocomplete_template(
 | 
			
		||||
        self, _: discord.Interaction, current: str
 | 
			
		||||
    ) -> list[app_commands.Choice[str]]:
 | 
			
		||||
        """Autocompletion for the 'meme'-parameter"""
 | 
			
		||||
        """Autocompletion for the 'template'-parameter"""
 | 
			
		||||
        return self.client.database_caches.memes.get_autocomplete_suggestions(current)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,44 +1,267 @@
 | 
			
		|||
from typing import List, Mapping, Optional
 | 
			
		||||
import inspect
 | 
			
		||||
import re
 | 
			
		||||
from typing import Mapping, Optional, Type
 | 
			
		||||
 | 
			
		||||
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 to override the default implementation
 | 
			
		||||
    """Customised Help command that overrides the default implementation
 | 
			
		||||
 | 
			
		||||
    The default is ugly as hell, so we do some fiddling with it
 | 
			
		||||
    The default is ugly as hell, so we do some fiddling with it and put everything
 | 
			
		||||
    in fancy embeds
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    @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, colour=discord.Colour.blue())
 | 
			
		||||
        embed.set_footer(text="Syntax: Didier Help [Categorie] of Didier Help [Commando]")
 | 
			
		||||
        embed = discord.Embed(title=title.title(), colour=discord.Colour.blue())
 | 
			
		||||
        return embed
 | 
			
		||||
 | 
			
		||||
    async def _filter_cogs(self, cogs: List[commands.Cog]) -> List[commands.Cog]:
 | 
			
		||||
    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]:
 | 
			
		||||
        """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))
 | 
			
		||||
 | 
			
		||||
        # 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))
 | 
			
		||||
        async def _predicate(cog: Optional[commands.Cog]) -> bool:
 | 
			
		||||
            if cog is None:
 | 
			
		||||
                return False
 | 
			
		||||
 | 
			
		||||
            # 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))
 | 
			
		||||
 | 
			
		||||
    @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)
 | 
			
		||||
    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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def setup(client: Didier):
 | 
			
		||||
    """Load the cog"""
 | 
			
		||||
    client.help_command = CustomHelpCommand()
 | 
			
		||||
    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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,21 +8,31 @@ from didier import Didier
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
class Meta(commands.Cog):
 | 
			
		||||
    """Cog for Didier-related commands"""
 | 
			
		||||
    """Commands related to Didier himself."""
 | 
			
		||||
 | 
			
		||||
    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):
 | 
			
		||||
        """Ping command to get the delay of the bot"""
 | 
			
		||||
        """Get Didier's latency."""
 | 
			
		||||
        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):
 | 
			
		||||
        """Command to get links to the source code of Didier"""
 | 
			
		||||
        """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
 | 
			
		||||
        ```
 | 
			
		||||
        """
 | 
			
		||||
        repo_home = "https://github.com/stijndcl/didier"
 | 
			
		||||
 | 
			
		||||
        if command_name is None:
 | 
			
		||||
| 
						 | 
				
			
			@ -38,12 +48,12 @@ class Meta(commands.Cog):
 | 
			
		|||
            filename = src.co_filename
 | 
			
		||||
 | 
			
		||||
        if command is None:
 | 
			
		||||
            return await ctx.reply(f"Geen commando gevonden voor ``{command_name}``.", mention_author=False)
 | 
			
		||||
            return await ctx.reply(f"Found no command named `{command_name}`.", mention_author=False)
 | 
			
		||||
 | 
			
		||||
        lines, first_line = inspect.getsourcelines(src)
 | 
			
		||||
 | 
			
		||||
        if filename is None:
 | 
			
		||||
            return await ctx.reply(f"Geen code gevonden voor ``{command_name}``.", mention_author=False)
 | 
			
		||||
            return await ctx.reply(f"Found no source file for `{command_name}`.", mention_author=False)
 | 
			
		||||
 | 
			
		||||
        file_location = os.path.relpath(filename).replace("\\", "/")
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,16 +13,18 @@ from didier.data.scrapers import google
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
class Other(commands.Cog):
 | 
			
		||||
    """Cog for commands that don't really belong anywhere else"""
 | 
			
		||||
    """Commands that don't really belong anywhere else."""
 | 
			
		||||
 | 
			
		||||
    client: Didier
 | 
			
		||||
 | 
			
		||||
    def __init__(self, client: Didier):
 | 
			
		||||
        self.client = client
 | 
			
		||||
 | 
			
		||||
    @commands.hybrid_command(name="define", description="Urban Dictionary", aliases=["Ud", "Urban"], usage="[Term]")
 | 
			
		||||
    @commands.hybrid_command(
 | 
			
		||||
        name="define", aliases=["ud", "urban"], description="Look up the definition of a word on the Urban Dictionary"
 | 
			
		||||
    )
 | 
			
		||||
    async def define(self, ctx: commands.Context, *, query: str):
 | 
			
		||||
        """Look up the definition of a word on the Urban Dictionary"""
 | 
			
		||||
        """Look up the definition of `query` on the Urban Dictionary."""
 | 
			
		||||
        async with ctx.typing():
 | 
			
		||||
            status_code, definitions = await urban_dictionary.lookup(self.client.http_session, query)
 | 
			
		||||
            if not definitions:
 | 
			
		||||
| 
						 | 
				
			
			@ -30,10 +32,17 @@ class Other(commands.Cog):
 | 
			
		|||
 | 
			
		||||
            await ctx.reply(embed=definitions[0].to_embed(), mention_author=False)
 | 
			
		||||
 | 
			
		||||
    @commands.hybrid_command(name="google", description="Google search", usage="[Query]")
 | 
			
		||||
    @commands.hybrid_command(name="google", description="Google search")
 | 
			
		||||
    @app_commands.describe(query="Search query")
 | 
			
		||||
    async def google(self, ctx: commands.Context, *, query: str):
 | 
			
		||||
        """Google something"""
 | 
			
		||||
        """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"
 | 
			
		||||
        ```
 | 
			
		||||
        """
 | 
			
		||||
        async with ctx.typing():
 | 
			
		||||
            results = await google.google_search(self.client.http_session, query)
 | 
			
		||||
            embed = GoogleSearch(results).to_embed()
 | 
			
		||||
| 
						 | 
				
			
			@ -43,9 +52,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"], usage="[Name]")
 | 
			
		||||
    @commands.command(name="Link", aliases=["Links"])
 | 
			
		||||
    async def link_msg(self, ctx: commands.Context, name: str):
 | 
			
		||||
        """Message command to get the link to something"""
 | 
			
		||||
        """Get the link to the resource named `name`."""
 | 
			
		||||
        link = await self._get_link(name)
 | 
			
		||||
        if link is None:
 | 
			
		||||
            return await ctx.reply(f"Found no links matching `{name}`.", mention_author=False)
 | 
			
		||||
| 
						 | 
				
			
			@ -53,10 +62,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", description="Get the link to something")
 | 
			
		||||
    @app_commands.describe(name="The name of the link")
 | 
			
		||||
    @app_commands.command(name="link")
 | 
			
		||||
    @app_commands.describe(name="The name of the resource")
 | 
			
		||||
    async def link_slash(self, interaction: discord.Interaction, name: str):
 | 
			
		||||
        """Slash command to get the link to something"""
 | 
			
		||||
        """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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,21 +27,26 @@ class School(commands.Cog):
 | 
			
		|||
    def __init__(self, client: Didier):
 | 
			
		||||
        self.client = client
 | 
			
		||||
 | 
			
		||||
    @commands.hybrid_command(name="deadlines", description="Show upcoming deadlines")
 | 
			
		||||
    @commands.hybrid_command(name="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", description="Show your personalized schedule for a given day.", aliases=["Sched", "Schedule"]
 | 
			
		||||
    )
 | 
			
		||||
    @commands.hybrid_command(name="les", 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."""
 | 
			
		||||
        """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.
 | 
			
		||||
        """
 | 
			
		||||
        if day_dt is None:
 | 
			
		||||
            day_dt = date.today()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -62,14 +67,14 @@ class School(commands.Cog):
 | 
			
		|||
 | 
			
		||||
    @commands.hybrid_command(
 | 
			
		||||
        name="menu",
 | 
			
		||||
        description="Show the menu in the Ghent University restaurants.",
 | 
			
		||||
        aliases=["Eten", "Food"],
 | 
			
		||||
        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.
 | 
			
		||||
        """Show the menu in the Ghent University restaurants on `date`.
 | 
			
		||||
 | 
			
		||||
        Menus are Dutch, as a lot of dishes have very weird translations
 | 
			
		||||
        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.
 | 
			
		||||
        """
 | 
			
		||||
        if day_dt is None:
 | 
			
		||||
            day_dt = date.today()
 | 
			
		||||
| 
						 | 
				
			
			@ -83,11 +88,22 @@ class School(commands.Cog):
 | 
			
		|||
            await ctx.reply(embed=embed, mention_author=False)
 | 
			
		||||
 | 
			
		||||
    @commands.hybrid_command(
 | 
			
		||||
        name="fiche", description="Sends the link to the study guide for [Course]", aliases=["guide", "studiefiche"]
 | 
			
		||||
        name="fiche", description="Sends the link to study guides", 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):
 | 
			
		||||
        """Create links to study guides"""
 | 
			
		||||
        """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"
 | 
			
		||||
        ```
 | 
			
		||||
        """
 | 
			
		||||
        async with self.client.postgres_session as session:
 | 
			
		||||
            ufora_course = await ufora_courses.get_course_by_name(session, course)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -190,8 +190,10 @@ 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__)))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,8 @@
 | 
			
		|||
import math
 | 
			
		||||
from typing import Optional
 | 
			
		||||
import re
 | 
			
		||||
from typing import Optional, Union
 | 
			
		||||
 | 
			
		||||
__all__ = ["abbreviate", "leading", "pluralize", "get_edu_year_name"]
 | 
			
		||||
__all__ = ["abbreviate", "leading", "pluralize", "re_find_all", "re_replace_with_list", "get_edu_year_name"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def abbreviate(text: str, max_length: int) -> str:
 | 
			
		||||
| 
						 | 
				
			
			@ -45,6 +46,29 @@ 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"]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue