Edit on GitHub

tickets_plus.cogs.tags

An extension for all your tagging needs!

This module contains the Tags cog, which allows users to create and manage tags, which are essentially just snippets of text that can be called up with a command. Tags can be used by anyone, and can be created by anyone who has a staff role.

Typical usage example:
from tickets_plus import bot
bot_instance = bot.TicketsPlus(...)
bot_instance.load_extension("tickets_plus.cogs.tags")
  1"""An extension for all your tagging needs!
  2
  3This module contains the Tags cog, which allows users to create and manage
  4tags, which are essentially just snippets of text that can be called up
  5with a command. Tags can be used by anyone, and can be created by anyone
  6who has a staff role.
  7
  8Typical usage example:
  9    ```py
 10    from tickets_plus import bot
 11    bot_instance = bot.TicketsPlus(...)
 12    bot_instance.load_extension("tickets_plus.cogs.tags")
 13    ```
 14"""
 15# License: EPL-2.0
 16# SPDX-License-Identifier: EPL-2.0
 17# Copyright (c) 2021-present The Tickets+ Contributors
 18# This Source Code may also be made available under the following
 19# Secondary Licenses when the conditions for such availability set forth
 20# in the Eclipse Public License, v. 2.0 are satisfied: GPL-3.0-only OR
 21# If later approved by the Initial Contributor, GPL-3.0-or-later.
 22
 23import logging
 24import types
 25from typing import List, Optional, Tuple
 26
 27import discord
 28from discord import app_commands
 29from discord.ext import commands
 30
 31from tickets_plus import bot
 32from tickets_plus.ext import checks, exceptions
 33
 34
 35@commands.guild_only()
 36class TagUtils(commands.GroupCog, name="tag", description="A for all your tagging needs!"):
 37    """Suitable for all your tagging needs!
 38
 39    This is the cog responsible for managing tags, which are essentially
 40    just snippets of text that can be called up with a command. Tags can
 41    be used by anyone, and can be created by anyone who has a staff role.
 42    """
 43
 44    def __init__(self, bot_instance: bot.TicketsPlusBot):
 45        """Initializes the TagUtils cog.
 46
 47        This method initializes the cog.
 48        It also sets the bot instance as a private attribute.
 49        And finally initializes the superclass.
 50
 51        Args:
 52            bot_instance: The bot instance.
 53        """
 54        self._bt = bot_instance
 55        super().__init__()
 56        logging.info("Loaded %s", self.__class__.__name__)
 57
 58    async def tag_autocomplete(self, ctx: discord.Interaction, arg: str) -> List[app_commands.Choice[str]]:
 59        """Autocomplete for tags.
 60
 61        This method is used to autocomplete tags.
 62        It gets the tags from the database and returns them as a list of
 63        choices.
 64
 65        Args:
 66            ctx: The interaction context.
 67            arg: The argument to autocomplete.
 68        """
 69        async with self._bt.get_connection() as conn:
 70            tags = await conn.get_tags(ctx.guild_id)  # type: ignore
 71        choices = []
 72        for tag in tags:
 73            if len(choices) >= 25:
 74                break
 75            if arg.lower() in tag.tag_name.lower():
 76                choices.append(app_commands.Choice(name=tag.tag_name, value=tag.tag_name))
 77        return choices
 78
 79    async def prep_tag(self, guild: int, tag: str, mention: Optional[discord.User]) -> Tuple[str, None | discord.Embed]:
 80        """Basic tag preparation.
 81
 82        Grabs the tag and packages it into a message and embed.
 83        The message also mentions the user if specified.
 84
 85        Args:
 86            guild: The guild to fetch the tag from.
 87            tag: The tag to send.
 88            mention: The user to mention.
 89
 90        Returns:
 91            A tuple of the message and embed.
 92
 93        Raises:
 94            InvalidParameters: The tag doesn't exist.
 95        """
 96        async with self._bt.get_connection() as conn:
 97            emd = None
 98            tag_data = await conn.fetch_tag(guild, tag.lower())  # type: ignore
 99            messg = ""
