const fs = require('node:fs'); const path = require('node:path'); const { Client, Collection, Events, GatewayIntentBits, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, PermissionsBitField } = require('discord.js'); const { token, winterToken } = require('./config.json'); const deployCommands = require('./deploy-commands.js'); const winterDeployCommands = require('./deploy-commands-winter.js'); const { startAPI } = require('./api/handler.js'); const STAFF_ROLE_ID = '1361903672526897379'; // Replace this with your actual staff role ID const clientBot1 = new Client({ intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent ] }); const clientBot2 = new Client({ intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent ] }); clientBot1.commands = new Collection(); clientBot1.cooldowns = new Collection(); clientBot2.commands = new Collection(); clientBot2.cooldowns = new Collection(); async function loadCommands(client, folders, botType) { for (const folderName of folders) { const commandsPath = path.join(__dirname, folderName); if (fs.existsSync(commandsPath)) { const commandFolders = fs.readdirSync(commandsPath); for (const folder of commandFolders) { const folderPath = path.join(commandsPath, folder); const files = fs.readdirSync(folderPath).filter(file => file.endsWith('.js')); for (const file of files) { const filePath = path.join(folderPath, file); const command = require(filePath); if ('data' in command && 'execute' in command) { client.commands.set(command.data.name, command); console.log(`[LAUNCH] Loaded '${command.data.name}' for ${botType}`); } else { console.warn(`[WARNING] Missing "data" or "execute" in ${filePath}`); } } } } else { console.warn(`[WARNING] Command folder not found: ${commandsPath}`); } } } loadCommands(clientBot1, ['commands'], 'pepe'); loadCommands(clientBot2, ['winter-commands'], 'winter'); const eventsPath = path.join(__dirname, 'events'); const eventFiles = fs.readdirSync(eventsPath).filter(file => file.endsWith('.js')); for (const file of eventFiles) { const filePath = path.join(eventsPath, file); const event = require(filePath); if (event.once) { clientBot1.once(event.name, (...args) => event.execute(...args)); clientBot2.once(event.name, (...args) => event.execute(...args)); } else { clientBot1.on(event.name, (...args) => event.execute(...args)); clientBot2.on(event.name, (...args) => event.execute(...args)); } } async function saveTranscript(channel) { const messages = await channel.messages.fetch({ limit: 100 }); const sorted = messages.sort((a, b) => a.createdTimestamp - b.createdTimestamp); const transcript = sorted.map(m => `${m.author.tag}: ${m.content}`).join('\n'); return transcript || 'No messages found.'; } async function sendTranscript(channel, transcript, user) { const logChannel = channel.guild.channels.cache.find(c => c.name === 'ticket-transcripts'); if (!logChannel) return; const embed = new EmbedBuilder() .setTitle(`Ticket Transcript`) .setDescription('Transcript of recently closed ticket') .addFields({ name: 'User', value: user.tag }, { name: 'Channel', value: channel.name }) .setColor(0x5865F2) .setTimestamp(); await logChannel.send({ embeds: [embed], files: [{ attachment: Buffer.from(transcript), name: `${channel.name}-transcript.txt` }] }); await user.send({ content: '📩 Here is your ticket transcript:', files: [{ attachment: Buffer.from(transcript), name: `${channel.name}-transcript.txt` }] }).catch(() => {}); } async function handleInteraction(interaction, client) { if (!interaction.isChatInputCommand()) return; const commandName = interaction.commandName; const command = client.commands.get(commandName); if (command) { console.log(`[RUN] Detected command '${commandName}' run by ${interaction.user.tag} on bot ${client === clientBot1 ? 'pepe' : 'winter'}`); // Log when command is run } if (!command) { console.error(`No command matching ${commandName} was found for bot ${client === clientBot1 ? 'pepe' : 'winter'}.`); return; } const { cooldowns } = client; if (!cooldowns.has(command.data.name)) { cooldowns.set(command.data.name, new Collection()); } const now = Date.now(); const timestamps = cooldowns.get(command.data.name); const defaultCooldownDuration = 3; const cooldownAmount = (command.cooldown ?? defaultCooldownDuration) * 1000; if (timestamps.has(interaction.user.id)) { const expirationTime = timestamps.get(interaction.user.id) + cooldownAmount; if (now < expirationTime) { const expiredTimestamp = Math.round(expirationTime / 1000); return interaction.reply({ content: `Please wait, you are on a cooldown for \`${command.data.name}\` on bot ${client === clientBot1 ? 'pepe' : 'winter'}. You can use it again .`, ephemeral: true }); } } timestamps.set(interaction.user.id, now); setTimeout(() => timestamps.delete(interaction.user.id), cooldownAmount); try { await command.execute(interaction); } catch (error) { console.error(`Error executing command ${commandName} for bot ${client === clientBot1 ? 'pepe' : 'winter'}:`, error); if (interaction.replied || interaction.deferred) { await interaction.followUp({ content: 'There was an error while executing this command!', ephemeral: true }); } else { await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true }); } } if (interaction.isButton()) { // Defer the update immediately to prevent "unknown interaction" error if (!interaction.deferred) await interaction.deferUpdate(); const { customId, member, channel } = interaction; if (customId === 'claim_ticket') { if (!member.roles.cache.has(STAFF_ROLE_ID)) { if (!interaction.replied) { return interaction.reply({ content: '❌ You do not have permission.', ephemeral: true }); } } await channel.permissionOverwrites.edit(member.id, { ViewChannel: true, SendMessages: true, ReadMessageHistory: true }); if (!interaction.replied) { return interaction.reply({ content: `✅ Ticket claimed by <@${member.id}>.` }); } } if (customId === 'close_ticket') { if (!interaction.replied) { return interaction.reply({ content: '⚠️ Are you sure you want to close this ticket?', components: [ new ActionRowBuilder().addComponents( new ButtonBuilder() .setCustomId('confirm_close_ticket') .setLabel('Yes, close it') .setStyle(ButtonStyle.Danger), new ButtonBuilder() .setCustomId('cancel_close_ticket') .setLabel('Cancel') .setStyle(ButtonStyle.Secondary) ) ] }); } } if (customId === 'confirm_close_ticket') { const targetUserId = channel.permissionOverwrites.cache.find(p => p.allow.has(PermissionsBitField.Flags.ViewChannel) && p.type === 1)?.id; const targetUser = targetUserId ? await client.users.fetch(targetUserId).catch(() => null) : null; const transcript = await saveTranscript(channel); await sendTranscript(channel, transcript, targetUser || interaction.user); const newName = `closed-${channel.name}`; await channel.setName(newName); const permsToRemove = channel.permissionOverwrites.cache.filter(po => po.id !== client.user.id && po.id !== STAFF_ROLE_ID ); for (const perm of permsToRemove.values()) { await channel.permissionOverwrites.delete(perm.id).catch(() => {}); } // Save the closure message ID and ticket creator in the topic const closureMessage = await channel.send({ content: `Ticket closed by <@${interaction.user.id}>.` }); await channel.setTopic(`${targetUserId},${closureMessage.id}`); // Store creator ID and closure message ID in the topic // Check if the interaction has already been replied to if (!interaction.replied) { await interaction.update({ content: `🔒 Ticket closed by <@${interaction.user.id}>.`, components: [ new ActionRowBuilder().addComponents( new ButtonBuilder() .setCustomId('reopen_ticket') .setLabel('🔓 Reopen Ticket') .setStyle(ButtonStyle.Success), new ButtonBuilder() .setCustomId('delete_ticket') .setLabel('🗑️ Delete Ticket') .setStyle(ButtonStyle.Danger) ) ] }); } } if (customId === 'cancel_close_ticket') { if (!interaction.replied) { await interaction.update({ content: '❎ Ticket close cancelled.', components: [] }); } } if (customId === 'reopen_ticket') { if (!member.roles.cache.has(STAFF_ROLE_ID)) { if (!interaction.replied) { return interaction.reply({ content: '❌ You do not have permission to reopen this ticket.', ephemeral: true }); } } const originalName = channel.name.replace(/^closed-/, ''); // Retrieve the ticket creator ID and closure message ID from the channel topic const [ticketCreatorId, closureMessageId] = channel.topic?.split(',') || []; if (!ticketCreatorId || !closureMessageId) { if (!interaction.replied) { return interaction.reply({ content: '❌ Ticket creator or closure message missing.', ephemeral: true }); } } // Fetch the closure message and delete it let closureMessage; try { closureMessage = await channel.messages.fetch(closureMessageId); await closureMessage.delete(); // Delete the closure message } catch (err) { console.error('Error deleting closure message:', err); } await channel.setName(originalName); await channel.permissionOverwrites.edit(ticketCreatorId, { ViewChannel: true, SendMessages: true, ReadMessageHistory: true }).catch(() => {}); if (!interaction.replied) { await interaction.update({ content: '✅ Ticket reopened.', components: [] }); } } if (customId === 'delete_ticket') { if (!member.roles.cache.has(STAFF_ROLE_ID)) { if (!interaction.replied) { return interaction.reply({ content: '❌ You do not have permission to delete this ticket.', ephemeral: true }); } } try { await channel.delete(); if (!interaction.replied) { await interaction.followUp({ content: '✅ Ticket deleted successfully.' }); } } catch (err) { console.error('Error deleting ticket:', err); if (!interaction.replied) { await interaction.followUp({ content: '❌ There was an error deleting the ticket. Please try again later.' }); } } } if (customId === 'cancel_delete_ticket') { if (!interaction.replied) { await interaction.update({ content: '❎ Ticket deletion cancelled.', components: [] }); } } } } clientBot1.login(token); clientBot2.login(winterToken); process.on('unhandledRejection', error => { console.error('Unhandled promise rejection:', error); }); startAPI();