const config = require("./config.json"); const { Client, REST, Routes, InteractionType, ButtonStyle, MessageFlags, ComponentType, ChannelType } = require("discord.js"); const client = new Client({ intents: ["Guilds", "MessageContent", "GuildMessages"] }); const rest = new REST().setToken(config.token); const sqlite3 = require("sqlite3").verbose(); const db = new sqlite3.Database(config.database_file || "database.db"); db.run(`CREATE TABLE IF NOT EXISTS tickets ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id TEXT NOT NULL, channel_id TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, data BLOB NOT NULL DEFAULT '{}' )`); let ticketCategory; let staffChannel; const genTranscript = async (channel) => { let messages = []; let lastId; while (true) { let fetched = await channel.messages.fetch({ limit: 100, before: lastId }); if (fetched.size === 0) break; messages = messages.concat(Array.from(fetched.values())); lastId = fetched.last().id; } messages = messages.sort((a, b) => a.createdTimestamp - b.createdTimestamp); let transcript = messages.map(m => `[${new Date(m.createdTimestamp).toISOString()}] ${m.author.tag} (${m.author.id}): ${m.content}`).join("\n"); return transcript; } client.once('clientReady', async () => { console.log(`Logged in as ${client.user.displayName}`); try { console.log("Updating commands") await rest.put( Routes.applicationCommands(client.user.id), { body: [ { name: 'postbutton', description: 'Posts a button to the channel', default_member_permissions: "0" // Admin only }, { name: "unbreak", description: "Unbreak a ticket (force delete it from the database, last resort!)", default_member_permissions: "0", // Admin only options: [ { name: "user", description: "The user to unbreak", type: 6, // USER type required: true } ] } ] }, ); console.log("Commands updated") } catch (error) { console.error(error); } // Get some stuff ready await client.guilds.fetch(config.guild_id); await client.guilds.cache.get(config.guild_id).channels.fetch(); ticketCategory = config.ticket_category ? await client.guilds.cache.get(config.guild_id).channels.fetch(config.ticket_category).catch(() => null) : null; staffChannel = config.staff_channel ? await client.guilds.cache.get(config.guild_id).channels.fetch(config.staff_channel).catch(() => null) : null; }) client.on('interactionCreate', async interaction => { switch (interaction.type) { case InteractionType.ApplicationCommand: switch (interaction.commandName) { case 'unbreak': let userId = interaction.options.getUser("user").id; db.get(`SELECT * FROM tickets WHERE user_id = ?`, [userId], async (err, row) => { if (err) { console.error(err); await interaction.reply({ content: "An error occurred while checking the ticket.", flags: [MessageFlags.Ephemeral] }).then(() => { setTimeout(() => { interaction.deleteReply().catch(() => { }); }, (config.staff_msg_autodelete_time || 30) * 1000); }); return; } if (row) { db.run(`DELETE FROM tickets WHERE id = ?`, [row.id], async (err) => { if (err) { console.error(err); await interaction.reply({ content: "An error occurred while deleting the ticket.", flags: [MessageFlags.Ephemeral] }).then(() => { setTimeout(() => { interaction.deleteReply().catch(() => { }); }, (config.staff_msg_autodelete_time || 30) * 1000); }); return; } await interaction.reply({ content: `Successfully unbroke ticket for <@${userId}>.`, flags: [MessageFlags.Ephemeral] }).then(() => { setTimeout(() => { interaction.deleteReply().catch(() => { }); }, (config.staff_msg_autodelete_time || 30) * 1000); }); }); } else { await interaction.reply({ content: `<@${userId}> does not have a broken ticket.`, flags: [MessageFlags.Ephemeral] }).then(() => { setTimeout(() => { interaction.deleteReply().catch(() => { }); }, (config.staff_msg_autodelete_time || 30) * 1000); }); } }); break; case 'postbutton': await interaction.channel.send({ content: config.button_message || "Here is a button!", components: [ { type: 1, components: [ { type: 2, label: config.button_label || "Click Me", style: ButtonStyle.Success, custom_id: "new-ticket" } ] } ] }); await interaction.reply({ content: "Button posted!", flags: [MessageFlags.Ephemeral] }).then(() => { setTimeout(() => { interaction.deleteReply().catch(() => { }); }, (config.staff_msg_autodelete_time || 30) * 1000); }); break; } break; case InteractionType.MessageComponent: let comm = interaction.customId.split('_')[0]; switch (comm) { case 'new-ticket': // Check if user already has a ticket db.get(`SELECT * FROM tickets WHERE user_id = ?`, [interaction.user.id], async (err, row) => { if (err) { console.error(err); await interaction.reply({ content: "An error occurred while checking your tickets.", flags: [MessageFlags.Ephemeral] }).then(() => { setTimeout(() => { interaction.deleteReply().catch(() => { }); }, (config.staff_msg_autodelete_time || 30) * 1000); }); return; } if (row) { // Check if the channel still exists, if not, delete the ticket from the database and tell the user we fixed things and to press the button again let existingChannel = await interaction.guild.channels.fetch(row.channel_id).catch(() => null); if (!existingChannel) { db.run(`DELETE FROM tickets WHERE id = ?`, [row.id], async (err) => { if (err) { console.error(err); await interaction.reply({ content: "An error occurred while checking your tickets.", flags: [MessageFlags.Ephemeral] }).then(() => { setTimeout(() => { interaction.deleteReply().catch(() => { }); }, (config.staff_msg_autodelete_time || 30) * 1000); }); return; } await interaction.reply({ content: "Your previous ticket channel was missing, but we've fixed it. Please press the button again to create a new ticket.", flags: [MessageFlags.Ephemeral] }).then(() => { setTimeout(() => { interaction.deleteReply().catch(() => { }); }, (config.staff_msg_autodelete_time || 30) * 1000); }); }); return; } await interaction.reply({ content: `You already have an open ticket: <#${row.channel_id}>`, flags: [MessageFlags.Ephemeral] }).then(() => { setTimeout(() => { interaction.deleteReply().catch(() => { }); }, (config.staff_msg_autodelete_time || 30) * 1000); }); return; } // ticket id first part of a uuid let ticket_id = Math.random().toString(36).substring(2, 8); // Create ticket channel let channel = await interaction.guild.channels.create({ name: `ticket-${ticket_id}`, type: ChannelType.GuildText, parent: ticketCategory, permissionOverwrites: [ { id: interaction.guild.roles.everyone, deny: ['ViewChannel'] }, { id: interaction.user.id, allow: ['ViewChannel', 'SendMessages', 'ReadMessageHistory'] }, ...((config.staff_roles || []).map(roleId => ({ id: roleId, allow: ['ViewChannel', 'SendMessages', 'ReadMessageHistory'] }))) ] }); // Insert into database db.run(`INSERT INTO tickets (user_id, channel_id, data) VALUES (?, ?, ?)`, [interaction.user.id, channel.id, JSON.stringify({ user_answers: {}, staff_answers: {}, question: 0 })], async (err) => { if (err) { console.error(err); await channel.send("An error occurred while creating your ticket. Please contact staff."); return; } await channel.send({ content: `# ${config.formData.title}\n\n${config.formData.description}\n\n*Please answer the following questions by responding in this channel.*`, components: [ { type: ComponentType.ActionRow, components: [ { type: ComponentType.Button, label: "Close Ticket", style: ButtonStyle.Danger, custom_id: "close-ticket" }, { type: ComponentType.Button, label: "Restart Form", style: ButtonStyle.Secondary, custom_id: "restart-form" } ] } ] }); await channel.send({ content: `(1/${config.formData.questions.length}) ${config.formData.questions[0]}` }); // First question }); await interaction.reply({ content: `Your ticket has been created: <#${channel.id}>`, flags: [MessageFlags.Ephemeral] }).then(() => { setTimeout(() => { interaction.deleteReply().catch(() => { }); }, (config.staff_msg_autodelete_time || 30) * 1000); }); }); break; case 'close-ticket': // Check if interaction is in a ticket channel db.get(`SELECT * FROM tickets WHERE channel_id = ?`, [interaction.channel.id], async (err, row) => { if (err) { console.error(err); await interaction.reply({ content: "An error occurred while checking this ticket.", flags: [MessageFlags.Ephemeral] }).then(() => { setTimeout(() => { interaction.deleteReply().catch(() => { }); }, (config.staff_msg_autodelete_time || 30) * 1000); return; }); } if (row) { // Create transcript and delete channel await interaction.reply({ content: "Closing ticket...", flags: [MessageFlags.Ephemeral] }); // Fetch all messages in channel let messages = []; let lastId; while (true) { let fetched = await interaction.channel.messages.fetch({ limit: 100, before: lastId }); if (fetched.size === 0) break; messages = messages.concat(Array.from(fetched.values())); lastId = fetched.last().id; } messages = messages.sort((a, b) => a.createdTimestamp - b.createdTimestamp); // Generate a txt file transcript let transcript = messages.map(m => `[${new Date(m.createdTimestamp).toISOString()}] ${m.author.tag}: ${m.content}`).join("\n"); // Send transcript to staff channel if (staffChannel) { await staffChannel.send({ content: `Transcript for ticket by <@${row.user_id}>:`, files: [{ attachment: Buffer.from(transcript, 'utf-8'), name: `transcript-${interaction.channel.name}.txt` }] }); } // Delete ticket from database db.run(`DELETE FROM tickets WHERE id = ?`, [row.id], async (err) => { if (err) { console.error(err); } await interaction.channel.delete("Ticket closed"); }); } else { await interaction.reply({ content: "This channel is not a ticket channel.", flags: [MessageFlags.Ephemeral] }).then(() => { setTimeout(() => { interaction.deleteReply().catch(() => { }); }, (config.staff_msg_autodelete_time || 30) * 1000); }); } }); break; case 'restart-form': // Check if interaction is in a ticket channel db.get(`SELECT * FROM tickets WHERE channel_id = ?`, [interaction.channel.id], async (err, row) => { if (err) { console.error(err); await interaction.reply({ content: "An error occurred while checking this ticket.", flags: [MessageFlags.Ephemeral] }).then(() => { setTimeout(() => { interaction.deleteReply().catch(() => { }); }, (config.staff_msg_autodelete_time || 30) * 1000); return; }); } if (row) { // Reset form data let parsed = JSON.parse(row.data); parsed.user_answers = {}; parsed.question = 0; db.run(`UPDATE tickets SET data = ? WHERE id = ?`, [JSON.stringify(parsed), row.id], async (err) => { if (err) { console.error(err); await interaction.reply({ content: "An error occurred while resetting the form.", flags: [MessageFlags.Ephemeral] }).then(() => { setTimeout(() => { interaction.deleteReply().catch(() => { }); }, (config.staff_msg_autodelete_time || 30) * 1000); return; }); } await interaction.reply({ content: "The form has been restarted.", flags: [MessageFlags.Ephemeral] }); await interaction.channel.send({ content: `(1/${config.formData.questions.length}) ${config.formData.questions[0]}`}); }); } else { await interaction.reply({ content: "This channel is not a ticket channel.", flags: [MessageFlags.Ephemeral] }).then(() => { setTimeout(() => { interaction.deleteReply().catch(() => { }); }, (config.staff_msg_autodelete_time || 30) * 1000); }); } }); break; case 'staff': // Handle staff options here // split to get option id, and ticket channel let optionId = interaction.customId.split('_')[2]; let ticketChannel = interaction.customId.split('_')[1] // Check if the ticket is still valid, if not something VERY bad happened. db.get(`SELECT * FROM tickets WHERE channel_id = ?`, [ticketChannel], async (err, row) => { if (err) { console.error(err); await interaction.reply({ content: "An error occurred while processing this action.", flags: [MessageFlags.Ephemeral] }).then(() => { setTimeout(() => { interaction.deleteReply().catch(() => { }); }, (config.staff_msg_autodelete_time || 30) * 1000); return; }); } if (row) { // Valid ticket, process the option // Add the count for staff option picked, check auto actions to see if any need to be performed let ticketData = JSON.parse(row.data); if (!ticketData.staff_answers[optionId]) { ticketData.staff_answers[optionId] = []; // List of staff user IDs that picked the option. } // Find all other options the staff member picked and remove their ID from those lists for (const [otherOptionId, userList] of Object.entries(ticketData.staff_answers)) { ticketData.staff_answers[otherOptionId] = userList.filter(id => id !== interaction.user.id); } // Add staff member to selected option ticketData.staff_answers[optionId].push(interaction.user.id); // Update database db.run(`UPDATE tickets SET data = ? WHERE id = ?`, [JSON.stringify(ticketData), row.id], async (err) => { if (err) { console.error(err); await interaction.reply({ content: "An error occurred while processing this action.", flags: [MessageFlags.Ephemeral] }).then(() => { setTimeout(() => { interaction.deleteReply().catch(() => { }); }, (config.staff_msg_autodelete_time || 30) * 1000); return; }); } try { await interaction.reply({ content: `You have selected the option: ${config.formData.staff_options[optionId].label}`, flags: [MessageFlags.Ephemeral] }).then(() => { setTimeout(() => { interaction.deleteReply().catch(() => { }); }, (config.staff_msg_autodelete_time || 30) * 1000); }); // Auto action stuff (under formData) for (const [id, option] of Object.entries(config.formData.staff_options)) { if (!option.auto_actions) continue; const minTrig = (option.auto_actions && option.auto_actions.minimum_trig) || 1; const votes = ticketData.staff_answers[id] ? ticketData.staff_answers[id].length : 0; const acts = option.auto_actions; if (votes >= minTrig) { if (acts.close_staff_notif) { // Just disable more staff votes (edit the staff message) // Update staff msg with counts let staffMsg = await staffChannel.messages.fetch(ticketData.staff_message_id).catch(() => console.error); if (staffMsg) { // Edit message to update counts let staffResponseCounters = `**Staff Responses:**\n` + Object.entries(config.formData.staff_options).map(([id, option]) => `**${option.label}:** ${ticketData.staff_answers[id] ? ticketData.staff_answers[id].length : 0}`).join("\n"); await staffMsg.edit({ content: `@here New ticket completed by <@${row.user_id}> in <#${row.channel_id}>.\n\n**Responses:**\n${Object.entries(ticketData.user_answers).map(([index, answer]) => `**Q${parseInt(index) + 1}:** ${config.formData.questions[index]}\n**A:** ${answer}`).join("\n\n")}\n\n${staffResponseCounters}\n\n# Staff voting closed` }); await staffMsg.components[0].components.forEach(component => { component.disabled = true; }); await staffMsg.edit({ components: staffMsg.components }); } } if (acts.send_message) { // Send messages places if (acts.send_message.in_ticket && acts.send_message.in_ticket?.enabled) { if (!(acts.kick || acts.ban || acts.close_ticket)) { interaction.guild.channels.cache.get(row.channel_id).send(acts.send_message.in_ticket.message.replaceAll(/%user%/g, `<@${row.user_id}>`)).catch(() => { }); } } if (acts.send_message.to_user && acts.send_message.to_user?.enabled) { interaction.guild.members.fetch(row.user_id).then(member => { member.send(acts.send_message.to_user.message.replaceAll(/%user%/g, `<@${row.user_id}>`)).catch(() => { }); }).catch(() => { }); } if (acts.send_message.external && acts.send_message.external?.enabled && acts.send_message.external?.channel_id) { interaction.guild.channels.fetch(acts.send_message.external.channel_id).then(channel => { channel.send(acts.send_message.external.message.replaceAll(/%user%/g, `<@${row.user_id}>`)).catch(() => { }); }).catch(() => { }); } } if (acts.ban) return interaction.guild.members.fetch(row.user_id).then(async member => { member.ban({ reason: acts.ban_reason || "Banned by staff vote via ticket system." }).catch(() => { }); // Do ticket close processing let transcript = await genTranscript(interaction.guild.channels.cache.get(row.channel_id)); if (staffChannel) { await staffChannel.send({ content: `Transcript for ticket by <@${row.user_id}> (banned):`, files: [{ attachment: Buffer.from(transcript, 'utf-8'), name: `transcript-${interaction.channel.name}.txt` }] }); } // Update staff msg let staffMsg = await staffChannel.messages.fetch(ticketData.staff_message_id).catch(() => console.error); if (staffMsg) { // Edit message to update counts let staffResponseCounters = `**Staff Responses:**\n` + Object.entries(config.formData.staff_options).map(([id, option]) => `**${option.label}:** ${ticketData.staff_answers[id] ? ticketData.staff_answers[id].length : 0}`).join("\n"); await staffMsg.edit({ content: `@here New ticket completed by <@${row.user_id}> in <#${row.channel_id}>.\n\n**Responses:**\n${Object.entries(ticketData.user_answers).map(([index, answer]) => `**Q${parseInt(index) + 1}:** ${config.formData.questions[index]}\n**A:** ${answer}`).join("\n\n")}\n\n${staffResponseCounters}\n\n# Ticket Closed - User Banned` }); await staffMsg.components[0].components.forEach(component => { component.disabled = true; }); await staffMsg.edit({ components: staffMsg.components }); } db.run(`DELETE FROM tickets WHERE id = ?`, [row.id], async (err) => { if (err) { console.error(err); } interaction.guild.channels.cache.get(row.channel_id).delete("Ticket closed - User banned"); }); return; // Exit to prevent other actions that wouldn't work with this! }).catch(() => { }); if (acts.kick) return interaction.guild.members.fetch(row.user_id).then(member => { member.kick(acts.kick_reason || "Kicked by staff vote via ticket system.").catch(() => { }); // Close ticket processing genTranscript(interaction.guild.channels.cache.get(row.channel_id)).then(async transcript => { if (staffChannel) { await staffChannel.send({ content: `Transcript for ticket by <@${row.user_id}> (kicked):`, files: [{ attachment: Buffer.from(transcript, 'utf-8'), name: `transcript-${interaction.channel.name}.txt` }] }); } // Update staff msg let staffMsg = await staffChannel.messages.fetch(ticketData.staff_message_id).catch(() => console.error); if (staffMsg) { // Edit message to update counts let staffResponseCounters = `**Staff Responses:**\n` + Object.entries(config.formData.staff_options).map(([id, option]) => `**${option.label}:** ${ticketData.staff_answers[id] ? ticketData.staff_answers[id].length : 0}`).join("\n"); await staffMsg.edit({ content: `@here New ticket completed by <@${row.user_id}> in <#${row.channel_id}>.\n\n**Responses:**\n${Object.entries(ticketData.user_answers).map(([index, answer]) => `**Q${parseInt(index) + 1}:** ${config.formData.questions[index]}\n**A:** ${answer}`).join("\n\n")}\n\n${staffResponseCounters}\n\n# Ticket Closed - User Kicked` }); await staffMsg.components[0].components.forEach(component => { component.disabled = true; }); await staffMsg.edit({ components: staffMsg.components }); } db.run(`DELETE FROM tickets WHERE id = ?`, [row.id], async (err) => { if (err) { console.error(err); } interaction.guild.channels.cache.get(row.channel_id).delete("Ticket closed - User kicked"); }); }).catch(() => { }); }).catch(() => { }); if (acts.add_roles?.length > 0) { acts.add_roles.forEach(roleId => { console.log("Adding role", roleId, "to user", row.user_id); interaction.guild.members.fetch(row.user_id).then(member => { member.roles.add(roleId).catch(() => { }); }).catch(() => { }); }); } if (acts.remove_roles?.length > 0) { acts.remove_roles.forEach(roleId => { console.log("Removing role", roleId, "from user", row.user_id); interaction.guild.members.fetch(row.user_id).then(member => { member.roles.remove(roleId).catch(() => { }); }).catch(() => { }); }); } if (acts.close_ticket) return genTranscript(interaction.guild.channels.cache.get(row.channel_id)).then(async transcript => { if (staffChannel) { await staffChannel.send({ content: `Transcript for ticket by <@${row.user_id}> (closed by staff vote):`, files: [{ attachment: Buffer.from(transcript, 'utf-8'), name: `transcript-${interaction.channel.name}.txt` }] }); } // Update staff msg let staffMsg = await staffChannel.messages.fetch(ticketData.staff_message_id).catch(() => console.error); if (staffMsg) { // Edit message to update counts let staffResponseCounters = `**Staff Responses:**\n` + Object.entries(config.formData.staff_options).map(([id, option]) => `**${option.label}:** ${ticketData.staff_answers[id] ? ticketData.staff_answers[id].length : 0}`).join("\n"); await staffMsg.edit({ content: `@here New ticket completed by <@${row.user_id}> in <#${row.channel_id}>.\n\n**Responses:**\n${Object.entries(ticketData.user_answers).map(([index, answer]) => `**Q${parseInt(index) + 1}:** ${config.formData.questions[index]}\n**A:** ${answer}`).join("\n\n")}\n\n${staffResponseCounters}\n\n# Ticket Closed` }); await staffMsg.components[0].components.forEach(component => { component.disabled = true; }); await staffMsg.edit({ components: staffMsg.components }); } db.run(`DELETE FROM tickets WHERE id = ?`, [row.id], async (err) => { if (err) { console.error(err); } interaction.guild.channels.cache.get(row.channel_id).delete("Ticket closed by staff vote"); }); }).catch(() => { }); } }; let staffResponseCounters = `**Staff Responses:**\n` + Object.entries(config.formData.staff_options).map(([id, option]) => `**${option.label}:** 0`).join("\n"); let staffMsg = await staffChannel.messages.fetch(ticketData.staff_message_id).catch(() => console.error); if (staffMsg) { // Edit message to update counts staffResponseCounters = `**Staff Responses:**\n` + Object.entries(config.formData.staff_options).map(([id, option]) => `**${option.label}:** ${ticketData.staff_answers[id] ? ticketData.staff_answers[id].length : 0}`).join("\n"); await staffMsg.edit({ content: `@here New ticket completed by <@${row.user_id}> in <#${row.channel_id}>.\n\n**Responses:**\n${Object.entries(ticketData.user_answers).map(([index, answer]) => `**Q${parseInt(index) + 1}:** ${config.formData.questions[index]}\n**A:** ${answer}`).join("\n\n")}\n\n${staffResponseCounters}` }); } } catch (e) { console.error(e); } }); } else { await interaction.reply({ content: "This ticket is no longer valid.", flags: [MessageFlags.Ephemeral] }).then(() => { setTimeout(() => { interaction.deleteReply().catch(() => { }); }, (config.staff_msg_autodelete_time || 30) * 1000); }); } }); break; } break; } return; }); client.on('messageCreate', async message => { if (message.author.bot) return; // Check if message is in a ticket channel db.get(`SELECT * FROM tickets WHERE channel_id = ?`, [message.channel.id], async (err, row) => { if (err) { console.error(err); return; } if (row) { // Verify message author is ticket owner if (message.author.id !== row.user_id) return; // It's a ticket channel let ticketData = JSON.parse(row.data); let questionIndex = ticketData.question || 0; if (questionIndex < config.formData.questions.length) { // Save answer ticketData.user_answers[questionIndex] = message.content; questionIndex++; ticketData.question = questionIndex; // Update database db.run(`UPDATE tickets SET data = ? WHERE id = ?`, [JSON.stringify(ticketData), row.id], async (err) => { if (err) { console.error(err); return; } if (questionIndex < config.formData.questions.length) { // Ask next question await message.channel.send({ content: `(${questionIndex + 1 }/${config.formData.questions.length}) ${config.formData.questions[questionIndex]}` }); } else { // All questions answered await message.channel.send("Thank you for completing the form! A staff member will be with you shortly."); // Generate staff buttons components from config.formData.staff_options let staffButtons = []; for (const [id, option] of Object.entries(config.formData.staff_options)) { staffButtons.push({ type: ComponentType.Button, label: option.label, style: option.style, emoji: option.emoji, custom_id: `staff_${message.channel.id}_${id}` }); } let staffResponseCounters = `**Staff Responses:**\n` + Object.entries(config.formData.staff_options).map(([id, option]) => `**${option.label}:** 0`).join("\n"); let thisTicketMsg = await staffChannel.send({ content: `@here New ticket completed by <@${row.user_id}> in <#${message.channel.id}>.\n\n**Responses:**\n${Object.entries(ticketData.user_answers).map(([index, answer]) => `**Q${parseInt(index) + 1}:** ${config.formData.questions[index]}\n**A:** ${answer}`).join("\n\n")}\n\n${staffResponseCounters}`, components: [ { type: ComponentType.ActionRow, components: staffButtons } ] }); // Store msg id in ticket data for future reference ticketData.staff_message_id = thisTicketMsg.id; db.run(`UPDATE tickets SET data = ? WHERE id = ?`, [JSON.stringify(ticketData), row.id], async (err) => { if (err) { console.error(err); return; } }); } }); } } }); }); client.on('guildMemberRemove', async member => { // Check if member has an open ticket db.get(`SELECT * FROM tickets WHERE user_id = ?`, [member.id], async (err, row) => { if (err) { console.error(err); return; } if (row) { // Generate transcript and delete channel let transcript = await genTranscript(member.guild.channels.cache.get(row.channel_id)); if (staffChannel) { await staffChannel.send({ content: `Transcript for ticket by <@${row.user_id}> (user left):`, files: [{ attachment: Buffer.from(transcript, 'utf-8'), name: `transcript-${member.guild.channels.cache.get(row.channel_id).name}.txt` }] }); } // Delete ticket from database db.run(`DELETE FROM tickets WHERE id = ?`, [row.id], async (err) => { if (err) { console.error(err); } member.guild.channels.cache.get(row.channel_id).delete("Ticket closed - User left the guild"); }); } }); }); client.login(config.token);