100            if mention:
101                messg = f"{mention.mention}\n"
102            if tag_data is None:
103                raise exceptions.InvalidParameters("That tag doesn't exist!")
104            if isinstance(tag_data, discord.Embed):
105                emd = tag_data
106            else:
107                messg += tag_data
108        return messg, emd
109
110    @app_commands.command(name="send", description="Send a tag")
111    @app_commands.describe(tag="The tag to send", mention="The user to mention", anon="Send anonymously")
112    @app_commands.autocomplete(tag=tag_autocomplete)
113    async def send(self,
114                   ctx: discord.Interaction,
115                   tag: str,
116                   mention: Optional[discord.User],
117                   anon: bool = False) -> None:
118        """Sends a tag.
119
120        This command sends a tag, which is a snippet of text that can be
121        called up with a command.
122
123        Args:
124            ctx: The interaction context.
125            tag: The tag to send.
126            mention: The user to mention.
127            anon: Whether to send anonymously.
128        """
129        await ctx.response.defer(ephemeral=anon)
130        if isinstance(ctx.channel, (
131                discord.ForumChannel,
132                discord.StageChannel,
133                discord.CategoryChannel,
134                types.NoneType,
135        )):
136            raise exceptions.InvalidLocation("You can't use this command here!")
137        messg, emd = await self.prep_tag(
138            ctx.guild_id,  # type: ignore
139            tag,
140            mention)
141        post = ctx.followup
142        if anon:
143            post = ctx.channel
144        if emd:
145            await post.send(content=messg, embed=emd)
146        else:
147            await post.send(messg)
148        await ctx.followup.send("Sent!", ephemeral=True)
149
150    @app_commands.command(name="create", description="Create/Delete a tag")
151    @checks.is_staff_check()
152    @app_commands.describe(
153        tag_name="The tag to create",
154        content="The content of the tag",
155        title="The title of the embed",
156        url="The url of the embed",
157        color="The color of the embed in hex (e.g. #ff0000)",
158        footer="The footer of the embed",
159        image="The image of the embed",
160        thumbnail="The thumbnail of the embed",
161        author="The author of the embed",
162    )
163    @app_commands.autocomplete(tag_name=tag_autocomplete)
164    async def create(self, ctx: discord.Interaction, tag_name: str, content: str, title: Optional[str],
165                     url: Optional[str], color: Optional[str], footer: Optional[str], image: Optional[str],
166                     thumbnail: Optional[str], author: Optional[str]) -> None:
167        """Creates or deletes a tag.
168
169        This command creates a tag, which is a snippet of text that can be
170        called up with a command.
171        If embed parameters are specified, it creates an embed.
172        If the tag already exists, it deletes it.
173
174        Args:
175            ctx: The interaction context.
176            tag_name: The tag to create.
177            content: The content of the tag.
178            title: The title of the embed.
179            url: The url of the embed.
180            color: The color of the embed.
181            footer: The footer of the embed.
182            image: The image of the embed.
183            thumbnail: The thumbnail of the embed.
184            author: The author of the embed.
185
186        Raises:
187            InvalidParameters: The tag already exists.
188        """
189        await ctx.response.defer(ephemeral=True)
190        parsed_color = None
191        opt_params = {
192            "title": title,
193            "url": url,
194            "color": parsed_color,
195            "footer": footer,
196            "image": image,
197            "thumbnail": thumbnail,
198            "author": author
199        }
200        if any(opt_params.values()) and not title:
201            raise exceptions.InvalidParameters("You need to specify a title"
202                                               " if you want to use embed parameters!")
203        if color:
204            parsed_color = discord.Color.from_str(color).value
205        opt_params["color"] = parsed_color
206        async with self._bt.get_connection() as conn:
207            new, tag_data = await conn.get_tag(
208                ctx.guild_id,  # type: ignore
209                tag_name.lower(),
210                content,
211                embed_args=opt_params)
212            if new:
213                emd = discord.Embed(title="Tag created!",
214                                    description=f"Tag `{tag_name}` created!",
215                                    color=discord.Color.green())
216            else:
217                await conn.delete(tag_data)
218                emd = discord.Embed(title="Tag deleted!",
219                                    description=f"Tag `{tag_name}` deleted!",
220                                    color=discord.Color.red())
221            await conn.commit()
222        await ctx.followup.send(embed=emd, ephemeral=True)
223
224    @app_commands.command(name="edit", description="Edit a tag")
225    @checks.is_staff_check()
226    @app_commands.describe(
227        tag="The tag to edit",
228        content="The content of the tag",
229        title="The title of the embed",
230        url="The url of the embed",
231        color="The color of the embed in hex (e.g. #ff0000)",
232        footer="The footer of the embed",
233        image="The image of the embed",
234        thumbnail="The thumbnail of the embed",
235        author="The author of the embed",
236    )
237    @app_commands.autocomplete(tag=tag_autocomplete)
238    async def edit(self, ctx: discord.Interaction, tag: str, content: Optional[str], title: Optional[str],
239                   url: Optional[str], color: Optional[str], footer: Optional[str], image: Optional[str],
240                   thumbnail: Optional[str], author: Optional[str]) -> None:
241        """Edits a tag.
242
243        This command edits a tag, which is a snippet of text that can be
244        called up with a command.
245        If embed parameters are specified, it edits the embed.
246
247        Args:
248            ctx: The interaction context.
249            tag: The tag to edit.
250            content: The content of the tag.
251            title: The title of the embed.
252            url: The url of the embed.
253            color: The color of the embed.
254            footer: The footer of the embed.
255            image: The image of the embed.
256            thumbnail: The thumbnail of the embed.
257            author: The author of the embed.
258
259        Raises:
260            InvalidParameters: The tag doesn't exist.
261        """
262        parsed_color = None
263        if color:
264            parsed_color = discord.Color.from_str(color).value
265        opt_params = {
266            "url": url,
267            "color": parsed_color,
268            "footer": footer,
269            "image": image,
270            "thumbnail": thumbnail,
271            "author": author
272        }
273        async with self._bt.get_connection() as conn:
274            new, tag_data = await conn.get_tag(
275                ctx.guild_id,  # type: ignore
276                tag.lower(),
277                "")
278            if new:
279                raise exceptions.InvalidParameters("That tag doesn't exist!")
280            if content:
281                tag_data.description = content
282            if title:
283                tag_data.title = title
284            if not tag_data.title and any(opt_params.values()):
285                raise exceptions.InvalidParameters("You need to specify a title if"
286                                                   " you want to use embed parameters!")
287            for param, value in opt_params.items():
288                if value:
289                    setattr(tag_data, param, value)
290            await conn.commit()
291        emd = discord.Embed(title="Tag edited!", description=f"Tag `{tag}` edited!", color=discord.Color.green())
292        await ctx.response.send_message(embed=emd, ephemeral=True)
293
294
295async def setup(bot_instance: bot.TicketsPlusBot) -> None:
296    """Sets up the tag cog.
297
298    Sets the bot to use the cog properly.
299
300    Args:
301        bot_instance: The bot instance.
302    """
303    await bot_instance.add_cog(TagUtils(bot_instance))
@commands.guild_only()
class TagUtils(discord.ext.commands.cog.GroupCog):
 36@commands.guild_only()
 37class TagUtils(commands.GroupCog, name="tag", description="A for all your tagging needs!"):
 38    """Suitable for all your tagging needs!
 39
 40    This is the cog responsible for managing tags, which are essentially
 41    just snippets of text that can be called up with a command. Tags can
 42    be used by anyone, and can be created by anyone who has a staff role.
 43    """
 44
 45    def __init__(self, bot_instance: bot.TicketsPlusBot):
 46        """Initializes the TagUtils cog.
 47
 48        This method initializes the cog.
 49        It also sets the bot instance as a private attribute.
 50        And finally initializes the superclass.
 51
 52        Args:
 53            bot_instance: The bot instance.
 54        """
 55        self._bt = bot_instance
 56        super().__init__()
 57        logging.info("Loaded %s", self.__class__.__name__)
 58
 59    async def tag_autocomplete(self, ctx: discord.Interaction, arg: str) -> List[app_commands.Choice[str]]:
 60        """Autocomplete for tags.
 61
 62        This method is used to autocomplete tags.
 63        It gets the tags from the database and returns them as a list of
 64        choices.
 65
 66        Args:
 67            ctx: The interaction context.
 68            arg: The argument to autocomplete.
 69        """
 70        async with self._bt.get_connection() as conn:
 71            tags = await conn.get_tags(ctx.guild_id)  # type: ignore
 72        choices = []
 73        for tag in tags:
 74            if len(choices) >= 25:
 75                break
 76            if arg.lower() in tag.tag_name.lower():
 77                choices.append(app_commands.Choice(name=tag.tag_name, value=tag.tag_name))
 78        return choices
 79
 80    async def prep_tag(self, guild: int, tag: str, mention: Optional[discord.User]) -> Tuple[str, None | discord.Embed]:
 81        """Basic tag preparation.
 82
 83        Grabs the tag and packages it into a message and embed.
 84        The message also mentions the user if specified.
 85
 86        Args:
 87            guild: The guild to fetch the tag from.
 88            tag: The tag to send.
 89            mention: The user to mention.
 90
 91        Returns:
 92            A tuple of the message and embed.
 93
 94        Raises:
 95            InvalidParameters: The tag doesn't exist.
 96        """
 97        async with self._bt.get_connection() as conn:
 98            emd = None
 99            tag_data = await conn.fetch_tag(guild, tag.lower())  # type: ignore
