657 lines
30 KiB
JavaScript
657 lines
30 KiB
JavaScript
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); |