Edit on GitHub

tickets_plus.cogs.settings

Settings change commands for Tickets+.

This extension provides commands to change the bot's settings. Those settings are guild-specific and are stored in the database.

Typical usage example:
from tickets_plus import bot
bot_instance = bot.TicketsPlusBot(...)
await bot_instance.load_extension("tickets_plus.cogs.settings")
  1"""Settings change commands for Tickets+.
  2
  3This extension provides commands to change the bot's settings.
  4Those settings are guild-specific and are stored in the database.
  5
  6Typical usage example:
  7    ```py
  8    from tickets_plus import bot
  9    bot_instance = bot.TicketsPlusBot(...)
 10    await bot_instance.load_extension("tickets_plus.cogs.settings")
 11    ```
 12"""
 13# License: EPL-2.0
 14# SPDX-License-Identifier: EPL-2.0
 15# Copyright (c) 2021-present The Tickets+ Contributors
 16# This Source Code may also be made available under the following
 17# Secondary Licenses when the conditions for such availability set forth
 18# in the Eclipse Public License, v. 2.0 are satisfied: GPL-3.0-only OR
 19# If later approved by the Initial Contributor, GPL-3.0-or-later.
 20
 21import datetime
 22import logging
 23from typing import List
 24
 25import discord
 26from discord import app_commands
 27from discord.ext import commands
 28
 29from tickets_plus import bot
 30from tickets_plus.ext import exceptions
 31
 32
 33@app_commands.guild_only()
 34@app_commands.default_permissions(administrator=True)
 35class Settings(commands.GroupCog, name="settings", description="Settings for the bot."):
 36    """Provides commands to change the bot's settings.
 37
 38    These settings are guild-specific and are stored in the database.
 39    We suggest the settings to be only changed by administrators;
 40    however, this can be changed discord-side.
 41    This is a group cog, so all commands are under the settings group.
 42    """
 43
 44    def __init__(self, bot_instance: bot.TicketsPlusBot):
 45        """Initializes the cog.
 46
 47        We initialize the variables we need.
 48        Then we call the superclasses __init__ method.
 49
 50        Args:
 51            bot_instance: The bot instance.
 52        """
 53        self._bt = bot_instance
 54        super().__init__()
 55        logging.info("Loaded %s", self.__class__.__name__)
 56
 57    async def ticket_types_autocomplete(self, ctx: discord.Interaction, arg: str) -> List[app_commands.Choice[str]]:
 58        """Autocomplete for ticket types.
 59
 60        This function is used to autocomplete the ticket types.
 61        It is used in the ticket type commands.
 62
 63        Args:
 64            ctx: The interaction context.
 65            arg: The argument to autocomplete.
 66
 67        Returns:
 68            A list of choices for the autocomplete.
 69        """
 70        async with self._bt.get_connection() as conn:
 71            ticket_types = await conn.get_ticket_types(ctx.guild_id  # type: ignore
 72                                                      )
 73        return [app_commands.Choice(name=t.prefix, value=t.prefix) for t in ticket_types if arg in t.prefix]
 74
 75    @app_commands.command(name="ticketbot", description="Change the ticket bots for your server.")
 76    @app_commands.describe(user="The user to add to ticket bots.")
 77    async def change_tracked(self, ctx: discord.Interaction, user: discord.User) -> None:
 78        """Changes the ticket bot for the server.
 79
 80        This command is used to change the ticket bots users.
 81        Ticket bots are users that can create tickets to be handled by our bot.
 82
 83        Args:
 84            ctx: The interaction context.
 85            user: The user to add/remove to ticket bots.
 86        """
 87        await ctx.response.defer(ephemeral=True)
 88        async with self._bt.get_connection() as conn:
 89            new, ticket_user = await conn.get_ticket_bot(
 90                user.id,
 91                ctx.guild_id,  # type: ignore
 92            )
 93            color = discord.Color.green() if new else discord.Color.red()
 94            emd = discord.Embed(title="Ticket Bot List Edited", color=color)
 95            if not new:
 96                await conn.delete(ticket_user)
 97                emd.add_field(name="Removed:", value=user.mention)
 98            else:
 99                emd.add_field(name="Added:", value=user.mention)