100            messg = ""
101            if mention:
102                messg = f"{mention.mention}\n"
103            if tag_data is None:
104                raise exceptions.InvalidParameters("That tag doesn't exist!")
105            if isinstance(tag_data, discord.Embed):
106                emd = tag_data
107            else:
108                messg += tag_data
109        return messg, emd
110
111    @app_commands.command(name="send", description="Send a tag")
112    @app_commands.describe(tag="The tag to send", mention="The user to mention", anon="Send anonymously")
113    @app_commands.autocomplete(tag=tag_autocomplete)
114    async def send(self,
115                   ctx: discord.Interaction,
116                   tag: str,
117                   mention: Optional[discord.User],
118                   anon: bool = False) -> None:
119        """Sends a tag.
120
121        This command sends a tag, which is a snippet of text that can be
122        called up with a command.
123
124        Args:
125            ctx: The interaction context.
126            tag: The tag to send.
127            mention: The user to mention.
128            anon: Whether to send anonymously.
129        """
130        await ctx.response.defer(ephemeral=anon)
131        if isinstance(ctx.channel, (
132                discord.ForumChannel,
133                discord.StageChannel,
134                discord.CategoryChannel,
135                types.NoneType,
136        )):
137            raise exceptions.InvalidLocation("You can't use this command here!")
138        messg, emd = await self.prep_tag(
139            ctx.guild_id,  # type: ignore
140            tag,
141            mention)
142        post = ctx.followup
143        if anon:
144            post = ctx.channel
145        if emd:
146            await post.send(content=messg, embed=emd)
147        else:
148            await post.send(messg)
149        await ctx.followup.send("Sent!", ephemeral=True)
150
151    @app_commands.command(name="create", description="Create/Delete a tag")
152    @checks.is_staff_check()
153    @app_commands.describe(
154        tag_name="The tag to create",
155        content="The content of the tag",
156        title="The title of the embed",
157        url="The url of the embed",
158        color="The color of the embed in hex (e.g. #ff0000)",
159        footer="The footer of the embed",
160        image="The image of the embed",
161        thumbnail="The thumbnail of the embed",
162        author="The author of the embed",
163    )
164    @app_commands.autocomplete(tag_name=tag_autocomplete)
165    async def create(self, ctx: discord.Interaction, tag_name: str, content: str, title: Optional[str],
166                     url: Optional[str], color: Optional[str], footer: Optional[str], image: Optional[str],
167                     thumbnail: Optional[str], author: Optional[str]) -> None:
168        """Creates or deletes a tag.
169
170        This command creates a tag, which is a snippet of text that can be
171        called up with a command.
172        If embed parameters are specified, it creates an embed.
173        If the tag already exists, it deletes it.
174
175        Args:
176            ctx: The interaction context.
177            tag_name: The tag to create.
178            content: The content of the tag.
179            title: The title of the embed.
180            url: The url of the embed.
181            color: The color of the embed.
182            footer: The footer of the embed.
183            image: The image of the embed.
184            thumbnail: The thumbnail of the embed.
185            author: The author of the embed.
186
187        Raises:
188            InvalidParameters: The tag already exists.
189        """
190        await ctx.response.defer(ephemeral=True)
191        parsed_color = None
192        opt_params = {
193            "title": title,
194            "url": url,
195            "color": parsed_color,
196            "footer": footer,
197            "image": image,
198            "thumbnail": thumbnail,
199            "author": author
200        }
201        if any(opt_params.values()) and not title:
202            raise exceptions.InvalidParameters("You need to specify a title"
203                                               " if you want to use embed parameters!")
204        if color:
205            parsed_color = discord.Color.from_str(color).value
206        opt_params["color"] = parsed_color
207        async with self._bt.get_connection() as conn:
208            new, tag_data = await conn.get_tag(
209                ctx.guild_id,  # type: ignore
210                tag_name.lower(),
211                content,
212                embed_args=opt_params)
213            if new:
214                emd = discord.Embed(title="Tag created!",
215                                    description=f"Tag `{tag_name}` created!",
216                                    color=discord.Color.green())
217            else:
218                await conn.delete(tag_data)
219                emd = discord.Embed(title="Tag deleted!",
220                                    description=f"Tag `{tag_name}` deleted!",
221                                    color=discord.Color.red())
222            await conn.commit()
223        await ctx.followup.send(embed=emd, ephemeral=True)
224
225    @app_commands.command(name="edit", description="Edit a tag")
226    @checks.is_staff_check()
227    @app_commands.describe(
228        tag="The tag to edit",
229        content="The content of the tag",
230        title="The title of the embed",
231        url="The url of the embed",
232        color="The color of the embed in hex (e.g. #ff0000)",
233        footer="The footer of the embed",
234        image="The image of the embed",
235        thumbnail="The thumbnail of the embed",
236        author="The author of the embed",
237    )
238    @app_commands.autocomplete(tag=tag_autocomplete)
239    async def edit(self, ctx: discord.Interaction, tag: str, content: Optional[str], title: Optional[str],
240                   url: Optional[str], color: Optional[str], footer: Optional[str], image: Optional[str],
241                   thumbnail: Optional[str], author: Optional[str]) -> None:
242        """Edits a tag.
243
244        This command edits a tag, which is a snippet of text that can be
245        called up with a command.
246        If embed parameters are specified, it edits the embed.
247
248        Args:
249            ctx: The interaction context.
250            tag: The tag to edit.
251            content: The content of the tag.
252            title: The title of the embed.
253            url: The url of the embed.
254            color: The color of the embed.
255            footer: The footer of the embed.
256            image: The image of the embed.
257            thumbnail: The thumbnail of the embed.
258            author: The author of the embed.
259
260        Raises:
261            InvalidParameters: The tag doesn't exist.
262        """
263        parsed_color = None
264        if color:
265            parsed_color = discord.Color.from_str(color).value
266        opt_params = {
267            "url": url,
268            "color": parsed_color,
269            "footer": footer,
270            "image": image,
271            "thumbnail": thumbnail,
272            "author": author
273        }
274        async with self._bt.get_connection() as conn:
275            new, tag_data = await conn.get_tag(
276                ctx.guild_id,  # type: ignore
277                tag.lower(),
278                "")
279            if new:
280                raise exceptions.InvalidParameters("That tag doesn't exist!")
281            if content:
282                tag_data.description = content
283            if title:
284                tag_data.title = title
285            if not tag_data.title and any(opt_params.values()):
286                raise exceptions.InvalidParameters("You need to specify a title if"
287                                                   " you want to use embed parameters!")
288            for param, value in opt_params.items():
289                if value:
290                    setattr(tag_data, param, value)
291            await conn.commit()
292        emd = discord.Embed(title="Tag edited!", description=f"Tag `{tag}` edited!", color=discord.Color.green())
293        await ctx.response.send_message(embed=emd, ephemeral=True)

