Initializes the Aethex Sentinel bot project, including package.json, Prisma schema, core client, configuration, health server, and event listeners for audit logs and member updates. Implements commands for federation management, sentinel security status, and ticket system. Replit-Commit-Author: Agent Replit-Commit-Session-Id: e72fc1b7-94bd-4d6c-801f-cbac2fae245c Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: fc082e4d-cb52-4049-9c2e-69ba6c2e78d4 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3bdfff67-975a-46ad-9845-fbb6b4a4c4b5/e72fc1b7-94bd-4d6c-801f-cbac2fae245c/jW8PJKQ Replit-Helium-Checkpoint-Created: true
202 lines
6 KiB
TypeScript
202 lines
6 KiB
TypeScript
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<void> {
|
|
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<ButtonBuilder>().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<void> {
|
|
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<ButtonBuilder>().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<void> {
|
|
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<string> {
|
|
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<void> {
|
|
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<ButtonBuilder>().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();
|