100            await conn.commit()
101        await ctx.followup.send(embed=emd, ephemeral=True)
102
103    @app_commands.command(name="staff", description="Change the staff roles.")
104    @app_commands.describe(role="The role to add/remove from staff roles.")
105    async def change_staff(self, ctx: discord.Interaction, role: discord.Role) -> None:
106        """Changes the staff roles for the server.
107
108        This command is used to change the staff roles.
109        Staff roles are roles that can use the staff commands.
110        I mean, it's pretty obvious, isn't it?
111
112        Args:
113            ctx: The interaction context.
114            role: The role to add/remove from staff roles.
115        """
116        await ctx.response.defer(ephemeral=True)
117        async with self._bt.get_connection() as conn:
118            new, staff_role = await conn.get_staff_role(
119                role.id,
120                ctx.guild_id,  # type: ignore
121            )
122            color = discord.Color.green() if new else discord.Color.red()
123            emd = discord.Embed(title="Staff Role List Edited", color=color)
124            if not new:
125                await conn.delete(staff_role)
126                emd.add_field(name="Removed:", value=role.mention)
127            else:
128                emd.add_field(name="Added:", value=role.mention)
129            await conn.commit()
130        await ctx.followup.send(embed=emd, ephemeral=True)
131
132    @app_commands.command(name="observers", description="Change the observers roles.")
133    @app_commands.describe(role="The role to add/remove from observers roles.")
134    async def change_observers(self, ctx: discord.Interaction, role: discord.Role) -> None:
135        """Changes the observer roles for the server.
136
137        This command is used to change the observer roles.
138        Observer roles are roles that are automatically added to ticket notes.
139        They are to be used in conjunction with the staff roles.
140        As observers don't have access to staff commands.
141
142        Args:
143            ctx: The interaction context.
144            role: The role to add/remove from observer roles.
145        """
146        await ctx.response.defer(ephemeral=True)
147        async with self._bt.get_connection() as conn:
148            new, obsrvrs = await conn.get_observers_role(
149                role.id,
150                ctx.guild_id,  # type: ignore
151            )
152            color = discord.Color.green() if new else discord.Color.red()
153            emd = discord.Embed(title="Observers Role List Edited", color=color)
154            emd.set_footer(text="Warning! Ticket notes are currently legacy, please use the main bot for ticket notes.")
155            if not new:
156                await conn.delete(obsrvrs)
157                emd.add_field(name="Removed:", value=role.mention)
158            else:
159                emd.add_field(name="Added:", value=role.mention)
160            await conn.commit()
161        await ctx.followup.send(embed=emd, ephemeral=True)
162
163    @app_commands.command(name="communitysupport", description="Change the community support roles.")
164    @app_commands.describe(role="The role to add/remove from community support roles.")
165    async def change_community_roles(self, ctx: discord.Interaction, role: discord.Role) -> None:
166        """Modifies the server's community support roles.
167
168        This command is used to change the community support roles.
169        Community support roles are roles that are automatically added to
170        community support tickets. But do not receive any permissions.
171        They are not pinged when a new ticket is created.
172
173        Args:
174            ctx: The interaction context.
175            role: The role to add/remove from community support roles.
176        """
177        await ctx.response.defer(ephemeral=True)
178        async with self._bt.get_connection() as conn:
179            new, comsup = await conn.get_community_role(
180                role.id,
181                ctx.guild_id,  # type: ignore
182            )
183            color = discord.Color.green() if new else discord.Color.red()
184            emd = discord.Embed(title="Community Support Role List Edited", color=color)
185            if not new:
186                await conn.delete(comsup)
187                emd.add_field(name="Removed:", value=role.mention)
188            else:
189                emd.add_field(name="Added:", value=role.mention)
190            await conn.commit()
191        await ctx.followup.send(embed=emd, ephemeral=True)
192
193    @app_commands.command(name="communityping", description="Change the community ping roles.")
194    @app_commands.describe(role="The role to add/remove from community ping roles.")
195    async def change_community_ping_roles(self, ctx: discord.Interaction, role: discord.Role) -> None:
196        """Modifies the server's community ping roles.
197
198        This command is used to change the community ping roles.
199        Community ping roles are to be used in conjunction with the community
200        support roles. They are pinged when a new ticket is created but,
201        after the addition of the community support roles.
202        They are not given any permissions, including the ability to see the
203        tickets. They are only pinged.
204
205        Args:
206            ctx: The interaction context.
207            role: The role to add/remove from community ping roles.
208        """
209        await ctx.response.defer(ephemeral=True)
210        async with self._bt.get_connection() as conn:
211            new, comsup = await conn.get_community_ping(
212                role.id,
213                ctx.guild_id,  # type: ignore
214            )
215            color = discord.Color.green() if new else discord.Color.red()
216            emd = discord.Embed(title="Community Ping Role List Edited", color=color)
217            if not new:
218                await conn.delete(comsup)
219                emd.add_field(name="Removed:", value=role.mention)
220            else:
221                emd.add_field(name="Added:", value=role.mention)
222            await conn.commit()
223        await ctx.followup.send(embed=emd, ephemeral=True)
224
225    @app_commands.command(name="openmsg", description="Change the open message.")
226    @app_commands.describe(message="The new open message.")
227    async def change_openmsg(self, ctx: discord.Interaction, message: str) -> None:
228        """This command is used to change the open message.
229
230        This is the message that opens a new ticket notes thread.
231        It is only visible to the staff team and observers.
232        The message must be less than 200 characters.
233        The default message is "Staff notes for Ticket $channel".
234        Where $channel is the channel mention.
235
236        Args:
237            ctx: The interaction context.
238            message: The new open message.
239
240        Raises:
241            `tickets_plus.ext.exceptions.InvalidParameters`: Message too long.
242                Raised when the message is longer than 200 characters.
243        """
244        await ctx.response.defer(ephemeral=True)
245        if len(message) > 200:
246            raise exceptions.InvalidParameters("The message must be less than"
247                                               " 200 characters.")
248        async with self._bt.get_connection() as conn:
249            guild = await conn.get_guild(ctx.guild_id)  # type: ignore
250            old = guild.open_message
251            guild.open_message = message
252            await conn.commit()
253        emd = discord.Embed(title="Open Message Changed", color=discord.Color.yellow())
254        emd.add_field(name="Old message:", value=old)
255        emd.add_field(name="New message:", value=message)
256        emd.set_footer(text="Warning! Ticket notes are currently legacy, please use the main bot for ticket notes.")
257        await ctx.followup.send(embed=emd, ephemeral=True)
258
259    @app_commands.command(name="penrole", description="Change the penalty roles.")
260    @app_commands.describe(role="The role to set as a penalty role.")
261    @app_commands.choices(penal=[
262        app_commands.Choice(name="Support Block", value=0),
263        app_commands.Choice(name="Helping Block", value=1),
264    ])
265    async def change_penrole(self, ctx: discord.Interaction, role: discord.Role,
266                             penal: app_commands.Choice[int]) -> None:
267        """Change the penalty roles.
268
269        Adjusts the penalty roles. These roles are used to block users from
270        creating tickets or helping in tickets.
271
272        Args:
273            ctx: The interaction context.
274            role: The role to set as a penalty role.
275            penal: The penalty for which to set the role.
276        """
277        await ctx.response.defer(ephemeral=True)
278        async with self._bt.get_connection() as conn:
279            guild = await conn.get_guild(ctx.guild_id)  # type: ignore
280            if penal.value == 0:
281                old = guild.support_block
282                guild.support_block = role.id
283            else:
284                old = guild.helping_block
285                guild.helping_block = role.id
286            await conn.commit()
287        emd = discord.Embed(title="Penalty Role Changed",
288                            description=f"Penalty: {penal.name}",
289                            color=discord.Color.yellow())
290        emd.add_field(name="Old role:", value=f"<@&{old}>")
291        emd.add_field(name="New role:", value=role.mention)
292        await ctx.followup.send(embed=emd, ephemeral=True)
293
294    @app_commands.command(name="staffteamname", description="Change the staff team's name.")
295    @app_commands.describe(name="The new staff team's name.")
296    async def change_staffteamname(self, ctx: discord.Interaction, name: str) -> None:
297        """This command is used to change the staff team's name.
298
299        We use this name when the bot sends messages as the staff team.
300        So it is important that it is set to something that makes sense.
301        The default is "Staff Team". But you can change it to whatever you
302        want. It is limited to 40 characters.
303
304        Args:
305            ctx: The interaction context.
306            name: The new staff team's name.
307
308        Raises:
309            `tickets_plus.ext.exceptions.InvalidParameters`: Name too long.
310                Raised when the name is longer than 40 characters.
311        """
312        await ctx.response.defer(ephemeral=True)
313        if len(name) > 40:
314            raise exceptions.InvalidParameters("Name too long")
315        async with self._bt.get_connection() as conn:
316            guild = await conn.get_guild(ctx.guild_id)  # type: ignore
317            old = guild.staff_team_name
318            guild.staff_team_name = name
319            await conn.commit()
320        emd = discord.Embed(title="Staff Team Name Changed", color=discord.Color.yellow())
321        emd.add_field(name="Old name:", value=old)
322        emd.add_field(name="New name:", value=name)
323        await ctx.followup.send(embed=emd, ephemeral=True)
324
325    @app_commands.command(name="timings", description="Change any internal bot timings.")
326    @app_commands.describe(
327        category="The category of timing to change.",
328        days="The new timing time days.",
329        hours="The new timing time hours.",
330        minutes="The new timing time minutes.",
331    )
332    @app_commands.choices(category=[
333        app_commands.Choice(name="First Response Auto-close", value=0),
334        app_commands.Choice(name="Last Response Auto-close", value=1),
335        app_commands.Choice(name="Ticket Close Warning", value=2),
336    ])
337    async def change_autoclose(self,
338                               ctx: discord.Interaction,
339                               category: app_commands.Choice[int],
340                               days: int = 0,
341                               hours: int = 0,
342                               minutes: int = 0) -> None:
343        """Set the guild-wide auto-close time.
344
345        This command is used to change the auto-close time.
346        We use this time to correctly calculate the time until a ticket
347        is automatically closed. This is the time displayed
348        in the ticket's description. Additionally, this is the time
349        we may later use to warn the user that their ticket is about
350        to be closed.
351
352        Args:
353            ctx: The interaction context.
354            category: The category of auto-close time to change.
355            days: The new auto-close time days. Default to 0.
356            hours: The new auto-close time hours. Default to 0.
357            minutes: The new auto-close time minutes. Default to 0.
358        """
359        await ctx.response.defer(ephemeral=True)
360        async with self._bt.get_connection() as conn:
361            guild = await conn.get_guild(ctx.guild_id)  # type: ignore
362            prev = None
363            if category.value == 1:
364                changed_close = guild.any_autoclose
365                category_txt = "Last Response"
366            elif category.value == 2:
367                changed_close = guild.warn_autoclose
368                category_txt = "Ticket Close Warning"
369            else:
370                changed_close = guild.first_autoclose
371                category_txt = "First Response"
372            if changed_close is not None:
373                prev = changed_close
374            if days + hours + minutes == 0:
375                changed_close = None
376                emd = discord.Embed(title=f"{category_txt} Disabled", color=discord.Color.red())
377                emd.add_field(name="Previous auto-close time:", value=f"{str(prev)}")
378            else:
379                changed_close = datetime.timedelta(days=days, hours=hours, minutes=minutes)
380                emd = discord.Embed(title=f"{category_txt} Timings Changed", color=discord.Color.yellow())
381                emd.add_field(name="Previous auto-close time:", value=f"{str(prev)}")
382                emd.add_field(name="New auto-close time:", value=f"{str(changed_close)}")
383            if category.value == 1:
384                guild.any_autoclose = changed_close
385            elif category.value == 2:
386                guild.warn_autoclose = changed_close
387            else:
388                guild.first_autoclose = changed_close
389            await conn.commit()
390        await ctx.followup.send(embed=emd, ephemeral=True)
391
392    @app_commands.command(name="toggle", description="Toggle a specified True/False value.")
393    @app_commands.describe(value="The value to toggle.")
394    @app_commands.choices(value=[
395        app_commands.Choice(name="Message Discovery", value=0),
396        app_commands.Choice(name="Button Stripping", value=1),
397        app_commands.Choice(name="Role Stripping", value=2),
398        app_commands.Choice(name="Integrated with Tickets", value=3)
399    ])
400    async def toggle_value(self, ctx: discord.Interaction, value: app_commands.Choice[int]) -> None:
401        """A generic toggle command.
402
403        A more space efficient way to implement the toggle commands.
404        This command is used to toggle the specified value.
405
406        Args:
407            ctx: The interaction context.
408            value: The value to toggle.
409        """
410        await ctx.response.defer(ephemeral=True)
411        async with self._bt.get_connection() as conn:
412            guild = await conn.get_guild(ctx.guild_id)  # type: ignore
413            if value.value == 0:
414                new_status = not guild.msg_discovery
415                guild.msg_discovery = new_status
416            elif value.value == 1:
417                new_status = not guild.strip_buttons
418                guild.strip_buttons = new_status
419            elif value.value == 2:
420                new_status = not guild.strip_roles
421                guild.strip_roles = new_status
422            else:
423                new_status = not guild.integrated
424                guild.integrated = new_status
425            await conn.commit()
426        emd = discord.Embed(title="Value Toggled",
427                            description=f"{value.name} is now {new_status}",
428                            color=discord.Color.green() if new_status else discord.Color.red())
429        await ctx.followup.send(embed=emd, ephemeral=True)
430
431    @app_commands.command(name="tickettype", description="Create/Delete a ticket type.")
432    @app_commands.describe(name=("The name of the ticket type."
433                                 " This should be the prefix you use for the ticket type."
434                                 " ie. #<name>-<number>."),
435                           comping="Whether or not to ping the community role.",
436                           comaccs="Whether or not to give the community role matched ticket.",
437                           strpbuttns="Whether or not to strip buttons from the ticket.",
438                           ignore="Whether or not to ignore the ticket type.")
439    @app_commands.rename(comping="communityping", comaccs="communityaccess", strpbuttns="stripbuttons")
440    @app_commands.autocomplete(name=ticket_types_autocomplete)
441    async def create_ticket_type(self,
442                                 ctx: discord.Interaction,
443                                 name: str,
444                                 comping: bool = False,
445                                 comaccs: bool = False,
446                                 strpbuttns: bool = False,
447                                 ignore: bool = False) -> None:
448        """Create/Delete a new ticket type.
449
450        This command is used to create a new ticket type.
451        A ticket type is a preset of overrides that are applied to
452        a ticket when it is created. This allows for some more
453        settings customization.
454
455        Args:
456            ctx: Interaction context
457            name: The name of the new ticket type.
458            comping: Whether to ping the community role.
459            comaccs: Whether to give the community role view tickets.
460            strpbuttns: Whether to strip buttons from the ticket.
461            ignore: Whether to ignore the ticket type.
462        """
463        await ctx.response.defer(ephemeral=True)
464        async with self._bt.get_connection() as conn:
465            new, tick_type = await conn.get_ticket_type(
466                ctx.guild_id,  # type: ignore
467                name=name,
468                comping=comping,
469                comaccs=comaccs,
470                strpbuttns=strpbuttns,
471                ignore=ignore)
472            if new:
473                emd = discord.Embed(title="Ticket Type Created",
474                                    description=f"Ticket type {name} created.",
475                                    color=discord.Color.green())
476            else:
477                await conn.delete(tick_type)
478                emd = discord.Embed(title="Ticket Type Deleted",
479                                    description=f"Ticket type {name} deleted.",
480                                    color=discord.Color.red())
481            await conn.commit()
482        await ctx.followup.send(embed=emd, ephemeral=True)
483
484    @app_commands.command(name="edittickettype", description="Edit a ticket type.")
485    @app_commands.describe(name=("The name of the ticket type."
486                                 " This should be the prefix you use for the ticket type."
487                                 " ie. #<name>-<number>."),
488                           comping="Whether or not to ping the community role.",
489                           comaccs="Whether or not to give the community role matched ticket.",
490                           strpbuttns="Whether or not to strip buttons from the ticket.",
491                           ignore="Whether or not to ignore the ticket type.")
492    @app_commands.rename(comping="communityping", comaccs="communityaccess", strpbuttns="stripbuttons")
493    @app_commands.autocomplete(name=ticket_types_autocomplete)
494    async def edit_ticket_type(self,
495                               ctx: discord.Interaction,
496                               name: str,
497                               comping: bool | None = None,
498                               comaccs: bool | None = None,
499                               strpbuttns: bool | None = None,
500                               ignore: bool | None = None) -> None:
501        """Edit a ticket type.
502
503        This command is used to edit a ticket type.
504        A ticket type is a preset of overrides that are applied to
505        a ticket when it is created. This allows for some more
506        settings customization.
507
508        Args:
509            ctx: Discord interaction context.
510            name: The name of the ticket type.
511            comping: Whether to ping the community role.
512            comaccs: Whether to give the community role view tickets.
513            strpbuttns: Whether to strip buttons from the ticket.
514            ignore: Whether to ignore the ticket type.
515        """
516        if not any([comping, comaccs, strpbuttns]):
517            raise exceptions.InvalidParameters("You must specify at least one value to edit.")
518        await ctx.response.defer(ephemeral=True)
519        async with self._bt.get_connection() as conn:
520            new, tick_type = await conn.get_ticket_type(
521                ctx.guild_id,  # type: ignore
522                name=name,
523            )
524            if new:
525                raise exceptions.InvalidParameters("Ticket type does not exist.")
526            else:
527                if comping is not None:
528                    tick_type.comping = comping
529                if comaccs is not None:
530                    tick_type.comaccs = comaccs
531                if strpbuttns is not None:
532                    tick_type.strpbuttns = strpbuttns
533                if ignore is not None:
534                    tick_type.ignore = ignore
535                emd = discord.Embed(title="Ticket Type Edited",
536                                    description=f"Ticket type {name} edited.",
537                                    color=discord.Color.green())
538            await conn.commit()
539        await ctx.followup.send(embed=emd, ephemeral=True)
540
541
542async def setup(bot_instance: bot.TicketsPlusBot) -> None:
543    """Setup up the settings commands.
544
545    We add the settings cog to the bot.
546    Nothing different from the normal setup function.
547
548    Args:
549        bot_instance: The bot instance.
550    """
551    await bot_instance.add_cog(Settings(bot_instance))
@app_commands.guild_only()
@app_commands.default_permissions(administrator=True)
class Settings(discord.ext.commands.cog.GroupCog):
 34@app_commands.guild_only()
 35@app_commands.default_permissions(administrator=True)
 36class Settings(commands.GroupCog, name="settings", description="Settings for the bot."):
 37    """Provides commands to change the bot's settings.
 38
 39    These settings are guild-specific and are stored in the database.
 40    We suggest the settings to be only changed by administrators;
 41    however, this can be changed discord-side.
 42    This is a group cog, so all commands are under the settings group.
 43    """
 44
 45    def __init__(self, bot_instance: bot.TicketsPlusBot):
 46        """Initializes the cog.
 47
 48        We initialize the variables we need.
 49        Then we call the superclasses __init__ method.
 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 ticket_types_autocomplete(self, ctx: discord.Interaction, arg: str) -> List[app_commands.Choice[str]]:
 59        """Autocomplete for ticket types.
 60
 61        This function is used to autocomplete the ticket types.
 62        It is used in the ticket type commands.
 63
 64        Args:
 65            ctx: The interaction context.
 66            arg: The argument to autocomplete.
 67
 68        Returns:
 69            A list of choices for the autocomplete.
 70        """
 71        async with self._bt.get_connection() as conn:
 72            ticket_types = await conn.get_ticket_types(ctx.guild_id  # type: ignore
 73                                                      )
 74        return [app_commands.Choice(name=t.prefix, value=t.prefix) for t in ticket_types if arg in t.prefix]
 75
 76    @app_commands.command(name="ticketbot", description="Change the ticket bots for your server.")
 77    @app_commands.describe(user="The user to add to ticket bots.")
 78    async def change_tracked(self, ctx: discord.Interaction, user: discord.User) -> None:
 79        """Changes the ticket bot for the server.
 80
 81        This command is used to change the ticket bots users.
 82        Ticket bots are users that can create tickets to be handled by our bot.
 83
 84        Args:
 85            ctx: The interaction context.
 86            user: The user to add/remove to ticket bots.
 87        """
 88        await ctx.response.defer(ephemeral=True)
 89        async with self._bt.get_connection() as conn:
 90            new, ticket_user = await conn.get_ticket_bot(
 91                user.id,
 92                ctx.guild_id,  # type: ignore
 93            )
 94            color = discord.Color.green() if new else discord.Color.red()
 95            emd = discord.Embed(title="Ticket Bot List Edited", color=color)
 96            if not new:
 97                await conn.delete(ticket_user)
 98                emd.add_field(name="Removed:", value=user.mention)
 99            else:
100                emd.add_field(name="Added:", value=user.mention)
101            await conn.commit()
102        await ctx.followup.send(embed=emd, ephemeral=True)
103
104    @app_commands.command(name="staff", description="Change the staff roles.")
105    @app_commands.describe(role="The role to add/remove from staff roles.")
106    async def change_staff(self, ctx: discord.Interaction, role: discord.Role) -> None:
107        """Changes the staff roles for the server.
108
109        This command is used to change the staff roles.
110        Staff roles are roles that can use the staff commands.
111        I mean, it's pretty obvious, isn't it?
112
113        Args:
114            ctx: The interaction context.
115            role: The role to add/remove from staff roles.
116        """
117        await ctx.response.defer(ephemeral=True)
118        async with self._bt.get_connection() as conn:
119            new, staff_role = await conn.get_staff_role(
120                role.id,
121                ctx.guild_id,  # type: ignore
122            )
123            color = discord.Color.green() if new else discord.Color.red()
124            emd = discord.Embed(title="Staff Role List Edited", color=color)
125            if not new:
126                await conn.delete(staff_role)
127                emd.add_field(name="Removed:", value=role.mention)
128            else:
129                emd.add_field(name="Added:", value=role.mention)
130            await conn.commit()
131        await ctx.followup.send(embed=emd, ephemeral=True)
132
133    @app_commands.command(name="observers", description="Change the observers roles.")
134    @app_commands.describe(role="The role to add/remove from observers roles.")
135    async def change_observers(self, ctx: discord.Interaction, role: discord.Role) -> None:
136        """Changes the observer roles for the server.
137
138        This command is used to change the observer roles.
139        Observer roles are roles that are automatically added to ticket notes.
140        They are to be used in conjunction with the staff roles.
141        As observers don't have access to staff commands.
142
143        Args:
144            ctx: The interaction context.
145            role: The role to add/remove from observer roles.
146        """
147        await ctx.response.defer(ephemeral=True)
148        async with self._bt.get_connection() as conn:
149            new, obsrvrs = await conn.get_observers_role(
150                role.id,
151                ctx.guild_id,  # type: ignore
152            )
153            color = discord.Color.green() if new else discord.Color.red()
154            emd = discord.Embed(title="Observers Role List Edited", color=color)
155            emd.set_footer(text="Warning! Ticket notes are currently legacy, please use the main bot for ticket notes.")
156            if not new:
157                await conn.delete(obsrvrs)
158                emd.add_field(name="Removed:", value=role.mention)
159            else:
160                emd.add_field(name="Added:", value=role.mention)
161            await conn.commit()
162        await ctx.followup.send(embed=emd, ephemeral=True)
163
164    @app_commands.command(name="communitysupport", description="Change the community support roles.")
165    @app_commands.describe(role="The role to add/remove from community support roles.")
166    async def change_community_roles(self, ctx: discord.Interaction, role: discord.Role) -> None:
167        """Modifies the server's community support roles.
168
169        This command is used to change the community support roles.
170        Community support roles are roles that are automatically added to
171        community support tickets. But do not receive any permissions.
172        They are not pinged when a new ticket is created.
173
174        Args:
175            ctx: The interaction context.
176            role: The role to add/remove from community support roles.
177        """
178        await ctx.response.defer(ephemeral=True)
179        async with self._bt.get_connection() as conn:
180            new, comsup = await conn.get_community_role(
181                role.id,
182                ctx.guild_id,  # type: ignore
183            )
184            color = discord.Color.green() if new else discord.Color.red()
185            emd = discord.Embed(title="Community Support Role List Edited", color=color)
186            if not new:
187                await conn.delete(comsup)
188                emd.add_field(name="Removed:", value=role.mention)
189            else:
190                emd.add_field(name="Added:", value=role.mention)
191            await conn.commit()
192        await ctx.followup.send(embed=emd, ephemeral=True)
193
194    @app_commands.command(name="communityping", description="Change the community ping roles.")
195    @app_commands.describe(role="The role to add/remove from community ping roles.")
196    async def change_community_ping_roles(self, ctx: discord.Interaction, role: discord.Role) -> None:
197        """Modifies the server's community ping roles.
198
199        This command is used to change the community ping roles.
200        Community ping roles are to be used in conjunction with the community
201        support roles. They are pinged when a new ticket is created but,
202        after the addition of the community support roles.
203        They are not given any permissions, including the ability to see the
204        tickets. They are only pinged.
205
206        Args:
207            ctx: The interaction context.
208            role: The role to add/remove from community ping roles.
209        """
210        await ctx.response.defer(ephemeral=True)
211        async with self._bt.get_connection() as conn:
212            new, comsup = await conn.get_community_ping(
213                role.id,
214                ctx.guild_id,  # type: ignore
215            )
216            color = discord.Color.green() if new else discord.Color.red()
217            emd = discord.Embed(title="Community Ping Role List Edited", color=color)
218            if not new:
219                await conn.delete(comsup)
220                emd.add_field(name="Removed:", value=role.mention)
221            else:
222                emd.add_field(name="Added:", value=role.mention)
223            await conn.commit()
224        await ctx.followup.send(embed=emd, ephemeral=True)
225
226    @app_commands.command(name="openmsg", description="Change the open message.")
227    @app_commands.describe(message="The new open message.")
228    async def change_openmsg(self, ctx: discord.Interaction, message: str) -> None:
229        """This command is used to change the open message.
230
231        This is the message that opens a new ticket notes thread.
232        It is only visible to the staff team and observers.
233        The message must be less than 200 characters.
234        The default message is "Staff notes for Ticket $channel".
235        Where $channel is the channel mention.
236
237        Args:
238            ctx: The interaction context.
239            message: The new open message.
240
241        Raises:
242            `tickets_plus.ext.exceptions.InvalidParameters`: Message too long.
243                Raised when the message is longer than 200 characters.
244        """
245        await ctx.response.defer(ephemeral=True)
246        if len(message) > 200:
247            raise exceptions.InvalidParameters("The message must be less than"
248                                               " 200 characters.")
249        async with self._bt.get_connection() as conn:
250            guild = await conn.get_guild(ctx.guild_id)  # type: ignore
251            old = guild.open_message
252            guild.open_message = message
253            await conn.commit()
254        emd = discord.Embed(title="Open Message Changed", color=discord.Color.yellow())
255        emd.add_field(name="Old message:", value=old)
256        emd.add_field(name="New message:", value=message)
257        emd.set_footer(text="Warning! Ticket notes are currently legacy, please use the main bot for ticket notes.")
258        await ctx.followup.send(embed=emd, ephemeral=True)
259
260    @app_commands.command(name="penrole", description="Change the penalty roles.")
261    @app_commands.describe(role="The role to set as a penalty role.")
262    @app_commands.choices(penal=[
263        app_commands.Choice(name="Support Block", value=0),
264        app_commands.Choice(name="Helping Block", value=1),
265    ])
266    async def change_penrole(self, ctx: discord.Interaction, role: discord.Role,
267                             penal: app_commands.Choice[int]) -> None:
268        """Change the penalty roles.
269
270        Adjusts the penalty roles. These roles are used to block users from
271        creating tickets or helping in tickets.
272
273        Args:
274            ctx: The interaction context.
275            role: The role to set as a penalty role.
276            penal: The penalty for which to set the role.
277        """
278        await ctx.response.defer(ephemeral=True)
279        async with self._bt.get_connection() as conn:
280            guild = await conn.get_guild(ctx.guild_id)  # type: ignore
281            if penal.value == 0:
282                old = guild.support_block
283                guild.support_block = role.id
284            else:
285                old = guild.helping_block
286                guild.helping_block = role.id
287            await conn.commit()
288        emd = discord.Embed(title="Penalty Role Changed",
289                            description=f"Penalty: {penal.name}",
290                            color=discord.Color.yellow())
291        emd.add_field(name="Old role:", value=f"<@&{old}>")
292        emd.add_field(name="New role:", value=role.mention)
293        await ctx.followup.send(embed=emd, ephemeral=True)
294
295    @app_commands.command(name="staffteamname", description="Change the staff team's name.")
296    @app_commands.describe(name="The new staff team's name.")
297    async def change_staffteamname(self, ctx: discord.Interaction, name: str) -> None:
298        """This command is used to change the staff team's name.
299
300        We use this name when the bot sends messages as the staff team.
301        So it is important that it is set to something that makes sense.
302        The default is "Staff Team". But you can change it to whatever you
303        want. It is limited to 40 characters.
304
305        Args:
306            ctx: The interaction context.
307            name: The new staff team's name.
308
309        Raises:
310            `tickets_plus.ext.exceptions.InvalidParameters`: Name too long.
311                Raised when the name is longer than 40 characters.
312        """
313        await ctx.response.defer(ephemeral=True)
314        if len(name) > 40:
315            raise exceptions.InvalidParameters("Name too long")
316        async with self._bt.get_connection() as conn:
317            guild = await conn.get_guild(ctx.guild_id)  # type: ignore
318            old = guild.staff_team_name
319            guild.staff_team_name = name
320            await conn.commit()
321        emd = discord.Embed(title="Staff Team Name Changed", color=discord.Color.yellow())
322        emd.add_field(name="Old name:", value=old)
323        emd.add_field(name="New name:", value=name)
324        await ctx.followup.send(embed=emd, ephemeral=True)
325
326    @app_commands.command(name="timings", description="Change any internal bot timings.")
327    @app_commands.describe(
328        category="The category of timing to change.",
329        days="The new timing time days.",
330        hours="The new timing time hours.",
331        minutes="The new timing time minutes.",
332    )
333    @app_commands.choices(category=[
334        app_commands.Choice(name="First Response Auto-close", value=0),
335        app_commands.Choice(name="Last Response Auto-close", value=1),
336        app_commands.Choice(name="Ticket Close Warning", value=2),
337    ])
338    async def change_autoclose(self,
339                               ctx: discord.Interaction,
340                               category: app_commands.Choice[int],
341                               days: int = 0,
342                               hours: int = 0,
343                               minutes: int = 0) -> None:
344        """Set the guild-wide auto-close time.
345
346        This command is used to change the auto-close time.
347        We use this time to correctly calculate the time until a ticket
348        is automatically closed. This is the time displayed
349        in the ticket's description. Additionally, this is the time
350        we may later use to warn the user that their ticket is about
351        to be closed.
352
353        Args:
354            ctx: The interaction context.
355            category: The category of auto-close time to change.
356            days: The new auto-close time days. Default to 0.
357            hours: The new auto-close time hours. Default to 0.
358            minutes: The new auto-close time minutes. Default to 0.
359        """
360        await ctx.response.defer(ephemeral=True)
361        async with self._bt.get_connection() as conn:
362            guild = await conn.get_guild(ctx.guild_id)  # type: ignore
363            prev = None
364            if category.value == 1:
365                changed_close = guild.any_autoclose
366                category_txt = "Last Response"
367            elif category.value == 2:
368                changed_close = guild.warn_autoclose
369                category_txt = "Ticket Close Warning"
370            else:
371                changed_close = guild.first_autoclose
372                category_txt = "First Response"
373            if changed_close is not None:
374                prev = changed_close
375            if days + hours + minutes == 0:
376                changed_close = None
377                emd = discord.Embed(title=f"{category_txt} Disabled", color=discord.Color.red())
378                emd.add_field(name="Previous auto-close time:", value=f"{str(prev)}")
379            else:
380                changed_close = datetime.timedelta(days=days, hours=hours, minutes=minutes)
381                emd = discord.Embed(title=f"{category_txt} Timings Changed", color=discord.Color.yellow())
382                emd.add_field(name="Previous auto-close time:", value=f"{str(prev)}")
383                emd.add_field(name="New auto-close time:", value=f"{str(changed_close)}")
384            if category.value == 1:
385                guild.any_autoclose = changed_close
386            elif category.value == 2:
387                guild.warn_autoclose = changed_close
388            else:
389                guild.first_autoclose = changed_close
390            await conn.commit()
391        await ctx.followup.send(embed=emd, ephemeral=True)
392
393    @app_commands.command(name="toggle", description="Toggle a specified True/False value.")
394    @app_commands.describe(value="The value to toggle.")
395    @app_commands.choices(value=[
396        app_commands.Choice(name="Message Discovery", value=0),
397        app_commands.Choice(name="Button Stripping", value=1),
398        app_commands.Choice(name="Role Stripping", value=2),
399        app_commands.Choice(name="Integrated with Tickets", value=3)
400    ])
401    async def toggle_value(self, ctx: discord.Interaction, value: app_commands.Choice[int]) -> None:
402        """A generic toggle command.
403
404        A more space efficient way to implement the toggle commands.
405        This command is used to toggle the specified value.
406
407        Args:
408            ctx: The interaction context.
409            value: The value to toggle.
410        """
411        await ctx.response.defer(ephemeral=True)
412        async with self._bt.get_connection() as conn:
413            guild = await conn.get_guild(ctx.guild_id)  # type: ignore
414            if value.value == 0:
415                new_status = not guild.msg_discovery
416                guild.msg_discovery = new_status
417            elif value.value == 1:
418                new_status = not guild.strip_buttons
419                guild.strip_buttons = new_status
420            elif value.value == 2:
421                new_status = not guild.strip_roles
422                guild.strip_roles = new_status
423            else:
424                new_status = not guild.integrated
425                guild.integrated = new_status
426            await conn.commit()
427        emd = discord.Embed(title="Value Toggled",
428                            description=f"{value.name} is now {new_status}",
429                            color=discord.Color.green() if new_status else discord.Color.red())
430        await ctx.followup.send(embed=emd, ephemeral=True)
431
432    @app_commands.command(name="tickettype", description="Create/Delete a ticket type.")
433    @app_commands.describe(name=("The name of the ticket type."
434                                 " This should be the prefix you use for the ticket type."
435                                 " ie. #<name>-<number>."),
436                           comping="Whether or not to ping the community role.",
437                           comaccs="Whether or not to give the community role matched ticket.",
438                           strpbuttns="Whether or not to strip buttons from the ticket.",
439                           ignore="Whether or not to ignore the ticket type.")
440    @app_commands.rename(comping="communityping", comaccs="communityaccess", strpbuttns="stripbuttons")
441    @app_commands.autocomplete(name=ticket_types_autocomplete)
442    async def create_ticket_type(self,
443                                 ctx: discord.Interaction,
444                                 name: str,
445                                 comping: bool = False,
446                                 comaccs: bool = False,
447                                 strpbuttns: bool = False,
448                                 ignore: bool = False) -> None:
449        """Create/Delete a new ticket type.
450
451        This command is used to create a new ticket type.
452        A ticket type is a preset of overrides that are applied to
453        a ticket when it is created. This allows for some more
454        settings customization.
455
456        Args:
457            ctx: Interaction context
458            name: The name of the new ticket type.
459            comping: Whether to ping the community role.
460            comaccs: Whether to give the community role view tickets.
461            strpbuttns: Whether to strip buttons from the ticket.
462            ignore: Whether to ignore the ticket type.
463        """
464        await ctx.response.defer(ephemeral=True)
465        async with self._bt.get_connection() as conn:
466            new, tick_type = await conn.get_ticket_type(
467                ctx.guild_id,  # type: ignore
468                name=name,
469                comping=comping,
470                comaccs=comaccs,
471                strpbuttns=strpbuttns,
472                ignore=ignore)
473            if new:
474                emd = discord.Embed(title="Ticket Type Created",
475                                    description=f"Ticket type {name} created.",
476                                    color=discord.Color.green())
477            else:
478                await conn.delete(tick_type)
479                emd = discord.Embed(title="Ticket Type Deleted",
480                                    description=f"Ticket type {name} deleted.",
481                                    color=discord.Color.red())
482            await conn.commit()
483        await ctx.followup.send(embed=emd, ephemeral=True)
484
485    @app_commands.command(name="edittickettype", description="Edit a ticket type.")
486    @app_commands.describe(name=("The name of the ticket type."
487                                 " This should be the prefix you use for the ticket type."
488                                 " ie. #<name>-<number>."),
489                           comping="Whether or not to ping the community role.",
490                           comaccs="Whether or not to give the community role matched ticket.",
491                           strpbuttns="Whether or not to strip buttons from the ticket.",
492                           ignore="Whether or not to ignore the ticket type.")
493    @app_commands.rename(comping="communityping", comaccs="communityaccess", strpbuttns="stripbuttons")
494    @app_commands.autocomplete(name=ticket_types_autocomplete)
495    async def edit_ticket_type(self,
496                               ctx: discord.Interaction,
497                               name: str,
498                               comping: bool | None = None,
499                               comaccs: bool | None = None,
500                               strpbuttns: bool | None = None,
501                               ignore: bool | None = None) -> None:
502        """Edit a ticket type.
503
504        This command is used to edit a ticket type.
505        A ticket type is a preset of overrides that are applied to
506        a ticket when it is created. This allows for some more
507        settings customization.
508
509        Args:
510            ctx: Discord interaction context.
511            name: The name of the ticket type.
512            comping: Whether to ping the community role.
513            comaccs: Whether to give the community role view tickets.
514            strpbuttns: Whether to strip buttons from the ticket.
515            ignore: Whether to ignore the ticket type.
516        """
517        if not any([comping, comaccs, strpbuttns]):
518            raise exceptions.InvalidParameters("You must specify at least one value to edit.")
519        await ctx.response.defer(ephemeral=True)
520        async with self._bt.get_connection() as conn:
521            new, tick_type = await conn.get_ticket_type(
522                ctx.guild_id,  # type: ignore
523                name=name,
524            )
525            if new:
526                raise exceptions.InvalidParameters("Ticket type does not exist.")
527            else:
528                if comping is not None:
529                    tick_type.comping = comping
530                if comaccs is not None:
531                    tick_type.comaccs = comaccs
532                if strpbuttns is not None:
533                    tick_type.strpbuttns = strpbuttns
534                if ignore is not None:
535                    tick_type.ignore = ignore
536                emd = discord.Embed(title="Ticket Type Edited",
537                                    description=f"Ticket type {name} edited.",
538                                    color=discord.Color.green())
539            await conn.commit()
540        await ctx.followup.send(embed=emd, ephemeral=True)

Provides commands to change the bot's settings.

These settings are guild-specific and are stored in the database. We suggest the settings to be only changed by administrators; however, this can be changed discord-side. This is a group cog, so all commands are under the settings group.

Settings(bot_instance: tickets_plus.bot.TicketsPlusBot)
45    def __init__(self, bot_instance: bot.TicketsPlusBot):
46        """Initializes the cog.
47
48        We initialize the variables we need.
49        Then we call the superclasses __init__ method.
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__)