Suitable for all your tagging needs!

This is the cog responsible for managing tags, which are essentially just snippets of text that can be called up with a command. Tags can be used by anyone, and can be created by anyone who has a staff role.

TagUtils(bot_instance: tickets_plus.bot.TicketsPlusBot)
45    def __init__(self, bot_instance: bot.TicketsPlusBot):
46        """Initializes the TagUtils cog.
47
48        This method initializes the cog.
49        It also sets the bot instance as a private attribute.
50        And finally initializes the superclass.
51
52        Args:
53            bot_instance: The bot instance.
54        """
55        self._bt = bot_instance
56        super().__init__()
57        logging.info("Loaded %s", self.__class__.__name__)

Initializes the TagUtils cog.

This method initializes the cog. It also sets the bot instance as a private attribute. And finally initializes the superclass.

Arguments:
  • bot_instance: The bot instance.
async def tag_autocomplete( self, ctx: discord.interactions.Interaction, arg: str) -> List[discord.app_commands.models.Choice[str]]:
59    async def tag_autocomplete(self, ctx: discord.Interaction, arg: str) -> List[app_commands.Choice[str]]:
60        """Autocomplete for tags.
61
62        This method is used to autocomplete tags.
63        It gets the tags from the database and returns them as a list of
64        choices.
65
66        Args:
67            ctx: The interaction context.
68            arg: The argument to autocomplete.
69        """
70        async with self._bt.get_connection() as conn:
71            tags = await conn.get_tags(ctx.guild_id)  # type: ignore
72        choices = []
73        for tag in tags:
74            if len(choices) >= 25:
75                break
76            if arg.lower() in tag.tag_name.lower():
77                choices.append(app_commands.Choice(name=tag.tag_name, value=tag.tag_name))
78        return choices

