import { ButtonInteraction, ChannelType, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, TextChannel, ThreadChannel, Message, } from 'discord.js'; import { prisma } from '../../core/client'; export class TicketManager { async createTicket(interaction: ButtonInteraction, type: string): Promise { const channel = interaction.channel as TextChannel; if (!channel || channel.type !== ChannelType.GuildText) { await interaction.reply({ content: 'Cannot create ticket here.', ephemeral: true }); return; } await prisma.user.upsert({ where: { id: interaction.user.id }, update: {}, create: { id: interaction.user.id }, }); const thread = await channel.threads.create({ name: `${type}-${interaction.user.username}-${Date.now().toString(36)}`, type: ChannelType.PrivateThread, invitable: false, reason: `Ticket created by ${interaction.user.tag}`, }); await thread.members.add(interaction.user.id); await prisma.ticket.create({ data: { threadId: thread.id, userId: interaction.user.id, type, status: 'open', }, }); const embed = new EmbedBuilder() .setColor(0x5865f2) .setTitle(`🎫 ${type.charAt(0).toUpperCase() + type.slice(1)} Ticket`) .setDescription( `Welcome ${interaction.user}!\n\n` + `A staff member will be with you shortly.\n` + `Please describe your request in detail.` ) .setFooter({ text: `Ticket ID: ${thread.id}` }) .setTimestamp(); const row = new ActionRowBuilder().addComponents( new ButtonBuilder() .setCustomId('ticket_invoice') .setLabel('Generate Invoice') .setStyle(ButtonStyle.Primary) .setEmoji('📄'), new ButtonBuilder() .setCustomId('ticket_close') .setLabel('Close Ticket') .setStyle(ButtonStyle.Danger) .setEmoji('🔒') ); await thread.send({ embeds: [embed], components: [row] }); await interaction.reply({ content: `✅ Your ticket has been created: ${thread}`, ephemeral: true, }); console.log(`🎫 Ticket created: ${thread.name} by ${interaction.user.tag}`); } async generateInvoice(interaction: ButtonInteraction): Promise { const invoiceId = `INV-${Date.now().toString(36).toUpperCase()}`; const embed = new EmbedBuilder() .setColor(0x00ff00) .setTitle('📄 Invoice Generated') .setDescription('Here is your mock invoice for the requested service.') .addFields( { name: 'Invoice ID', value: invoiceId, inline: true }, { name: 'Status', value: 'Pending', inline: true }, { name: 'Amount', value: '$XX.XX', inline: true }, { name: 'Service', value: 'Server Rental', inline: false }, ) .setFooter({ text: 'This is a mock invoice for demonstration' }) .setTimestamp(); const row = new ActionRowBuilder().addComponents( new ButtonBuilder() .setCustomId('invoice_pay') .setLabel('Mark as Paid') .setStyle(ButtonStyle.Success) .setEmoji('💳') ); await interaction.reply({ embeds: [embed], components: [row] }); } async closeTicket(interaction: ButtonInteraction): Promise { const thread = interaction.channel as ThreadChannel; if (!thread || thread.type !== ChannelType.PrivateThread) { await interaction.reply({ content: 'This is not a ticket thread.', ephemeral: true }); return; } const ticket = await prisma.ticket.findUnique({ where: { threadId: thread.id }, }); if (!ticket) { await interaction.reply({ content: 'Ticket not found in database.', ephemeral: true }); return; } await interaction.reply({ content: '🔒 Closing ticket and saving transcript...' }); const transcript = await this.generateTranscript(thread); await prisma.ticket.update({ where: { threadId: thread.id }, data: { status: 'closed', transcript, closedAt: new Date(), }, }); await thread.setLocked(true, 'Ticket closed'); await thread.setArchived(true, 'Ticket closed'); console.log(`🎫 Ticket closed: ${thread.name}`); } private async generateTranscript(thread: ThreadChannel): Promise { const messages: Message[] = []; let lastId: string | undefined; while (true) { const batch = await thread.messages.fetch({ limit: 100, before: lastId, }); if (batch.size === 0) break; messages.push(...batch.values()); lastId = batch.last()?.id; } messages.reverse(); const transcript = messages .map(m => `[${m.createdAt.toISOString()}] ${m.author.tag}: ${m.content}`) .join('\n'); return transcript; } async createSupportPanel(channel: TextChannel): Promise { const embed = new EmbedBuilder() .setColor(0x5865f2) .setTitle('🎫 Support Center') .setDescription( 'Need help? Click a button below to open a support ticket.\n\n' + '**Available Services:**\n' + '• Server Rental - Rent a game server\n' + '• Technical Support - Get help with issues\n' + '• Billing - Payment and invoice questions' ) .setFooter({ text: 'Aethex Sentinel Support System' }); const row = new ActionRowBuilder().addComponents( new ButtonBuilder() .setCustomId('ticket_rental') .setLabel('Rent Server') .setStyle(ButtonStyle.Primary) .setEmoji('🖥️'), new ButtonBuilder() .setCustomId('ticket_support') .setLabel('Technical Support') .setStyle(ButtonStyle.Secondary) .setEmoji('🔧'), new ButtonBuilder() .setCustomId('ticket_billing') .setLabel('Billing') .setStyle(ButtonStyle.Secondary) .setEmoji('💰') ); await channel.send({ embeds: [embed], components: [row] }); } } export const ticketManager = new TicketManager();