Initializes the cog.

We initialize the variables we need. Then we call the superclasses __init__ method.

Arguments:
  • bot_instance: The bot instance.
async def ticket_types_autocomplete( self, ctx: discord.interactions.Interaction, arg: str) -> List[discord.app_commands.models.Choice[str]]:
58    async def ticket_types_autocomplete(self, ctx: discord.Interaction, arg: str) -> List[app_commands.Choice[str]]:
59        """Autocomplete for ticket types.
60
61        This function is used to autocomplete the ticket types.
62        It is used in the ticket type commands.
63
64        Args:
65            ctx: The interaction context.
66            arg: The argument to autocomplete.
67
68        Returns:
69            A list of choices for the autocomplete.
70        """
71        async with self._bt.get_connection() as conn:
72            ticket_types = await conn.get_ticket_types(ctx.guild_id  # type: ignore
73                                                      )
74        return [app_commands.Choice(name=t.prefix, value=t.prefix) for t in ticket_types if arg in t.prefix]

Autocomplete for ticket types.

This function is used to autocomplete the ticket types. It is used in the ticket type commands.

Arguments:
  • ctx: The interaction context.
  • arg: The argument to autocomplete.
Returns:

A list of choices for the autocomplete.

change_tracked = <discord.app_commands.commands.Command object>
change_staff = <discord.app_commands.commands.Command object>
change_observers = <discord.app_commands.commands.Command object>
change_community_roles = <discord.app_commands.commands.Command object>
change_community_ping_roles = <discord.app_commands.commands.Command object>
change_openmsg = <discord.app_commands.commands.Command object>
change_penrole = <discord.app_commands.commands.Command object>
change_staffteamname = <discord.app_commands.commands.Command object>
change_autoclose = <discord.app_commands.commands.Command object>
toggle_value = <discord.app_commands.commands.Command object>
create_ticket_type = <discord.app_commands.commands.Command object>
edit_ticket_type = <discord.app_commands.commands.Command object>
async def setup(bot_instance: tickets_plus.bot.TicketsPlusBot) -> None:
543async def setup(bot_instance: bot.TicketsPlusBot) -> None:
544    """Setup up the settings commands.
545
546    We add the settings cog to the bot.
547    Nothing different from the normal setup function.
548
549    Args:
550        bot_instance: The bot instance.
551    """
552    await bot_instance.add_cog(Settings(bot_instance))

Setup up the settings commands.

We add the settings cog to the bot. Nothing different from the normal setup function.

Arguments:
  • bot_instance: The bot instance.