Autocomplete for tags.

This method is used to autocomplete tags. It gets the tags from the database and returns them as a list of choices.

Arguments:
  • ctx: The interaction context.
  • arg: The argument to autocomplete.
async def prep_tag( self, guild: int, tag: str, mention: Optional[discord.user.User]) -> Tuple[str, None | discord.embeds.Embed]:
 80    async def prep_tag(self, guild: int, tag: str, mention: Optional[discord.User]) -> Tuple[str, None | discord.Embed]:
 81        """Basic tag preparation.
 82
 83        Grabs the tag and packages it into a message and embed.
 84        The message also mentions the user if specified.
 85
 86        Args:
 87            guild: The guild to fetch the tag from.
 88            tag: The tag to send.
 89            mention: The user to mention.
 90
 91        Returns:
 92            A tuple of the message and embed.
 93
 94        Raises:
 95            InvalidParameters: The tag doesn't exist.
 96        """
 97        async with self._bt.get_connection() as conn:
 98            emd = None
 99            tag_data = await conn.fetch_tag(guild, tag.lower())  # type: ignore
100            messg = ""
101            if mention:
102                messg = f"{mention.mention}\n"
103            if tag_data is None:
104                raise exceptions.InvalidParameters("That tag doesn't exist!")
105            if isinstance(tag_data, discord.Embed):
106                emd = tag_data
107            else:
108                messg += tag_data
109        return messg, emd

Basic tag preparation.

Grabs the tag and packages it into a message and embed. The message also mentions the user if specified.

Arguments:
  • guild: The guild to fetch the tag from.
  • tag: The tag to send.
  • mention: The user to mention.
Returns:

A tuple of the message and embed.

Raises:
  • InvalidParameters: The tag doesn't exist.
send = <discord.app_commands.commands.Command object>
create = <discord.app_commands.commands.Command object>
edit = <discord.app_commands.commands.Command object>
async def setup(bot_instance: tickets_plus.bot.TicketsPlusBot) -> None:
296async def setup(bot_instance: bot.TicketsPlusBot) -> None:
297    """Sets up the tag cog.
298
299    Sets the bot to use the cog properly.
300
301    Args:
302        bot_instance: The bot instance.
303    """
304    await bot_instance.add_cog(TagUtils(bot_instance))

Sets up the tag cog.

Sets the bot to use the cog properly.

Arguments:
  • bot_instance: The bot instance.