From a2c10ffd6bb5bfeb47be842e8cb465efba7834bd Mon Sep 17 00:00:00 2001 From: Kozejin <2613841+dkoz@users.noreply.github.com> Date: Thu, 14 Nov 2024 01:07:02 -0500 Subject: [PATCH] Implement Ticket System --- cogs/tickets.py | 228 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 cogs/tickets.py diff --git a/cogs/tickets.py b/cogs/tickets.py new file mode 100644 index 0000000..3e60a99 --- /dev/null +++ b/cogs/tickets.py @@ -0,0 +1,228 @@ +import nextcord +from nextcord.ext import commands +from nextcord.ui import Button, View +from nextcord.ext.commands import has_permissions +import utils.constants as constants +import json +import os +from io import StringIO + +class TicketSystem(commands.Cog): + def __init__(self, bot): + self.bot = bot + self.data_folder = 'data' + self.config_file = os.path.join(self.data_folder, 'tickets.json') + self.data = self.load_config() + self.ticket_counter = self.data.get('ticket_counter', 1) + self.dm_on_close = self.data.get('dm_on_close', False) + self.transcript_enabled = self.data.get('transcript_enabled', False) + self.bot.loop.create_task(self.setup_buttons()) + + def load_config(self): + if not os.path.exists(self.config_file): + os.makedirs(self.data_folder, exist_ok=True) + with open(self.config_file, 'w') as f: + json.dump({'ticket_channel_id': None, 'log_channel_id': None, 'buttons': [], 'categories': [], 'ticket_counter': 1, 'dm_on_close': False, 'transcript_enabled': False}, f) + with open(self.config_file, 'r') as f: + return json.load(f) + + def save_config(self): + with open(self.config_file, 'w') as f: + json.dump(self.data, f) + + async def setup_buttons(self): + await self.bot.wait_until_ready() + ticket_channel_id = self.data.get('ticket_channel_id') + if ticket_channel_id: + channel = self.bot.get_channel(ticket_channel_id) + if channel: + for button_info in self.data.get('buttons', []): + try: + message = await channel.fetch_message(button_info['message_id']) + except (nextcord.NotFound, nextcord.HTTPException): + continue + + view = View(timeout=None) + for category in self.data.get('categories', []): + button = Button(label=category, style=nextcord.ButtonStyle.green, custom_id=f"create_ticket_{category}") + button.callback = self.button_callback + view.add_item(button) + await message.edit(view=view) + + @commands.group(name="tickets", description="Get list of available ticket commands.", invoke_without_command=True) + @has_permissions(manage_channels=True) + async def tickets(self, ctx): + prefix = ctx.prefix + embed = nextcord.Embed( + title="Ticket System", + description="Commands for setting up the ticket system.", + color=nextcord.Color.orange() + ) + embed.add_field( + name="Commands", + value=f"`{prefix}tickets channel` - Set the ticket channel\n" + f"`{prefix}tickets logchannel` - Set the log channel\n" + f"`{prefix}tickets addcategory` - Add a new ticket category\n" + f"`{prefix}tickets removecategory` - Remove a ticket category\n" + f"`{prefix}tickets transcript` - Toggle DM on close and transcript generation." + ) + embed.set_footer(text="Still in testing...", icon_url=constants.FOOTER_IMAGE) + await ctx.send(embed=embed) + + @tickets.command(name="transcript") + @has_permissions(manage_channels=True) + async def toggle_transcript(self, ctx, dm_on_close: bool, transcript_enabled: bool): + self.data['dm_on_close'] = dm_on_close + self.data['transcript_enabled'] = transcript_enabled + self.save_config() + await ctx.send(f"DM on close set to {dm_on_close}. Transcript generation set to {transcript_enabled}.") + + @tickets.command(name="addcategory") + @has_permissions(manage_channels=True) + async def add_category(self, ctx, *, category_name: str): + if 'categories' not in self.data: + self.data['categories'] = [] + self.data['categories'].append(category_name) + self.save_config() + await self.update_ticket_message(ctx) + await ctx.send(f"Category '{category_name}' added to the ticket system.") + + @tickets.command(name="removecategory") + @has_permissions(manage_channels=True) + async def remove_category(self, ctx, *, category_name: str): + if 'categories' in self.data and category_name in self.data['categories']: + self.data['categories'].remove(category_name) + self.save_config() + await self.update_ticket_message(ctx) + await ctx.send(f"Category '{category_name}' removed from the ticket system.") + else: + await ctx.send(f"Category '{category_name}' does not exist.") + + async def update_ticket_message(self, ctx): + ticket_channel_id = self.data.get('ticket_channel_id') + if ticket_channel_id: + ticket_channel = self.bot.get_channel(ticket_channel_id) + if ticket_channel: + for button_info in self.data.get('buttons', []): + if button_info['channel_id'] == ticket_channel_id: + try: + message = await ticket_channel.fetch_message(button_info['message_id']) + await message.delete() + except (nextcord.NotFound, nextcord.HTTPException): + pass + + view = View(timeout=None) + for category in self.data['categories']: + button = Button(label=category, style=nextcord.ButtonStyle.green, custom_id=f"create_ticket_{category}") + button.callback = self.button_callback + view.add_item(button) + + embed = nextcord.Embed(title="Support Tickets", description="Click a button below to create a new ticket in a specific category.") + message = await ticket_channel.send(embed=embed, view=view) + + self.data['buttons'] = [{ + 'channel_id': ticket_channel.id, + 'message_id': message.id, + 'categories': self.data['categories'] + }] + self.save_config() + + @tickets.command(name="channel") + @has_permissions(manage_channels=True) + async def setup_ticket(self, ctx, channel: nextcord.TextChannel): + self.data['ticket_channel_id'] = channel.id + self.save_config() + await self.update_ticket_message(ctx) + await ctx.send(f"Ticket system set up in {channel.mention}") + + @tickets.command(name="logchannel") + @has_permissions(manage_channels=True) + async def setup_log(self, ctx, channel: nextcord.TextChannel): + self.data['log_channel_id'] = channel.id + self.save_config() + await ctx.send(f"Ticket log channel set to {channel.mention}") + + async def button_callback(self, interaction: nextcord.Interaction): + custom_id = interaction.data.get('custom_id') + if custom_id.startswith("create_ticket_"): + category = custom_id.split("_", 2)[2] + await self.create_ticket(interaction, category) + elif custom_id.startswith("close_ticket_"): + thread_id = int(custom_id.split("_")[-1]) + thread = await self.bot.fetch_channel(thread_id) + await self.close_ticket(interaction, thread) + + async def create_ticket(self, interaction: nextcord.Interaction, category: str): + member = interaction.user + ticket_channel_id = self.data.get('ticket_channel_id') + if ticket_channel_id: + ticket_channel = self.bot.get_channel(ticket_channel_id) + thread = await ticket_channel.create_thread(name=f"ticket-{self.ticket_counter}-{member.display_name}", auto_archive_duration=60) + self.ticket_counter += 1 + self.data['ticket_counter'] = self.ticket_counter + self.save_config() + + if self.data.get('log_channel_id'): + log_channel = self.bot.get_channel(self.data['log_channel_id']) + if log_channel: + embed = nextcord.Embed(title="Ticket Opened", color=nextcord.Color.green()) + embed.set_thumbnail(url=member.avatar.url) + embed.add_field(name="Created By", value=f"{member.display_name}", inline=False) + embed.add_field(name="User ID", value=member.id, inline=False) + embed.add_field(name="Opened", value=interaction.created_at.strftime("%Y-%m-%d %H:%M:%S"), inline=False) + embed.add_field(name="Category", value=category, inline=False) + embed.add_field(name="Ticket Name", value=thread.name, inline=False) + view = View(timeout=None) + view.add_item(Button(label="Go to Ticket", style=nextcord.ButtonStyle.link, url=thread.jump_url)) + await log_channel.send(embed=embed, view=view) + + close_button = Button(label="Close Ticket", style=nextcord.ButtonStyle.red, custom_id=f"close_ticket_{thread.id}") + close_button.callback = self.button_callback + view = View(timeout=None) + view.add_item(close_button) + embed = nextcord.Embed(title="Your Ticket", description="Support will be with you shortly. Click the button to close this ticket.") + embed.add_field(name="Explain your issue", value="Provide details about your issue to help us assist you.", inline=False) + await thread.send(member.mention, embed=embed, view=view) + await interaction.response.send_message("Ticket created!", ephemeral=True) + + async def close_ticket(self, interaction: nextcord.Interaction, thread: nextcord.Thread): + if not thread.archived: + await interaction.response.send_message( + embed=nextcord.Embed(title="Closed", description="Your ticket has been closed.", color=nextcord.Color.red()), + ephemeral=True + ) + await thread.edit(archived=True, locked=True) + + if self.transcript_enabled: + messages = await thread.history(limit=100).flatten() + transcript = StringIO() + for msg in reversed(messages): + transcript.write(f"{msg.author.display_name} [{msg.created_at}]: {msg.content}\n") + + transcript.seek(0) + + if self.dm_on_close: + try: + await interaction.user.send(file=nextcord.File(transcript, filename=f"{thread.name}_transcript.txt")) + except nextcord.Forbidden: + pass + + transcript.seek(0) + + if 'log_channel_id' in self.data: + log_channel = self.bot.get_channel(self.data['log_channel_id']) + if log_channel: + await log_channel.send(file=nextcord.File(transcript, filename=f"{thread.name}_transcript.txt")) + + self.data['buttons'] = [button for button in self.data['buttons'] if button['message_id'] != thread.last_message_id] + self.save_config() + + @tickets.error + @setup_ticket.error + @setup_log.error + async def tickets_error(self, ctx, error): + if isinstance(error, commands.MissingPermissions): + await ctx.send("You don't have permission to use this command.") + +def setup(bot): + bot.add_cog(TicketSystem(bot))