Did stuff, test file

This commit is contained in:
Christopher Cookman 2026-01-21 16:22:28 -07:00
parent af8eaa1a02
commit 9aa941a563
5 changed files with 3420 additions and 243 deletions

View file

@ -1,18 +1,83 @@
{
"token": "",
"staff_channel": "",
"database_file": "database.db",
"joinleave-logs": "",
"staff_channel": "",
"staff_msg_autodelete_time": 30,
"ticket_category": "",
"ticket_bot": "",
"ticket_detect_phrase": "Welcome",
"ticket_channel_prefix": "ticket-",
"staff_ping": "@here",
"questions": [
{
"type": 1,
"components": [
{
"type": 4,
"custom_id": "question_1",
"label": "Are you a Satanist?",
"style": 1,
"required": true,
"placeholder": "Yes or No",
"min_length": 2,
"max_length": 10
},
{
"type": 4,
"custom_id": "question_2",
"label": "What does the word 'Satan' mean to you?",
"style": 1,
"required": true,
"placeholder": "Your answer",
"min_length": 5,
"max_length": 100
},
{
"type": 4,
"custom_id": "question_3",
"label": "What about Satanism is appealing to you?",
"style": 1,
"required": true,
"placeholder": "Your answer",
"min_length": 10,
"max_length": 100
},
{
"type": 4,
"custom_id": "question_4",
"label": "What is the purpose of joining this server?",
"style": 1,
"required": true,
"placeholder": "Your answer",
"min_length": 10,
"max_length": 100
},
{
"type": 4,
"custom_id": "question_5",
"label": "Are you here to learn more about non-theistic Satanism?",
"style": 1,
"required": true,
"placeholder": "Yes or No",
"min_length": 2,
"max_length": 10
},
{
"type": 4,
"custom_id": "question_6",
"label": "Are you theistic, or atheistic?",
"style": 1,
"required": true,
"placeholder": "Theistic or Atheistic",
"min_length": 5,
"max_length": 20
}
]
}
]
"auto_accept": {
"threshold": 4,
"remove_roles": [""],
"add_roles": [""],
"welcome_message": "Welcome %user%! Please be sure to read <#> and get some <#>!",
"welcome_channel": ""
},
"ntfyUrl": ""
}
}

BIN
database.db Normal file

Binary file not shown.

2926
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -10,6 +10,7 @@
"license": "ISC",
"dependencies": {
"colors": "^1.4.0",
"discord.js": "^14.14.1"
"discord.js": "^14.14.1",
"sqlite3": "^5.1.7"
}
}

657
test.js Normal file
View file

@ -0,0 +1,657 @@
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);