Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3fa8b6d72 | ||
|
|
5c6a9f7306 | ||
|
|
71e33dbf66 | ||
|
|
d468ab1575 | ||
|
|
74482af471 | ||
|
|
0b8b092cb9 | ||
|
|
3cdfaef942 | ||
|
|
fcf441a190 | ||
|
|
5a1c4ad99c | ||
|
|
d9cb72f22e | ||
|
|
58eb37124e | ||
|
|
afeeda3a8d | ||
|
|
fece90d2bd | ||
|
|
357a85fa3d | ||
|
|
f34158caa6 | ||
|
|
1bb343e49a | ||
|
|
dffa5d4161 | ||
|
|
13590b08a8 | ||
|
|
6e14ce209d | ||
|
|
72b8c2247e | ||
|
|
925504a928 | ||
|
|
236d67707f |
No files matched your search
+327
-205
@@ -17,7 +17,7 @@ module.exports = new Command({
|
|||||||
name: "reason",
|
name: "reason",
|
||||||
description: "Ticket Close Reason",
|
description: "Ticket Close Reason",
|
||||||
type: Discord.ApplicationCommandOptionType.String,
|
type: Discord.ApplicationCommandOptionType.String,
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
permissions: cmdconfig.CloseTicket.Permission,
|
permissions: cmdconfig.CloseTicket.Permission,
|
||||||
|
|
||||||
@@ -28,21 +28,23 @@ module.exports = new Command({
|
|||||||
let SupportStaff = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild);
|
let SupportStaff = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild);
|
||||||
let Admin = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild);
|
let Admin = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild);
|
||||||
|
|
||||||
if (!SupportStaff || !Admin)
|
if (!SupportStaff || !Admin) {
|
||||||
return interaction.reply("Some roles seem to be missing!\nPlease check for errors when starting the bot.");
|
return interaction.reply("Some roles seem to be missing! Please check for errors when starting the bot.");
|
||||||
|
}
|
||||||
|
|
||||||
const NoPerms = new Discord.EmbedBuilder()
|
const NoPerms = new Discord.EmbedBuilder()
|
||||||
.setTitle("Invalid Permissions!")
|
.setTitle("Invalid Permissions!")
|
||||||
.setDescription(`${msgconfig.Error.IncorrectPerms}\n\nRole Required: \`${supportbot.Roles.StaffMember.Staff}\` or \`${supportbot.Roles.StaffMember.Admin}\``)
|
.setDescription(`${msgconfig.Error.IncorrectPerms}\n\nRole Required: \`${supportbot.Roles.StaffMember.Staff}\` or \`${supportbot.Roles.StaffMember.Admin}\``)
|
||||||
.setColor(supportbot.Embed.Colours.Warn);
|
.setColor(supportbot.Embed.Colours.Warn);
|
||||||
|
|
||||||
if (!interaction.member.roles.cache.has(SupportStaff.id) && !interaction.member.roles.cache.has(Admin.id))
|
if (!interaction.member.roles.cache.has(SupportStaff.id) && !interaction.member.roles.cache.has(Admin.id)) {
|
||||||
return interaction.reply({ embeds: [NoPerms] });
|
return interaction.reply({ embeds: [NoPerms] });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if it's a thread or channel based on config
|
const isThread = interaction.channel.type === Discord.ChannelType.PrivateThread;
|
||||||
if (
|
if (
|
||||||
(supportbot.Ticket.TicketType === "threads" && interaction.channel.type !== Discord.ChannelType.PrivateThread) ||
|
(supportbot.Ticket.TicketType === "threads" && !isThread) ||
|
||||||
(supportbot.Ticket.TicketType === "channels" && interaction.channel.type !== Discord.ChannelType.GuildText)
|
(supportbot.Ticket.TicketType === "channels" && interaction.channel.type !== Discord.ChannelType.GuildText)
|
||||||
) {
|
) {
|
||||||
const NotTicketChannel = new Discord.EmbedBuilder()
|
const NotTicketChannel = new Discord.EmbedBuilder()
|
||||||
@@ -55,7 +57,14 @@ module.exports = new Command({
|
|||||||
|
|
||||||
await interaction.deferReply();
|
await interaction.deferReply();
|
||||||
|
|
||||||
let tickets = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8"));
|
let tickets;
|
||||||
|
try {
|
||||||
|
tickets = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8"));
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error reading ticket data file:", err);
|
||||||
|
return interaction.followUp({ content: "There was an error loading ticket data." });
|
||||||
|
}
|
||||||
|
|
||||||
let TicketData = tickets.tickets.findIndex((t) => t.id === interaction.channel.id);
|
let TicketData = tickets.tickets.findIndex((t) => t.id === interaction.channel.id);
|
||||||
let ticket = tickets.tickets[TicketData];
|
let ticket = tickets.tickets[TicketData];
|
||||||
|
|
||||||
@@ -68,237 +77,350 @@ module.exports = new Command({
|
|||||||
}
|
}
|
||||||
|
|
||||||
let reason = interaction.options?.getString("reason") || "No Reason Provided.";
|
let reason = interaction.options?.getString("reason") || "No Reason Provided.";
|
||||||
|
const ticketUserId = ticket.user;
|
||||||
|
|
||||||
const CloseButton = new Discord.ButtonBuilder()
|
if (supportbot.Ticket.ReviewSystem.Enabled) {
|
||||||
.setCustomId("confirmCloseTicket")
|
const rating = await collectReviewRating(interaction, ticketUserId);
|
||||||
.setLabel(supportbot.Ticket.Close.Confirmation_Button)
|
const comment = await collectReviewComment(interaction, ticketUserId);
|
||||||
.setEmoji(supportbot.Ticket.Close.Confirmation_Emoji)
|
|
||||||
.setStyle(supportbot.Ticket.Close.Confirmation_Style);
|
|
||||||
|
|
||||||
const row = new Discord.ActionRowBuilder().addComponents(CloseButton);
|
const reviewChannel = await getChannel(supportbot.Ticket.ReviewSystem.Channel, interaction.guild);
|
||||||
|
const reviewer = supportbot.Ticket.ClaimTickets ? `<@${ticket.claimedBy}>` : null;
|
||||||
|
|
||||||
const CloseTicketRequest = new Discord.EmbedBuilder()
|
const reviewEmbed = new Discord.EmbedBuilder()
|
||||||
.setTitle(`**${supportbot.Ticket.Close.Title}**`)
|
.setTitle(msgconfig.ReviewSystem.ReviewEmbed.Title)
|
||||||
.setDescription(`${msgconfig.Ticket.ConfirmClose}`)
|
.addFields(
|
||||||
.setColor(supportbot.Embed.Colours.General);
|
{
|
||||||
|
name: msgconfig.ReviewSystem.ReviewEmbed.RatingTitle,
|
||||||
|
value: `${msgconfig.ReviewSystem.ReviewEmbed.ReviewEmoji.repeat(rating)}`,
|
||||||
|
inline: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: msgconfig.ReviewSystem.ReviewEmbed.CommentTitle,
|
||||||
|
value: `\`\`\`${comment}\`\`\``,
|
||||||
|
inline: false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.setColor(msgconfig.ReviewSystem.ReviewEmbed.Color);
|
||||||
|
|
||||||
await interaction.followUp({
|
if (supportbot.Ticket.ClaimTickets.Enabled) {
|
||||||
embeds: [CloseTicketRequest],
|
reviewEmbed.setDescription(
|
||||||
components: [row],
|
`**${msgconfig.ReviewSystem.ReviewEmbed.ReviewedStaffTitle}** ${reviewer}\n**${msgconfig.ReviewSystem.ReviewEmbed.ReviewedByTitle}** <@${ticketUserId}>` || `N/A`
|
||||||
});
|
);
|
||||||
|
} else {
|
||||||
|
reviewEmbed.setDescription(
|
||||||
|
`**${msgconfig.ReviewSystem.ReviewEmbed.ReviewedByTitle}** <@${ticketUserId}>`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const filter = (btnInteraction) => btnInteraction.customId === "confirmCloseTicket" && btnInteraction.user.id === interaction.user.id;
|
if (reviewChannel) {
|
||||||
|
await reviewChannel.send({ embeds: [reviewEmbed] });
|
||||||
|
}
|
||||||
|
|
||||||
interaction.channel.awaitMessageComponent({ filter, time: 20000 })
|
if (!interaction.replied && !interaction.deferred) {
|
||||||
.then(async (btnInteraction) => {
|
await interaction.followUp({ embeds: [reviewEmbed] });
|
||||||
await handleCloseTicket(interaction, reason, ticket, TicketData);
|
}
|
||||||
await btnInteraction.reply({ content: "Ticket successfully closed.", ephemeral: true });
|
|
||||||
})
|
await handleCloseTicket(interaction, reason, ticket, TicketData);
|
||||||
.catch((err) => {
|
} else {
|
||||||
interaction.followUp(`${msgconfig.Ticket.CloseTimeout}`);
|
await handleCloseTicket(interaction, reason, ticket, TicketData);
|
||||||
console.error(err);
|
}
|
||||||
});
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function collectReviewRating(interaction, ticketUserId) {
|
||||||
|
const reviewPrompt = new Discord.EmbedBuilder()
|
||||||
|
.setTitle(msgconfig.ReviewSystem.Rate.Title)
|
||||||
|
.setDescription(`${msgconfig.ReviewSystem.Rate.Description}`)
|
||||||
|
.setColor(supportbot.Embed.Colours.General);
|
||||||
|
|
||||||
|
const stars = new Discord.ActionRowBuilder().addComponents(
|
||||||
|
new Discord.StringSelectMenuBuilder()
|
||||||
|
.setCustomId("starRating")
|
||||||
|
.setPlaceholder("Select a rating")
|
||||||
|
.addOptions([
|
||||||
|
{ label: msgconfig.ReviewSystem.Stars.One, value: "1" },
|
||||||
|
{ label: msgconfig.ReviewSystem.Stars.Two, value: "2" },
|
||||||
|
{ label: msgconfig.ReviewSystem.Stars.Three, value: "3" },
|
||||||
|
{ label: msgconfig.ReviewSystem.Stars.Four, value: "4" },
|
||||||
|
{ label: msgconfig.ReviewSystem.Stars.Five, value: "5" },
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
await interaction.followUp({
|
||||||
|
content: `<@${ticketUserId}>`,
|
||||||
|
embeds: [reviewPrompt],
|
||||||
|
components: [stars],
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const starRating = await interaction.channel.awaitMessageComponent({
|
||||||
|
componentType: Discord.ComponentType.StringSelect,
|
||||||
|
filter: (i) => i.customId === "starRating" && i.user.id === ticketUserId,
|
||||||
|
});
|
||||||
|
await starRating.deferUpdate();
|
||||||
|
return starRating.values[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function collectReviewComment(interaction, ticketUserId) {
|
||||||
|
const commentPrompt = new Discord.EmbedBuilder()
|
||||||
|
.setTitle(msgconfig.ReviewSystem.Comment.Title)
|
||||||
|
.setDescription(`${msgconfig.ReviewSystem.Comment.Description}`)
|
||||||
|
.setColor(supportbot.Embed.Colours.General);
|
||||||
|
|
||||||
|
await interaction.followUp({
|
||||||
|
content: `<@${ticketUserId}>`,
|
||||||
|
embeds: [commentPrompt],
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const filter = (response) => response.author.id === ticketUserId;
|
||||||
|
|
||||||
|
const commentCollection = await interaction.channel.awaitMessages({ filter, max: 1 });
|
||||||
|
const comment = commentCollection.first()?.content;
|
||||||
|
return comment && comment.toLowerCase() !== "no" ? comment : "No Comment Provided";
|
||||||
|
}
|
||||||
async function handleCloseTicket(interaction, reason, ticket, TicketData) {
|
async function handleCloseTicket(interaction, reason, ticket, TicketData) {
|
||||||
const { getChannel } = interaction.client;
|
const { getChannel } = interaction.client;
|
||||||
|
|
||||||
let tickets = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8"));
|
let tickets;
|
||||||
|
try {
|
||||||
|
tickets = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8"));
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error reading ticket data:", err);
|
||||||
|
return interaction.followUp("Failed to load ticket data.");
|
||||||
|
}
|
||||||
|
|
||||||
let tUser = interaction.client.users.cache.get(ticket.user);
|
let tUser = interaction.client.users.cache.get(ticket.user);
|
||||||
let transcriptChannel = await getChannel(supportbot.Ticket.Log.TicketDataLog, interaction.guild);
|
let transcriptChannel = await getChannel(supportbot.Ticket.Log.TicketDataLog, interaction.guild);
|
||||||
|
|
||||||
if (!transcriptChannel)
|
if (!transcriptChannel) {
|
||||||
return interaction.followUp("Some Channels seem to be missing!");
|
console.log("Transcript channel missing or inaccessible");
|
||||||
|
return interaction.followUp("Error: Transcript log channel is missing or bot lacks permission.");
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
tickets.tickets[TicketData].open = false;
|
// Fetch all messages from the channel
|
||||||
fs.writeFileSync(
|
let allMessages = [];
|
||||||
"./Data/TicketData.json",
|
let lastId = null;
|
||||||
JSON.stringify(tickets, null, 4),
|
|
||||||
(err) => {
|
while (true) {
|
||||||
if (err) console.error(err);
|
const options = { limit: 100 };
|
||||||
|
if (lastId) {
|
||||||
|
options.before = lastId;
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
const messages = await interaction.channel.messages.fetch(options);
|
||||||
|
if (messages.size === 0) break;
|
||||||
|
|
||||||
|
allMessages = [...allMessages, ...messages.values()];
|
||||||
|
lastId = messages.last().id;
|
||||||
|
|
||||||
|
if (messages.size < 100) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort messages by timestamp
|
||||||
|
allMessages.sort((a, b) => a.createdTimestamp - b.createdTimestamp);
|
||||||
|
|
||||||
|
// Create transcript data
|
||||||
|
const transcriptData = allMessages.map(msg => ({
|
||||||
|
content: msg.content || "No content",
|
||||||
|
username: msg.author.username,
|
||||||
|
userId: msg.author.id,
|
||||||
|
avatar: msg.author.displayAvatarURL(),
|
||||||
|
timestamp: msg.createdAt.toISOString(),
|
||||||
|
attachments: Array.from(msg.attachments.values()).map(att => ({
|
||||||
|
url: att.url,
|
||||||
|
name: att.name
|
||||||
|
})),
|
||||||
|
embeds: msg.embeds.map(embed => ({
|
||||||
|
title: embed.title,
|
||||||
|
description: embed.description,
|
||||||
|
fields: embed.fields
|
||||||
|
}))
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Update ticket status
|
||||||
|
tickets.tickets[TicketData].open = false;
|
||||||
|
tickets.tickets[TicketData].messages = transcriptData;
|
||||||
|
fs.writeFileSync("./Data/TicketData.json", JSON.stringify(tickets, null, 4));
|
||||||
|
|
||||||
const transcriptEmbed = new Discord.EmbedBuilder()
|
const transcriptEmbed = new Discord.EmbedBuilder()
|
||||||
.setTitle(msgconfig.TicketLog.Title)
|
.setTitle(msgconfig.TicketLog.Title)
|
||||||
.setColor(msgconfig.TicketLog.Colour)
|
.setColor(msgconfig.TicketLog.Colour)
|
||||||
.setFooter({
|
.setFooter({ text: supportbot.Embed.Footer, iconURL: interaction.user.displayAvatarURL() })
|
||||||
text: supportbot.Embed.Footer,
|
.setDescription(
|
||||||
iconURL: interaction.user.displayAvatarURL(),
|
`> **Ticket:** ${interaction.channel.name} (\`${interaction.channel.id}\`)\n` +
|
||||||
})
|
`> **User:** ${tUser?.tag || "Unknown User"} (\`${tUser?.id || ticket.user}\`)\n` +
|
||||||
.addFields(
|
`> **Closed by:** <@${interaction.user.id}>\n` +
|
||||||
{ name: "Ticket", value: `${interaction.channel.name} (${interaction.channel.id})` },
|
`> **Message Count:** ${transcriptData.length}`
|
||||||
{ name: "User", value: `${tUser?.username || "N/A"}#${tUser?.discriminator || "N/A"} (${tUser?.id || ticket.user})` },
|
)
|
||||||
{ name: "Closed By", value: interaction.user.tag },
|
.addFields({ name: "Reason", value: `\`\`\`${reason}\`\`\``, inline: true });
|
||||||
{ name: "Reason", value: reason }
|
|
||||||
);
|
|
||||||
|
|
||||||
let msgs = await interaction.channel.messages.fetch({ limit: 100 });
|
const html = createTranscriptHTML(
|
||||||
let html = `
|
{
|
||||||
<html>
|
id: interaction.channel.id,
|
||||||
<head>
|
messages: transcriptData,
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
|
name: interaction.channel.name
|
||||||
<style>
|
},
|
||||||
body {
|
reason
|
||||||
background-color: #1a1a1a;
|
|
||||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
||||||
color: white;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
background-color: rgba(0, 128, 0, 0.3);
|
|
||||||
border-radius: 15px;
|
|
||||||
padding: 20px;
|
|
||||||
margin: 20px;
|
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
|
||||||
flex-grow: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
max-width: 1000px;
|
|
||||||
width: 100%;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
.message {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
padding: 10px;
|
|
||||||
background-color: rgba(255, 255, 255, 0.1);
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
.avatar {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border-radius: 50%;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
.username {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.timestamp {
|
|
||||||
font-size: 0.8em;
|
|
||||||
color: #bbb;
|
|
||||||
}
|
|
||||||
.download-button {
|
|
||||||
background-color: #4CAF50;
|
|
||||||
color: white;
|
|
||||||
padding: 10px 20px;
|
|
||||||
text-align: center;
|
|
||||||
text-decoration: none;
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 16px;
|
|
||||||
margin-top: 20px;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<h1 class="text-2xl font-bold mb-4">Ticket Transcript</h1>
|
|
||||||
<p><strong>Server Name:</strong> ${interaction.guild.name}</p>
|
|
||||||
<p><strong>Ticket:</strong> ${interaction.channel.name}</p>
|
|
||||||
<p><strong>Messages:</strong> ${msgs.size} Messages</p>
|
|
||||||
<div class="mt-4">
|
|
||||||
`;
|
|
||||||
|
|
||||||
msgs = msgs.sort((a, b) => a.createdTimestamp - b.createdTimestamp);
|
|
||||||
msgs.forEach((msg) => {
|
|
||||||
if (msg.content) {
|
|
||||||
html += `
|
|
||||||
<div class="message">
|
|
||||||
<img class="avatar" src="${msg.author.displayAvatarURL()}" alt="Avatar">
|
|
||||||
<div class="content">
|
|
||||||
<p class="username">${msg.author.tag}</p>
|
|
||||||
<p class="timestamp">${msg.createdAt.toLocaleString()}</p>
|
|
||||||
<p>${convertMarkdownToHTML(msg.content)}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const fileName = `${interaction.channel.name}.html`;
|
|
||||||
|
|
||||||
html += `
|
|
||||||
</div>
|
|
||||||
<a href="${fileName}" download class="download-button">Download Transcript</a>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
`;
|
|
||||||
|
|
||||||
let file = new Discord.AttachmentBuilder(
|
|
||||||
Buffer.from(html),
|
|
||||||
{ name: fileName }
|
|
||||||
);
|
);
|
||||||
|
|
||||||
await transcriptChannel
|
const fileName = `${interaction.channel.id}-transcript.html`;
|
||||||
.send({ embeds: [transcriptEmbed], files: [file] })
|
fs.writeFileSync(`./Data/Transcripts/${fileName}`, html);
|
||||||
.catch((err) => {
|
|
||||||
console.error(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (supportbot.Ticket.DMTranscripts) {
|
await transcriptChannel.send({
|
||||||
await tUser
|
embeds: [transcriptEmbed],
|
||||||
?.send({ embeds: [transcriptEmbed], files: [file] })
|
files: [{
|
||||||
.catch((err) => {
|
attachment: `./Data/Transcripts/${fileName}`,
|
||||||
console.error(err);
|
name: `SPOILER_${fileName}`,
|
||||||
});
|
spoiler: true
|
||||||
|
}]
|
||||||
if (ticket.subUsers) {
|
|
||||||
ticket.subUsers.forEach(async (subUser) => {
|
|
||||||
await interaction.client.users.cache
|
|
||||||
.get(subUser)
|
|
||||||
?.send({ embeds: [transcriptEmbed], files: [file] });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await interaction.channel.delete().catch((error) => {
|
|
||||||
console.error(error);
|
|
||||||
if (error.code !== Discord.Constants.APIErrors.UNKNOWN_CHANNEL) {
|
|
||||||
console.error("Failed to delete the interaction:", error);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
if (interaction.channel.type === Discord.ChannelType.GuildText ||
|
||||||
|
interaction.channel.type === Discord.ChannelType.PrivateThread) {
|
||||||
|
await interaction.channel.delete();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error handling ticket close:", err);
|
||||||
|
interaction.followUp("An error occurred while closing the ticket.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertMarkdownToHTML(text) {
|
function parseMarkdown(content) {
|
||||||
// Convert **bold** to <strong>bold</strong>
|
// Convert **bold** text to <strong> tags
|
||||||
text = text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
|
content = content.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
|
||||||
|
|
||||||
// Convert *italic* to <em>italic</em>
|
// Convert _italic_ text to <em> tags
|
||||||
text = text.replace(/\*(.*?)\*/g, '<em>$1</em>');
|
content = content.replace(/_(.*?)_/g, '<em>$1</em>');
|
||||||
|
|
||||||
// Convert __underline__ to <u>underline</u>
|
// Convert inline `code` to <code> tags
|
||||||
text = text.replace(/__(.*?)__/g, '<u>$1</u>');
|
content = content.replace(/`(.*?)`/g, '<code>$1</code>');
|
||||||
|
|
||||||
// Convert ~~strikethrough~~ to <s>strikethrough</s>
|
// Convert [text](link) to <a> tags
|
||||||
text = text.replace(/~~(.*?)~~/g, '<s>$1</s>');
|
content = content.replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2" target="_blank">$1</a>');
|
||||||
|
|
||||||
// Convert inline code `code` to <code>code</code>
|
// Convert multiline code blocks ```code``` to <pre><code> tags
|
||||||
text = text.replace(/`(.*?)`/g, '<code>$1</code>');
|
content = content.replace(/```(.*?)```/gs, '<pre><code>$1</code></pre>');
|
||||||
|
|
||||||
// Convert blockquotes > quote to <blockquote>quote</blockquote>
|
return content;
|
||||||
text = text.replace(/^> (.*?$)/gm, '<blockquote>$1</blockquote>');
|
}
|
||||||
|
|
||||||
// Convert multiline code blocks ```code``` to <pre><code>code</code></pre>
|
function createTranscriptHTML(ticket, reason) {
|
||||||
text = text.replace(/```(.*?)```/gs, '<pre><code>$1</code></pre>');
|
return `
|
||||||
|
<!DOCTYPE html>
|
||||||
// Convert line breaks to <br>
|
<html>
|
||||||
text = text.replace(/\n/g, '<br>');
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
return text;
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
color: white;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 1000px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background-color: rgba(0, 0, 0, 0.3);
|
||||||
|
border-radius: 15px;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.message {
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
.message-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.avatar {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
.username {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #4CAF50;
|
||||||
|
}
|
||||||
|
.timestamp {
|
||||||
|
margin-left: auto;
|
||||||
|
color: #888;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
.embed {
|
||||||
|
border-left: 4px solid #4CAF50;
|
||||||
|
padding-left: 10px;
|
||||||
|
margin: 10px 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
.attachment {
|
||||||
|
display: inline-block;
|
||||||
|
background-color: rgba(76, 175, 80, 0.1);
|
||||||
|
border: 1px solid #4CAF50;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
margin: 5px 0;
|
||||||
|
color: #4CAF50;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="mb-6">
|
||||||
|
<h1 class="text-3xl font-bold mb-4">Ticket Transcript</h1>
|
||||||
|
<p><strong>Channel:</strong> ${ticket.name}</p>
|
||||||
|
<p><strong>Ticket ID:</strong> ${ticket.id}</p>
|
||||||
|
<p><strong>Message Count:</strong> ${ticket.messages.length}</p>
|
||||||
|
<p><strong>Close Reason:</strong> ${reason}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="messages">
|
||||||
|
${ticket.messages.map(msg => `
|
||||||
|
<div class="message">
|
||||||
|
<div class="message-header">
|
||||||
|
<img src="${msg.avatar}" alt="Avatar" class="avatar">
|
||||||
|
<span class="username">${msg.username}</span>
|
||||||
|
<span class="timestamp">${new Date(msg.timestamp).toLocaleString()}</span>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
${parseMarkdown(msg.content)}
|
||||||
|
|
||||||
|
${msg.embeds.map(embed => `
|
||||||
|
<div class="embed">
|
||||||
|
${embed.title ? `<div class="font-bold">${embed.title}</div>` : ''}
|
||||||
|
${embed.description ? `<div>${embed.description}</div>` : ''}
|
||||||
|
${embed.fields.map(field => `
|
||||||
|
<div class="mt-2">
|
||||||
|
<strong>${field.name}:</strong>
|
||||||
|
<div>${field.value}</div>
|
||||||
|
</div>
|
||||||
|
`).join('')}
|
||||||
|
</div>
|
||||||
|
`).join('')}
|
||||||
|
|
||||||
|
${msg.attachments.map(att => `
|
||||||
|
<a href="${att.url}" class="attachment" target="_blank">
|
||||||
|
📎 ${att.name}
|
||||||
|
</a>
|
||||||
|
`).join('')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
+449
-137
@@ -1,167 +1,479 @@
|
|||||||
// File path: ./commands/embed.js
|
const {
|
||||||
|
ApplicationCommandType,
|
||||||
|
ApplicationCommandOptionType,
|
||||||
|
ActionRowBuilder,
|
||||||
|
ButtonBuilder,
|
||||||
|
EmbedBuilder,
|
||||||
|
StringSelectMenuBuilder,
|
||||||
|
ModalBuilder,
|
||||||
|
TextInputBuilder,
|
||||||
|
TextInputStyle,
|
||||||
|
ButtonStyle,
|
||||||
|
ComponentType
|
||||||
|
} = require('discord.js');
|
||||||
|
const Command = require('../Structures/Command.js');
|
||||||
|
const fs = require('fs');
|
||||||
|
const yaml = require('js-yaml');
|
||||||
|
|
||||||
const fs = require("fs");
|
const supportbot = yaml.load(fs.readFileSync('./Configs/supportbot.yml', 'utf8'));
|
||||||
const Discord = require("discord.js");
|
const cmdconfig = yaml.load(fs.readFileSync('./Configs/commands.yml', 'utf8'));
|
||||||
const yaml = require("js-yaml");
|
const msgconfig = yaml.load(fs.readFileSync('./Configs/messages.yml', 'utf8'));
|
||||||
|
|
||||||
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
|
// Helper function to validate and convert hex color
|
||||||
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
|
function parseColor(color) {
|
||||||
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
|
if (!color) return 0x5865F2;
|
||||||
|
if (typeof color === 'number') return color;
|
||||||
const Command = require("../Structures/Command.js");
|
if (typeof color !== 'string') return 0x5865F2;
|
||||||
|
color = color.replace('#', '');
|
||||||
|
if (/^[0-9A-F]{6}$/i.test(color)) {
|
||||||
|
return parseInt(color, 16);
|
||||||
|
}
|
||||||
|
return 0x5865F2;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = new Command({
|
module.exports = new Command({
|
||||||
name: cmdconfig.Embed.Command,
|
name: cmdconfig.Embed.Command,
|
||||||
description: cmdconfig.Embed.Description,
|
description: cmdconfig.Embed.Description,
|
||||||
type: Discord.ApplicationCommandType.ChatInput,
|
type: ApplicationCommandType.ChatInput,
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
name: "title",
|
name: 'channel',
|
||||||
description: "Embed Title",
|
description: 'Select channel to send the message',
|
||||||
type: Discord.ApplicationCommandOptionType.String,
|
type: ApplicationCommandOptionType.Channel,
|
||||||
required: true,
|
required: true
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "message",
|
|
||||||
description: "Embed Message",
|
|
||||||
type: Discord.ApplicationCommandOptionType.String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "color",
|
|
||||||
description: "Embed HEX Color",
|
|
||||||
type: Discord.ApplicationCommandOptionType.String,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "fields",
|
|
||||||
description: "Embed Fields (title|content;title|content;...)",
|
|
||||||
type: Discord.ApplicationCommandOptionType.String,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "thumbnail",
|
|
||||||
description: "Embed Thumbnail URL",
|
|
||||||
type: Discord.ApplicationCommandOptionType.String,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "image",
|
|
||||||
description: "Embed Image URL",
|
|
||||||
type: Discord.ApplicationCommandOptionType.String,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "footer",
|
|
||||||
description: "Embed Footer Text",
|
|
||||||
type: Discord.ApplicationCommandOptionType.String,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "footericon",
|
|
||||||
description: "Embed Footer Icon URL",
|
|
||||||
type: Discord.ApplicationCommandOptionType.String,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "author",
|
|
||||||
description: "Embed Author Name",
|
|
||||||
type: Discord.ApplicationCommandOptionType.String,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "authoricon",
|
|
||||||
description: "Embed Author Icon URL",
|
|
||||||
type: Discord.ApplicationCommandOptionType.String,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "authorurl",
|
|
||||||
description: "Embed Author URL",
|
|
||||||
type: Discord.ApplicationCommandOptionType.String,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "timestamp",
|
|
||||||
description: "Include Timestamp",
|
|
||||||
type: Discord.ApplicationCommandOptionType.Boolean,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "inline",
|
|
||||||
description: "Make Fields Inline",
|
|
||||||
type: Discord.ApplicationCommandOptionType.Boolean,
|
|
||||||
required: false,
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
permissions: cmdconfig.Embed.Permission,
|
permissions: cmdconfig.Embed.Permission,
|
||||||
|
|
||||||
async run(interaction) {
|
async run(interaction) {
|
||||||
|
// Permission check
|
||||||
const { getRole } = interaction.client;
|
const { getRole } = interaction.client;
|
||||||
let SupportStaff = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild);
|
let SupportStaff = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild);
|
||||||
let Admin = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild);
|
let Admin = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild);
|
||||||
|
|
||||||
if (!SupportStaff || !Admin) {
|
if (!SupportStaff || !Admin) {
|
||||||
return interaction.reply("Some roles seem to be missing!\nPlease check for errors when starting the bot.");
|
return interaction.reply('Some roles seem to be missing!\nPlease check for errors when starting the bot.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const NoPerms = new Discord.EmbedBuilder()
|
if (!interaction.member.roles.cache.has(SupportStaff.id) &&
|
||||||
.setTitle("Invalid Permissions!")
|
!interaction.member.roles.cache.has(Admin.id)) {
|
||||||
.setDescription(`${msgconfig.Error.IncorrectPerms}\n\nRole Required: \`${supportbot.Roles.StaffMember.Staff}\` or \`${supportbot.Roles.StaffMember.Admin}\``)
|
const NoPerms = new EmbedBuilder()
|
||||||
.setColor(supportbot.Embed.Colours.Warn);
|
.setTitle('Invalid Permissions!')
|
||||||
|
.setDescription(`${msgconfig.Error.IncorrectPerms}\n\nRole Required: \`${supportbot.Roles.StaffMember.Staff}\` or \`${supportbot.Roles.StaffMember.Admin}\``)
|
||||||
|
.setColor(0xFF0000);
|
||||||
|
return interaction.reply({ embeds: [NoPerms], ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
const channel = interaction.options.getChannel('channel');
|
||||||
interaction.member.roles.cache.has(SupportStaff.id) ||
|
|
||||||
interaction.member.roles.cache.has(Admin.id)
|
|
||||||
) {
|
|
||||||
const EmbedTitle = interaction.options.getString("title").replace(/\\n/g, '\n');
|
|
||||||
const EmbedSubject = interaction.options.getString("message").replace(/\\n/g, '\n');
|
|
||||||
const GeneralColour = interaction.options.getString("color") || supportbot.Embed.Colours.Default;
|
|
||||||
const EmbedThumbnail = interaction.options.getString("thumbnail");
|
|
||||||
const EmbedImage = interaction.options.getString("image");
|
|
||||||
const EmbedFields = interaction.options.getString("fields");
|
|
||||||
const EmbedFooter = interaction.options.getString("footer")?.replace(/\\n/g, '\n');
|
|
||||||
const EmbedFooterIcon = interaction.options.getString("footericon");
|
|
||||||
const EmbedAuthor = interaction.options.getString("author")?.replace(/\\n/g, '\n');
|
|
||||||
const EmbedAuthorIcon = interaction.options.getString("authoricon");
|
|
||||||
const EmbedAuthorURL = interaction.options.getString("authorurl");
|
|
||||||
const EmbedTimestamp = interaction.options.getBoolean("timestamp");
|
|
||||||
const InlineFields = interaction.options.getBoolean("inline");
|
|
||||||
|
|
||||||
const EmbedMsg = new Discord.EmbedBuilder()
|
// Initial message state
|
||||||
.setTitle(EmbedTitle)
|
const messageData = {
|
||||||
.setDescription(EmbedSubject)
|
type: 'embed',
|
||||||
.setColor(GeneralColour)
|
title: 'New Embed',
|
||||||
.setThumbnail(EmbedThumbnail)
|
description: 'Click the buttons below to edit this embed',
|
||||||
.setImage(EmbedImage);
|
color: 0x5865F2,
|
||||||
|
fields: [],
|
||||||
|
footer: null,
|
||||||
|
thumbnail: null,
|
||||||
|
image: null,
|
||||||
|
timestamp: false
|
||||||
|
};
|
||||||
|
|
||||||
if (EmbedFields) {
|
// Create initial embed
|
||||||
const fieldsArray = EmbedFields.split(';');
|
function createEmbed() {
|
||||||
fieldsArray.forEach(field => {
|
const embed = new EmbedBuilder()
|
||||||
const [title, value] = field.split('|');
|
.setTitle(messageData.title)
|
||||||
if (title && value) {
|
.setDescription(messageData.description)
|
||||||
EmbedMsg.addFields({ name: title.replace(/\\n/g, '\n'), value: value.replace(/\\n/g, '\n'), inline: InlineFields || false });
|
.setColor(messageData.color);
|
||||||
}
|
|
||||||
|
if (messageData.fields.length > 0) {
|
||||||
|
embed.addFields(messageData.fields);
|
||||||
|
}
|
||||||
|
if (messageData.footer) {
|
||||||
|
embed.setFooter({ text: messageData.footer });
|
||||||
|
}
|
||||||
|
if (messageData.thumbnail) {
|
||||||
|
embed.setThumbnail(messageData.thumbnail);
|
||||||
|
}
|
||||||
|
if (messageData.image) {
|
||||||
|
embed.setImage(messageData.image);
|
||||||
|
}
|
||||||
|
if (messageData.timestamp) {
|
||||||
|
embed.setTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
return embed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create type selector
|
||||||
|
const typeRow = new ActionRowBuilder()
|
||||||
|
.addComponents(
|
||||||
|
new StringSelectMenuBuilder()
|
||||||
|
.setCustomId('message_type')
|
||||||
|
.setPlaceholder('Select message type')
|
||||||
|
.addOptions([
|
||||||
|
{
|
||||||
|
label: 'Embed Message',
|
||||||
|
description: 'Send as an embedded message',
|
||||||
|
value: 'embed'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Text Message',
|
||||||
|
description: 'Send as a plain text message',
|
||||||
|
value: 'text'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create button rows function
|
||||||
|
function createButtons(messageType) {
|
||||||
|
const buttons = [];
|
||||||
|
|
||||||
|
if (messageType === 'embed') {
|
||||||
|
// Only show these buttons for embed mode
|
||||||
|
const row1 = new ActionRowBuilder()
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId('edit_title')
|
||||||
|
.setLabel('Edit Title')
|
||||||
|
.setStyle(ButtonStyle.Secondary),
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId('edit_description')
|
||||||
|
.setLabel('Edit Description')
|
||||||
|
.setStyle(ButtonStyle.Secondary),
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId('edit_color')
|
||||||
|
.setLabel('Edit Color')
|
||||||
|
.setStyle(ButtonStyle.Secondary),
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId('add_field')
|
||||||
|
.setLabel('Add Field')
|
||||||
|
.setStyle(ButtonStyle.Secondary)
|
||||||
|
);
|
||||||
|
|
||||||
|
const row2 = new ActionRowBuilder()
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId('edit_footer')
|
||||||
|
.setLabel('Edit Footer')
|
||||||
|
.setStyle(ButtonStyle.Secondary),
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId('edit_image')
|
||||||
|
.setLabel('Add Image')
|
||||||
|
.setStyle(ButtonStyle.Secondary),
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId('edit_thumbnail')
|
||||||
|
.setLabel('Add Thumbnail')
|
||||||
|
.setStyle(ButtonStyle.Secondary),
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId('toggle_timestamp')
|
||||||
|
.setLabel('Toggle Timestamp')
|
||||||
|
.setStyle(ButtonStyle.Secondary)
|
||||||
|
);
|
||||||
|
|
||||||
|
buttons.push(row1, row2);
|
||||||
|
} else {
|
||||||
|
// Text mode only shows edit text button
|
||||||
|
const textRow = new ActionRowBuilder()
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId('edit_description')
|
||||||
|
.setLabel('Edit Text')
|
||||||
|
.setStyle(ButtonStyle.Secondary)
|
||||||
|
);
|
||||||
|
|
||||||
|
buttons.push(textRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send button always shows
|
||||||
|
const sendRow = new ActionRowBuilder()
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId('send_message')
|
||||||
|
.setLabel('Send')
|
||||||
|
.setStyle(ButtonStyle.Success)
|
||||||
|
);
|
||||||
|
|
||||||
|
buttons.push(sendRow);
|
||||||
|
return buttons;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to update the message preview
|
||||||
|
async function updatePreview(i) {
|
||||||
|
if (messageData.type === 'embed') {
|
||||||
|
await i.update({
|
||||||
|
content: 'Customize your message:',
|
||||||
|
embeds: [createEmbed()],
|
||||||
|
components: [typeRow, ...createButtons('embed')]
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// For text mode, just show the text directly
|
||||||
|
await i.update({
|
||||||
|
content: messageData.description,
|
||||||
|
embeds: [],
|
||||||
|
components: [typeRow, ...createButtons('text')]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send initial message
|
||||||
|
const message = await interaction.reply({
|
||||||
|
content: messageData.type === 'text' ? messageData.description : 'Customize your message:',
|
||||||
|
components: [typeRow, ...createButtons(messageData.type)],
|
||||||
|
embeds: messageData.type === 'embed' ? [createEmbed()] : [],
|
||||||
|
ephemeral: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create collector for interactions
|
||||||
|
const collector = message.createMessageComponentCollector({
|
||||||
|
time: 900000
|
||||||
|
});
|
||||||
|
|
||||||
|
collector.on('collect', async (i) => {
|
||||||
|
if (i.user.id !== interaction.user.id) {
|
||||||
|
return i.reply({
|
||||||
|
content: 'You cannot edit this message.',
|
||||||
|
ephemeral: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (EmbedFooter) {
|
switch (i.customId) {
|
||||||
EmbedMsg.setFooter({ text: EmbedFooter, iconURL: EmbedFooterIcon });
|
case 'message_type':
|
||||||
|
messageData.type = i.values[0];
|
||||||
|
await updatePreview(i);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'edit_description':
|
||||||
|
const descModal = new ModalBuilder()
|
||||||
|
.setCustomId('desc_modal')
|
||||||
|
.setTitle(messageData.type === 'embed' ? 'Edit Description' : 'Edit Text');
|
||||||
|
|
||||||
|
descModal.addComponents(
|
||||||
|
new ActionRowBuilder()
|
||||||
|
.addComponents(
|
||||||
|
new TextInputBuilder()
|
||||||
|
.setCustomId('desc_input')
|
||||||
|
.setLabel(messageData.type === 'embed' ? 'Description' : 'Message Text')
|
||||||
|
.setStyle(TextInputStyle.Paragraph)
|
||||||
|
.setValue(messageData.description)
|
||||||
|
.setMaxLength(4000)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
await i.showModal(descModal);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'edit_title':
|
||||||
|
const titleModal = new ModalBuilder()
|
||||||
|
.setCustomId('title_modal')
|
||||||
|
.setTitle('Edit Title');
|
||||||
|
|
||||||
|
titleModal.addComponents(
|
||||||
|
new ActionRowBuilder()
|
||||||
|
.addComponents(
|
||||||
|
new TextInputBuilder()
|
||||||
|
.setCustomId('title_input')
|
||||||
|
.setLabel('Title')
|
||||||
|
.setStyle(TextInputStyle.Short)
|
||||||
|
.setValue(messageData.title)
|
||||||
|
.setMaxLength(256)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
await i.showModal(titleModal);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'edit_color':
|
||||||
|
const colorModal = new ModalBuilder()
|
||||||
|
.setCustomId('color_modal')
|
||||||
|
.setTitle('Edit Color');
|
||||||
|
|
||||||
|
colorModal.addComponents(
|
||||||
|
new ActionRowBuilder()
|
||||||
|
.addComponents(
|
||||||
|
new TextInputBuilder()
|
||||||
|
.setCustomId('color_input')
|
||||||
|
.setLabel('Color (HEX)')
|
||||||
|
.setStyle(TextInputStyle.Short)
|
||||||
|
.setPlaceholder('#FF0000')
|
||||||
|
.setMinLength(6)
|
||||||
|
.setMaxLength(7)
|
||||||
|
.setValue(messageData.color.toString(16).padStart(6, '0'))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
await i.showModal(colorModal);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'add_field':
|
||||||
|
const fieldModal = new ModalBuilder()
|
||||||
|
.setCustomId('field_modal')
|
||||||
|
.setTitle('Add Field');
|
||||||
|
|
||||||
|
fieldModal.addComponents(
|
||||||
|
new ActionRowBuilder()
|
||||||
|
.addComponents(
|
||||||
|
new TextInputBuilder()
|
||||||
|
.setCustomId('field_name')
|
||||||
|
.setLabel('Field Name')
|
||||||
|
.setStyle(TextInputStyle.Short)
|
||||||
|
.setMaxLength(256)
|
||||||
|
),
|
||||||
|
new ActionRowBuilder()
|
||||||
|
.addComponents(
|
||||||
|
new TextInputBuilder()
|
||||||
|
.setCustomId('field_value')
|
||||||
|
.setLabel('Field Value')
|
||||||
|
.setStyle(TextInputStyle.Paragraph)
|
||||||
|
.setMaxLength(1024)
|
||||||
|
),
|
||||||
|
new ActionRowBuilder()
|
||||||
|
.addComponents(
|
||||||
|
new TextInputBuilder()
|
||||||
|
.setCustomId('field_inline')
|
||||||
|
.setLabel('Inline? (true/false)')
|
||||||
|
.setStyle(TextInputStyle.Short)
|
||||||
|
.setValue('false')
|
||||||
|
.setPlaceholder('true or false')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
await i.showModal(fieldModal);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'edit_footer':
|
||||||
|
const footerModal = new ModalBuilder()
|
||||||
|
.setCustomId('footer_modal')
|
||||||
|
.setTitle('Edit Footer');
|
||||||
|
|
||||||
|
footerModal.addComponents(
|
||||||
|
new ActionRowBuilder()
|
||||||
|
.addComponents(
|
||||||
|
new TextInputBuilder()
|
||||||
|
.setCustomId('footer_input')
|
||||||
|
.setLabel('Footer Text')
|
||||||
|
.setStyle(TextInputStyle.Short)
|
||||||
|
.setValue(messageData.footer || '')
|
||||||
|
.setMaxLength(2048)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
await i.showModal(footerModal);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'edit_image':
|
||||||
|
const imageModal = new ModalBuilder()
|
||||||
|
.setCustomId('image_modal')
|
||||||
|
.setTitle('Add Image');
|
||||||
|
|
||||||
|
imageModal.addComponents(
|
||||||
|
new ActionRowBuilder()
|
||||||
|
.addComponents(
|
||||||
|
new TextInputBuilder()
|
||||||
|
.setCustomId('image_input')
|
||||||
|
.setLabel('Image URL')
|
||||||
|
.setStyle(TextInputStyle.Short)
|
||||||
|
.setValue(messageData.image || '')
|
||||||
|
.setPlaceholder('https://example.com/image.png')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
await i.showModal(imageModal);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'edit_thumbnail':
|
||||||
|
const thumbnailModal = new ModalBuilder()
|
||||||
|
.setCustomId('thumbnail_modal')
|
||||||
|
.setTitle('Add Thumbnail');
|
||||||
|
|
||||||
|
thumbnailModal.addComponents(
|
||||||
|
new ActionRowBuilder()
|
||||||
|
.addComponents(
|
||||||
|
new TextInputBuilder()
|
||||||
|
.setCustomId('thumbnail_input')
|
||||||
|
.setLabel('Thumbnail URL')
|
||||||
|
.setStyle(TextInputStyle.Short)
|
||||||
|
.setValue(messageData.thumbnail || '')
|
||||||
|
.setPlaceholder('https://example.com/thumbnail.png')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
await i.showModal(thumbnailModal);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'toggle_timestamp':
|
||||||
|
messageData.timestamp = !messageData.timestamp;
|
||||||
|
await updatePreview(i);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'send_message':
|
||||||
|
try {
|
||||||
|
if (messageData.type === 'embed') {
|
||||||
|
await channel.send({ embeds: [createEmbed()] });
|
||||||
|
} else {
|
||||||
|
await channel.send(messageData.description);
|
||||||
|
}
|
||||||
|
await i.update({
|
||||||
|
content: 'Message sent successfully!',
|
||||||
|
components: [],
|
||||||
|
embeds: []
|
||||||
|
});
|
||||||
|
collector.stop();
|
||||||
|
} catch (error) {
|
||||||
|
await i.reply({
|
||||||
|
content: 'Failed to send message. Please check channel permissions.',
|
||||||
|
ephemeral: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle modal submissions
|
||||||
|
interaction.client.on('interactionCreate', async (modal) => {
|
||||||
|
if (!modal.isModalSubmit()) return;
|
||||||
|
if (modal.user.id !== interaction.user.id) return;
|
||||||
|
|
||||||
|
switch (modal.customId) {
|
||||||
|
case 'title_modal':
|
||||||
|
messageData.title = modal.fields.getTextInputValue('title_input');
|
||||||
|
break;
|
||||||
|
case 'desc_modal':
|
||||||
|
messageData.description = modal.fields.getTextInputValue('desc_input');
|
||||||
|
break;
|
||||||
|
case 'color_modal':
|
||||||
|
messageData.color = parseColor(modal.fields.getTextInputValue('color_input'));
|
||||||
|
break;
|
||||||
|
case 'field_modal':
|
||||||
|
const inlineValue = modal.fields.getTextInputValue('field_inline').toLowerCase();
|
||||||
|
messageData.fields.push({
|
||||||
|
name: modal.fields.getTextInputValue('field_name'),
|
||||||
|
value: modal.fields.getTextInputValue('field_value'),
|
||||||
|
inline: inlineValue === 'true'
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'footer_modal':
|
||||||
|
messageData.footer = modal.fields.getTextInputValue('footer_input');
|
||||||
|
break;
|
||||||
|
case 'image_modal':
|
||||||
|
messageData.image = modal.fields.getTextInputValue('image_input');
|
||||||
|
break;
|
||||||
|
case 'thumbnail_modal':
|
||||||
|
messageData.thumbnail = modal.fields.getTextInputValue('thumbnail_input');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (EmbedAuthor) {
|
await updatePreview(modal);
|
||||||
EmbedMsg.setAuthor({ name: EmbedAuthor, iconURL: EmbedAuthorIcon, url: EmbedAuthorURL });
|
});
|
||||||
}
|
|
||||||
|
|
||||||
if (EmbedTimestamp) {
|
collector.on('end', () => {
|
||||||
EmbedMsg.setTimestamp();
|
interaction.editReply({
|
||||||
}
|
content: 'Message editor timed out.',
|
||||||
|
components: [],
|
||||||
interaction.reply({
|
embeds: []
|
||||||
embeds: [EmbedMsg],
|
}).catch(() => {});
|
||||||
});
|
});
|
||||||
} else {
|
}
|
||||||
return interaction.reply({ embeds: [NoPerms] });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
+261
@@ -0,0 +1,261 @@
|
|||||||
|
const fs = require("fs");
|
||||||
|
const { ApplicationCommandOptionType, ApplicationCommandType, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, ComponentType } = require("discord.js");
|
||||||
|
const yaml = require("js-yaml");
|
||||||
|
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
|
||||||
|
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
|
||||||
|
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
|
||||||
|
|
||||||
|
const Command = require("../Structures/Command.js");
|
||||||
|
|
||||||
|
// Function to chunk array into smaller arrays
|
||||||
|
function chunkArray(arr, size) {
|
||||||
|
const chunks = [];
|
||||||
|
for (let i = 0; i < arr.length; i += size) {
|
||||||
|
chunks.push(arr.slice(i, i + size));
|
||||||
|
}
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new Command({
|
||||||
|
name: cmdconfig.Mod.Command,
|
||||||
|
description: cmdconfig.Mod.Command,
|
||||||
|
type: ApplicationCommandType.ChatInput,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: cmdconfig.Mod.TicketBlacklist.Command,
|
||||||
|
description: cmdconfig.Mod.TicketBlacklist.Description,
|
||||||
|
type: ApplicationCommandOptionType.SubcommandGroup,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: cmdconfig.Mod.TicketBlacklist.Add.Command,
|
||||||
|
description: msgconfig.Mod.TicketBlacklist.Add.Description || "Add a user to the ticket blacklist",
|
||||||
|
type: ApplicationCommandOptionType.Subcommand,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'user',
|
||||||
|
description: msgconfig.Mod.TicketBlacklist.Add.UserDescription || "User to blacklist",
|
||||||
|
type: ApplicationCommandOptionType.User,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'reason',
|
||||||
|
description: msgconfig.Mod.TicketBlacklist.Add.ReasonDescription || "Reason for blacklisting",
|
||||||
|
type: ApplicationCommandOptionType.String,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: cmdconfig.Mod.TicketBlacklist.Remove.Command,
|
||||||
|
description: msgconfig.Mod.TicketBlacklist.Remove.Description || "Remove a user from the ticket blacklist",
|
||||||
|
type: ApplicationCommandOptionType.Subcommand,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'user',
|
||||||
|
description: msgconfig.Mod.TicketBlacklist.Remove.UserDescription || "User to remove from blacklist",
|
||||||
|
type: ApplicationCommandOptionType.User,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'reason',
|
||||||
|
description: msgconfig.Mod.TicketBlacklist.Remove.ReasonDescription || "Reason for removing from blacklist",
|
||||||
|
type: ApplicationCommandOptionType.String,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "view",
|
||||||
|
description: msgconfig.Mod.TicketBlacklist.View.Description || "View all blacklisted users",
|
||||||
|
type: ApplicationCommandOptionType.Subcommand,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
permissions: cmdconfig.Mod.Permission,
|
||||||
|
|
||||||
|
async run(interaction) {
|
||||||
|
const { getRole } = interaction.client;
|
||||||
|
|
||||||
|
let SupportStaff = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild);
|
||||||
|
let Admin = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild);
|
||||||
|
let Moderator = await getRole(supportbot.Roles.StaffMember.Moderator, interaction.guild);
|
||||||
|
|
||||||
|
if (!SupportStaff || !Admin || !Moderator) {
|
||||||
|
const missingRolesEmbed = new EmbedBuilder()
|
||||||
|
.setDescription(msgconfig.Error.InvalidChannel || "Required roles are missing!")
|
||||||
|
.setColor(supportbot.Embed.Colours.Warn);
|
||||||
|
return interaction.reply({ embeds: [missingRolesEmbed] });
|
||||||
|
}
|
||||||
|
|
||||||
|
const NoPerms = new EmbedBuilder()
|
||||||
|
.setDescription(msgconfig.Error.IncorrectPerms || "You do not have the correct permissions!")
|
||||||
|
.setColor(supportbot.Embed.Colours.Warn);
|
||||||
|
|
||||||
|
if (!interaction.member.roles.cache.has(Admin.id) && !interaction.member.roles.cache.has(Moderator.id) && (!supportbot.Mod.AllowSupportStaff || !interaction.member.roles.cache.has(SupportStaff.id))) {
|
||||||
|
return interaction.reply({ embeds: [NoPerms] });
|
||||||
|
}
|
||||||
|
|
||||||
|
const subcommand = interaction.options.getSubcommand(false);
|
||||||
|
const user = interaction.options.getUser('user');
|
||||||
|
const reason = interaction.options.getString('reason') || "No reason provided";
|
||||||
|
|
||||||
|
try {
|
||||||
|
let blacklistedUsers;
|
||||||
|
try {
|
||||||
|
blacklistedUsers = JSON.parse(fs.readFileSync("./Data/BlacklistedUsers.json", "utf8")).blacklistedUsers;
|
||||||
|
if (!Array.isArray(blacklistedUsers)) {
|
||||||
|
blacklistedUsers = [];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
blacklistedUsers = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subcommand === cmdconfig.Mod.TicketBlacklist.Add.Command) {
|
||||||
|
if (blacklistedUsers.includes(user.id)) {
|
||||||
|
const alreadyBlacklistedEmbed = new EmbedBuilder()
|
||||||
|
.setDescription(msgconfig.Mod.TicketBlacklist.Add.AlreadyBlacklisted.replace("{userTag}", user.tag) || ":x: User is already blacklisted.")
|
||||||
|
.setColor(supportbot.Embed.Colours.Warn);
|
||||||
|
return interaction.reply({ embeds: [alreadyBlacklistedEmbed], ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
blacklistedUsers.push(user.id);
|
||||||
|
fs.writeFileSync("./Data/BlacklistedUsers.json", JSON.stringify({ "blacklistedUsers": blacklistedUsers }, null, 4));
|
||||||
|
|
||||||
|
const successEmbed = new EmbedBuilder()
|
||||||
|
.setDescription(msgconfig.Mod.TicketBlacklist.Add.Success.replace("{userTag}", user.tag) || ":white_check_mark: User has been blacklisted.")
|
||||||
|
.setColor(supportbot.Embed.Colours.Success);
|
||||||
|
|
||||||
|
const blacklistChannel = interaction.guild.channels.cache.get(supportbot.Ticket.Log.TicketBlacklistLog);
|
||||||
|
if (blacklistChannel) {
|
||||||
|
const blacklistLogEmbed = new EmbedBuilder()
|
||||||
|
.setTitle(msgconfig.TicketBlacklistLog.Title || "Ticket Blacklist Log")
|
||||||
|
.setColor(msgconfig.TicketBlacklistLog.Colour || supportbot.Embed.Colours.Success)
|
||||||
|
.setFooter({ text: supportbot.Embed.Footer, iconURL: interaction.user.displayAvatarURL() })
|
||||||
|
.setDescription(`> **User:** ${user.tag} (\`${user.id}\`)\n> **Actioned by:** <@${interaction.user.id}>`)
|
||||||
|
.addFields({ name: "Action", value: "\`\`\`Removed from blacklist\`\`\`", inline: false })
|
||||||
|
.addFields({ name: "Reason", value: `\`\`\`${reason}\`\`\``, inline: false });
|
||||||
|
|
||||||
|
blacklistChannel.send({ embeds: [blacklistLogEmbed] });
|
||||||
|
}
|
||||||
|
|
||||||
|
return interaction.reply({
|
||||||
|
embeds: [successEmbed],
|
||||||
|
ephemeral: true
|
||||||
|
});
|
||||||
|
|
||||||
|
} else if (subcommand === cmdconfig.Mod.TicketBlacklist.Remove.Command) {
|
||||||
|
if (!blacklistedUsers.includes(user.id)) {
|
||||||
|
const notBlacklistedEmbed = new EmbedBuilder()
|
||||||
|
.setDescription(msgconfig.Mod.TicketBlacklist.Remove.NotBlacklisted.replace("{userTag}", user.tag) || ":x: User is not blacklisted.")
|
||||||
|
.setColor(supportbot.Embed.Colours.Warn);
|
||||||
|
return interaction.reply({ embeds: [notBlacklistedEmbed], ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
blacklistedUsers = blacklistedUsers.filter(id => id !== user.id);
|
||||||
|
fs.writeFileSync("./Data/BlacklistedUsers.json", JSON.stringify({ "blacklistedUsers": blacklistedUsers }, null, 4));
|
||||||
|
|
||||||
|
const removedEmbed = new EmbedBuilder()
|
||||||
|
.setDescription(msgconfig.Mod.TicketBlacklist.Remove.Success.replace("{userTag}", user.tag) || ":white_check_mark: User has been removed from the blacklist.")
|
||||||
|
.setColor(supportbot.Embed.Colours.Success);
|
||||||
|
|
||||||
|
const blacklistChannel = interaction.guild.channels.cache.get(supportbot.Ticket.Log.TicketBlacklistLog);
|
||||||
|
if (blacklistChannel) {
|
||||||
|
const blacklistLogEmbed = new EmbedBuilder()
|
||||||
|
.setTitle(msgconfig.TicketBlacklistLog.Title || "Ticket Blacklist Log")
|
||||||
|
.setColor(msgconfig.TicketBlacklistLog.Colour || supportbot.Embed.Colours.Success)
|
||||||
|
.setFooter({ text: supportbot.Embed.Footer, iconURL: interaction.user.displayAvatarURL() })
|
||||||
|
.setDescription(`> **User:** ${user.tag} (\`${user.id}\`)\n> **Actioned by:** <@${interaction.user.id}>`)
|
||||||
|
.addFields({ name: "Action", value: "\`\`\`Removed from blacklist\`\`\`", inline: false })
|
||||||
|
.addFields({ name: "Reason", value: `\`\`\`${reason}\`\`\``, inline: false });
|
||||||
|
|
||||||
|
blacklistChannel.send({ embeds: [blacklistLogEmbed] });
|
||||||
|
}
|
||||||
|
|
||||||
|
return interaction.reply({ embeds: [removedEmbed], ephemeral: true });
|
||||||
|
|
||||||
|
} else if (subcommand === "view") {
|
||||||
|
if (blacklistedUsers.length === 0) {
|
||||||
|
const noBlacklistedUsersEmbed = new EmbedBuilder()
|
||||||
|
.setDescription(msgconfig.Mod.TicketBlacklist.View.NoBlacklistedUsers || "No users are blacklisted.")
|
||||||
|
.setColor(supportbot.Embed.Colours.Success);
|
||||||
|
return interaction.reply({ embeds: [noBlacklistedUsersEmbed], ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const chunkedUsers = chunkArray(blacklistedUsers, 5);
|
||||||
|
let currentPage = 0;
|
||||||
|
|
||||||
|
const createEmbed = (page) => {
|
||||||
|
return new EmbedBuilder()
|
||||||
|
.setTitle(msgconfig.Mod.TicketBlacklist.View.EmbedTitle || "Blacklisted Users")
|
||||||
|
.setDescription(chunkedUsers[page].map(id => `<@${id}>`).join("\n"))
|
||||||
|
.setColor(supportbot.Embed.Colours.Success)
|
||||||
|
.setFooter({ text: `Page ${page + 1} of ${chunkedUsers.length}` });
|
||||||
|
};
|
||||||
|
|
||||||
|
const buttons = new ActionRowBuilder()
|
||||||
|
.addComponents(
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId('previous')
|
||||||
|
.setLabel('Previous')
|
||||||
|
.setStyle(ButtonStyle.Secondary)
|
||||||
|
.setDisabled(currentPage === 0),
|
||||||
|
new ButtonBuilder()
|
||||||
|
.setCustomId('next')
|
||||||
|
.setLabel('Next')
|
||||||
|
.setStyle(ButtonStyle.Secondary)
|
||||||
|
.setDisabled(currentPage === chunkedUsers.length - 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await interaction.reply({
|
||||||
|
embeds: [createEmbed(currentPage)],
|
||||||
|
components: chunkedUsers.length > 1 ? [buttons] : [],
|
||||||
|
ephemeral: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (chunkedUsers.length <= 1) return;
|
||||||
|
|
||||||
|
const collector = response.createMessageComponentCollector({
|
||||||
|
componentType: ComponentType.Button,
|
||||||
|
time: 60000
|
||||||
|
});
|
||||||
|
|
||||||
|
collector.on('collect', async (i) => {
|
||||||
|
if (i.user.id !== interaction.user.id) {
|
||||||
|
return i.reply({
|
||||||
|
content: 'You cannot use these buttons.',
|
||||||
|
ephemeral: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i.customId === 'previous' && currentPage > 0) {
|
||||||
|
currentPage--;
|
||||||
|
} else if (i.customId === 'next' && currentPage < chunkedUsers.length - 1) {
|
||||||
|
currentPage++;
|
||||||
|
}
|
||||||
|
|
||||||
|
buttons.components[0].setDisabled(currentPage === 0);
|
||||||
|
buttons.components[1].setDisabled(currentPage === chunkedUsers.length - 1);
|
||||||
|
|
||||||
|
await i.update({
|
||||||
|
embeds: [createEmbed(currentPage)],
|
||||||
|
components: [buttons]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
collector.on('end', () => {
|
||||||
|
buttons.components.forEach(button => button.setDisabled(true));
|
||||||
|
interaction.editReply({
|
||||||
|
components: [buttons]
|
||||||
|
}).catch(() => {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error in mod command:", error);
|
||||||
|
const errorEmbed = new EmbedBuilder()
|
||||||
|
.setDescription(msgconfig.Error.ActionFailed || "An error occurred while processing your request.")
|
||||||
|
.setColor(supportbot.Embed.Colours.Warn);
|
||||||
|
interaction.reply({ embeds: [errorEmbed], ephemeral: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
+1
-5
@@ -10,10 +10,6 @@ const Command = require("../Structures/Command.js");
|
|||||||
|
|
||||||
const clockedInUsers = new Set();
|
const clockedInUsers = new Set();
|
||||||
|
|
||||||
// async function getRole(roleName, guild) {
|
|
||||||
// return guild.roles.cache.find(role => role.name === roleName);
|
|
||||||
// }
|
|
||||||
|
|
||||||
function updateClockedInStatus(profileEmbed, clockedIn) {
|
function updateClockedInStatus(profileEmbed, clockedIn) {
|
||||||
profileEmbed.data.fields = profileEmbed.data.fields.map(field => {
|
profileEmbed.data.fields = profileEmbed.data.fields.map(field => {
|
||||||
if (field.name === 'Clocked In Status') {
|
if (field.name === 'Clocked In Status') {
|
||||||
@@ -39,7 +35,7 @@ module.exports = new Command({
|
|||||||
],
|
],
|
||||||
|
|
||||||
async run(interaction) {
|
async run(interaction) {
|
||||||
await interaction.deferReply({ ephemeral: true }); // Acknowledge the interaction immediately
|
await interaction.deferReply({ ephemeral: true });
|
||||||
|
|
||||||
const userOption = interaction.options.getUser('user');
|
const userOption = interaction.options.getUser('user');
|
||||||
const viewingUser = userOption || interaction.user;
|
const viewingUser = userOption || interaction.user;
|
||||||
|
|||||||
+95
-15
@@ -9,6 +9,7 @@ const {
|
|||||||
StringSelectMenuBuilder,
|
StringSelectMenuBuilder,
|
||||||
StringSelectMenuOptionBuilder,
|
StringSelectMenuOptionBuilder,
|
||||||
ButtonStyle,
|
ButtonStyle,
|
||||||
|
MessageCollector,
|
||||||
} = require("discord.js");
|
} = require("discord.js");
|
||||||
const yaml = require("js-yaml");
|
const yaml = require("js-yaml");
|
||||||
|
|
||||||
@@ -20,6 +21,23 @@ const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
|
|||||||
const Command = require("../Structures/Command.js");
|
const Command = require("../Structures/Command.js");
|
||||||
const TicketNumberID = require("../Structures/TicketID.js");
|
const TicketNumberID = require("../Structures/TicketID.js");
|
||||||
|
|
||||||
|
async function isBlacklisted(userId) {
|
||||||
|
let blacklistData;
|
||||||
|
try {
|
||||||
|
blacklistData = JSON.parse(fs.readFileSync("./Data/BlacklistedUsers.json", "utf8"));
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error reading BlacklistedUsers.json:", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!blacklistData || !Array.isArray(blacklistData.blacklistedUsers)) {
|
||||||
|
console.error("blacklistedUsers is not defined or is not an array");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return blacklistData.blacklistedUsers.includes(userId);
|
||||||
|
}
|
||||||
|
|
||||||
async function getClockedInUsers(guild) {
|
async function getClockedInUsers(guild) {
|
||||||
const clockedInUsers = new Set();
|
const clockedInUsers = new Set();
|
||||||
|
|
||||||
@@ -37,6 +55,60 @@ async function getClockedInUsers(guild) {
|
|||||||
return clockedInUsers;
|
return clockedInUsers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function askTicketQuestions(ticketChannel, interaction, questions) {
|
||||||
|
const answers = [];
|
||||||
|
let questionIndex = 0;
|
||||||
|
|
||||||
|
const sendNextQuestion = async () => {
|
||||||
|
if (questionIndex < questions.length) {
|
||||||
|
|
||||||
|
const questionsEmbed = new EmbedBuilder()
|
||||||
|
.setDescription(`**${questionIndex + 1}.** ${questions[questionIndex]}`)
|
||||||
|
.setColor(supportbot.Embed.Colours.General)
|
||||||
|
|
||||||
|
const questionMessage = await ticketChannel.send({
|
||||||
|
embeds: [questionsEmbed],
|
||||||
|
});
|
||||||
|
|
||||||
|
const collector = new MessageCollector(ticketChannel, {
|
||||||
|
filter: (m) => m.author.id === interaction.user.id,
|
||||||
|
max: 1,
|
||||||
|
time: 60000,
|
||||||
|
});
|
||||||
|
|
||||||
|
collector.on("collect", async (message) => {
|
||||||
|
answers.push({ question: questions[questionIndex], answer: message.content });
|
||||||
|
questionIndex++;
|
||||||
|
await message.delete();
|
||||||
|
await questionMessage.delete();
|
||||||
|
sendNextQuestion();
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
const summaryEmbed = new EmbedBuilder()
|
||||||
|
.setTitle(msgconfig.Ticket.TicketQuestions.Details.Title)
|
||||||
|
.setDescription(msgconfig.Ticket.TicketQuestions.Details.Description.replace('%user%', interaction.user.id))
|
||||||
|
.setColor(supportbot.Embed.Colours.General)
|
||||||
|
.addFields(
|
||||||
|
answers.map((a) => ({
|
||||||
|
name: `Q: ${a.question}`,
|
||||||
|
value: `A: ${a.answer}`,
|
||||||
|
inline: false,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
await ticketChannel.send({
|
||||||
|
embeds: [summaryEmbed]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start asking the first question
|
||||||
|
sendNextQuestion();
|
||||||
|
}
|
||||||
|
|
||||||
async function assignTicketToUser(ticketChannel, Staff, Admin, interaction, attempts = 0) {
|
async function assignTicketToUser(ticketChannel, Staff, Admin, interaction, attempts = 0) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -205,7 +277,16 @@ module.exports = new Command({
|
|||||||
permissions: cmdconfig.OpenTicket.Permission,
|
permissions: cmdconfig.OpenTicket.Permission,
|
||||||
|
|
||||||
async run(interaction) {
|
async run(interaction) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
if (await isBlacklisted(interaction.user.id)) {
|
||||||
|
return interaction.reply({
|
||||||
|
content: msgconfig.Ticket.Blacklisted,
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let TicketReason = null;
|
let TicketReason = null;
|
||||||
if (supportbot.Ticket.TicketReason) {
|
if (supportbot.Ticket.TicketReason) {
|
||||||
TicketReason = interaction.reason || null; // Retrieve the reason from the interaction object
|
TicketReason = interaction.reason || null; // Retrieve the reason from the interaction object
|
||||||
@@ -231,20 +312,6 @@ module.exports = new Command({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (User.roles.cache.has(supportbot.Roles.ModRoles.Blacklisted)) {
|
|
||||||
return interaction.reply({
|
|
||||||
content: msgconfig.Ticket.Blacklisted,
|
|
||||||
ephemeral: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (User.roles.cache.has(supportbot.Roles.ModRoles.Muted)) {
|
|
||||||
return interaction.reply({
|
|
||||||
content: msgconfig.Ticket.Muted,
|
|
||||||
ephemeral: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let ticketNumberID = await TicketNumberID.pad();
|
let ticketNumberID = await TicketNumberID.pad();
|
||||||
const TicketSubject = TicketReason || msgconfig.Ticket.InvalidSubject;
|
const TicketSubject = TicketReason || msgconfig.Ticket.InvalidSubject;
|
||||||
|
|
||||||
@@ -352,6 +419,14 @@ module.exports = new Command({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ask questions to the user after the ticket is opened
|
||||||
|
if (supportbot.Ticket.Questions.Enabled) {
|
||||||
|
const questions = supportbot.Ticket.Questions.List || [];
|
||||||
|
if (questions.length > 0) {
|
||||||
|
await askTicketQuestions(ticketChannel, interaction, questions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Save ticket data
|
// Save ticket data
|
||||||
TicketData.tickets.push({
|
TicketData.tickets.push({
|
||||||
id: ticketChannel.id,
|
id: ticketChannel.id,
|
||||||
@@ -405,7 +480,6 @@ module.exports = new Command({
|
|||||||
)
|
)
|
||||||
.setColor(supportbot.Embed.Colours.General);
|
.setColor(supportbot.Embed.Colours.General);
|
||||||
|
|
||||||
// Add reason field if ticket reason is enabled
|
|
||||||
if (supportbot.Ticket.TicketReason && TicketReason) {
|
if (supportbot.Ticket.TicketReason && TicketReason) {
|
||||||
TicketMessage.addFields(
|
TicketMessage.addFields(
|
||||||
{ name: "Reason", value: TicketReason, inline: false }
|
{ name: "Reason", value: TicketReason, inline: false }
|
||||||
@@ -422,6 +496,11 @@ module.exports = new Command({
|
|||||||
.setDescription("Close the ticket.")
|
.setDescription("Close the ticket.")
|
||||||
.setEmoji(supportbot.SelectMenus.Tickets.CloseEmoji)
|
.setEmoji(supportbot.SelectMenus.Tickets.CloseEmoji)
|
||||||
.setValue("ticketclose"),
|
.setValue("ticketclose"),
|
||||||
|
new StringSelectMenuOptionBuilder()
|
||||||
|
.setLabel("Voice Channel")
|
||||||
|
.setDescription("Create a support voice channel.")
|
||||||
|
.setEmoji(supportbot.SelectMenus.Tickets.VCEmoji)
|
||||||
|
.setValue("supportvc"),
|
||||||
new StringSelectMenuOptionBuilder()
|
new StringSelectMenuOptionBuilder()
|
||||||
.setLabel("Archive")
|
.setLabel("Archive")
|
||||||
.setDescription("Archive the ticket.")
|
.setDescription("Archive the ticket.")
|
||||||
@@ -463,6 +542,7 @@ module.exports = new Command({
|
|||||||
components: [row2],
|
components: [row2],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error in run method:", error);
|
console.error("Error in run method:", error);
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-6
@@ -7,13 +7,11 @@
|
|||||||
# SupportBot created by Emerald Services
|
# SupportBot created by Emerald Services
|
||||||
# Installed with MIT License
|
# Installed with MIT License
|
||||||
#
|
#
|
||||||
# Discord Support: https://dsc.gg/emerald-dev
|
# Discord Support: https://discord.gg/emeraldsrv
|
||||||
# Official Documentation: https://emeraldsrv.dev/wiki
|
# Official Documentation: https://docs.emeraldsrv.com
|
||||||
# Third-Party Documentation: https://emeraldsrv.dev/third-party
|
# Addons Marketplace: https://community.emeraldsrv.com
|
||||||
#
|
#
|
||||||
# Command Configuration
|
# Commands Configuration (commands.yml)
|
||||||
|
|
||||||
# If you don't want to mess with this configuration, You can manage commands via Discord Settings the Intregations.
|
|
||||||
|
|
||||||
Help:
|
Help:
|
||||||
Command: "help"
|
Command: "help"
|
||||||
@@ -99,6 +97,32 @@ Ping:
|
|||||||
Permission: ["SendMessages"]
|
Permission: ["SendMessages"]
|
||||||
Enabled: true
|
Enabled: true
|
||||||
|
|
||||||
|
# ---------------------------------------
|
||||||
|
# Bot Moderator Commands
|
||||||
|
# ---------------------------------------
|
||||||
|
|
||||||
|
Mod:
|
||||||
|
Command: "mod"
|
||||||
|
Description: "Execute a moderation action."
|
||||||
|
Permission: ["BanMembers", "KickMembers"]
|
||||||
|
Enabled: true
|
||||||
|
|
||||||
|
TicketBlacklist:
|
||||||
|
Command: "ticket-blacklist"
|
||||||
|
Description: "Blacklist a user from creating Tickets."
|
||||||
|
|
||||||
|
Add:
|
||||||
|
Command: "add"
|
||||||
|
Description: "Add a user to the blacklist."
|
||||||
|
|
||||||
|
Remove:
|
||||||
|
Command: "remove"
|
||||||
|
Description: "Remove a user from the blacklist."
|
||||||
|
|
||||||
|
View:
|
||||||
|
Command: "view"
|
||||||
|
Description: "View all blacklisted users."
|
||||||
|
|
||||||
# ---------------------------------------
|
# ---------------------------------------
|
||||||
# Bot Administrator Commands
|
# Bot Administrator Commands
|
||||||
# ---------------------------------------
|
# ---------------------------------------
|
||||||
|
|||||||
+101
-9
@@ -7,11 +7,15 @@
|
|||||||
# SupportBot created by Emerald Services
|
# SupportBot created by Emerald Services
|
||||||
# Installed with MIT License
|
# Installed with MIT License
|
||||||
#
|
#
|
||||||
# Discord Support: https://dsc.gg/emerald-dev
|
# Discord Support: https://discord.gg/emeraldsrv
|
||||||
# Official Documentation: https://emeraldsrv.com/wiki
|
# Official Documentation: https://docs.emeraldsrv.com
|
||||||
# Third-Party Documentation: https://emeraldsrv.com/third-party
|
# Addons Marketplace: https://community.emeraldsrv.com
|
||||||
#
|
#
|
||||||
# Messages Configuration
|
# Messages Configuration (messages.yml)
|
||||||
|
|
||||||
|
# ----------------------------
|
||||||
|
# Info Command
|
||||||
|
# ----------------------------
|
||||||
|
|
||||||
Info:
|
Info:
|
||||||
Title: "About Us"
|
Title: "About Us"
|
||||||
@@ -20,6 +24,10 @@ Info:
|
|||||||
URL: "https://docs.emeraldsrv.com"
|
URL: "https://docs.emeraldsrv.com"
|
||||||
Colour: "#f74343"
|
Colour: "#f74343"
|
||||||
|
|
||||||
|
# ----------------------------
|
||||||
|
# Welcome/Leave Messages
|
||||||
|
# ----------------------------
|
||||||
|
|
||||||
Welcome:
|
Welcome:
|
||||||
Embed:
|
Embed:
|
||||||
Colour: "#f74343"
|
Colour: "#f74343"
|
||||||
@@ -40,12 +48,20 @@ Leave:
|
|||||||
ImageEnabled: false
|
ImageEnabled: false
|
||||||
ImageURL: "https://animagelink.png/"
|
ImageURL: "https://animagelink.png/"
|
||||||
|
|
||||||
|
# ----------------------------
|
||||||
|
# Suggestion Messages
|
||||||
|
# ----------------------------
|
||||||
|
|
||||||
Suggestions:
|
Suggestions:
|
||||||
Sent_Title: "Suggestion Sent!"
|
Sent_Title: "Suggestion Sent!"
|
||||||
Sent: ":white_check_mark: **You have successfully sent a suggestion.**"
|
Sent: ":white_check_mark: **You have successfully sent a suggestion.**"
|
||||||
NoSelfVote: ":x: **You cannot vote on your own suggestion.**"
|
NoSelfVote: ":x: **You cannot vote on your own suggestion.**"
|
||||||
RemoveVoteButton: "Remove Vote"
|
RemoveVoteButton: "Remove Vote"
|
||||||
|
|
||||||
|
# ----------------------------
|
||||||
|
# Ticket Messages
|
||||||
|
# ----------------------------
|
||||||
|
|
||||||
Ticket:
|
Ticket:
|
||||||
|
|
||||||
# Available Placeholders:
|
# Available Placeholders:
|
||||||
@@ -95,6 +111,44 @@ Ticket:
|
|||||||
ReassigningTitle: "Reassigning!..."
|
ReassigningTitle: "Reassigning!..."
|
||||||
ReassigningMessage: "<@%user%> did not claim the ticket. **Reassigning...**"
|
ReassigningMessage: "<@%user%> did not claim the ticket. **Reassigning...**"
|
||||||
|
|
||||||
|
TicketQuestions:
|
||||||
|
Details:
|
||||||
|
Title: "Ticket Details"
|
||||||
|
Description: "Questions by <@%user%>" # Use <@%user%> to display the username.
|
||||||
|
|
||||||
|
# ----------------------------
|
||||||
|
# Review Messages
|
||||||
|
# ----------------------------
|
||||||
|
|
||||||
|
ReviewSystem:
|
||||||
|
Rate:
|
||||||
|
Title: "Review us!"
|
||||||
|
Description: "Please rate our support for this ticket (1-5 stars)."
|
||||||
|
|
||||||
|
Stars:
|
||||||
|
One: "⭐ 1 Star"
|
||||||
|
Two: "⭐⭐ 2 Stars"
|
||||||
|
Three: "⭐⭐⭐ 3 Stars"
|
||||||
|
Four: "⭐⭐⭐⭐ 4 Stars"
|
||||||
|
Five: "⭐⭐⭐⭐⭐ 5 Stars"
|
||||||
|
|
||||||
|
Comment:
|
||||||
|
Title: "Write a comment."
|
||||||
|
Description: "Write a comment about your experience."
|
||||||
|
|
||||||
|
ReviewEmbed:
|
||||||
|
Title: "Ticket Review"
|
||||||
|
CommentTitle: "Comment"
|
||||||
|
RatingTitle: "Rating"
|
||||||
|
ReviewedStaffTitle: "Reviewed Staff:"
|
||||||
|
ReviewedByTitle: "Reviewed By:"
|
||||||
|
ReviewEmoji: "⭐"
|
||||||
|
Color: "#f7a943"
|
||||||
|
|
||||||
|
# ----------------------------
|
||||||
|
# Profile Messages
|
||||||
|
# ----------------------------
|
||||||
|
|
||||||
TicketStats:
|
TicketStats:
|
||||||
OpenTickets: "✅ Open Tickets"
|
OpenTickets: "✅ Open Tickets"
|
||||||
TotalTickets: "🎫 Total Tickets"
|
TotalTickets: "🎫 Total Tickets"
|
||||||
@@ -102,10 +156,46 @@ TicketStats:
|
|||||||
|
|
||||||
Title: "Your Ticket Stats"
|
Title: "Your Ticket Stats"
|
||||||
|
|
||||||
|
# ----------------------------
|
||||||
|
# Mod Command Messages
|
||||||
|
# ----------------------------
|
||||||
|
|
||||||
|
Mod:
|
||||||
|
|
||||||
|
TicketBlacklist:
|
||||||
|
Add:
|
||||||
|
Description: "Add a user to the ticket blacklist"
|
||||||
|
UserDescription: "User to blacklist"
|
||||||
|
AlreadyBlacklisted: ":x: {userTag} is already blacklisted."
|
||||||
|
Success: ":white_check_mark: {userTag} has been added to the blacklist. They can no longer use the ticket system."
|
||||||
|
|
||||||
|
Remove:
|
||||||
|
Description: "Remove a user from the ticket blacklist"
|
||||||
|
UserDescription: "User to remove from blacklist"
|
||||||
|
NotBlacklisted: ":x: {userTag} is not blacklisted."
|
||||||
|
Success: ":white_check_mark: {userTag} has been removed from the blacklist. They can now use the ticket system again."
|
||||||
|
|
||||||
|
View:
|
||||||
|
Description: "View the list of all blacklisted users"
|
||||||
|
NoBlacklistedUsers: ":information_source: There are currently no users on the blacklist."
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------
|
||||||
|
# Logging Messages
|
||||||
|
# ----------------------------
|
||||||
|
|
||||||
TicketLog:
|
TicketLog:
|
||||||
Title: "Ticket Log"
|
Title: "Ticket Log"
|
||||||
Colour: "#0cc559"
|
Colour: "#0cc559"
|
||||||
|
|
||||||
|
TicketBlacklistLog:
|
||||||
|
Title: "Ticket Blacklist Log"
|
||||||
|
Colour: "#0cc559"
|
||||||
|
|
||||||
|
# ----------------------------
|
||||||
|
# Misc Messages
|
||||||
|
# ----------------------------
|
||||||
|
|
||||||
AddUser:
|
AddUser:
|
||||||
NotInTicket_Title: "Ticket Invitation"
|
NotInTicket_Title: "Ticket Invitation"
|
||||||
NotInTicket_Description: "This command can only be used within a ticket thread."
|
NotInTicket_Description: "This command can only be used within a ticket thread."
|
||||||
@@ -171,11 +261,13 @@ ForceAddUser:
|
|||||||
Error_Adding: "There was an error adding the user to the ticket."
|
Error_Adding: "There was an error adding the user to the ticket."
|
||||||
|
|
||||||
Error:
|
Error:
|
||||||
IncorrectPerms: ":x: **Err!** You do not have the correct permissions to use this."
|
IncorrectPermissions: ":x: **Error!** You do not have the correct permissions to use this command."
|
||||||
InvalidChannel: ":x: **Err!** Channel not found. Please create it!"
|
MissingChannel: ":x: **Error!** Required channel not found. Please ensure it is set up correctly!"
|
||||||
UserNotFound: ":x: **Err!** This user doesn't exist, Are they in this server?"
|
UserNotFound: ":x: **Error!** Specified user not found. Please check if they are in this server."
|
||||||
NoValidTicket: ":warning: You cannot execute this command here."
|
CommandNotAllowedHere: ":warning: This command cannot be used in this channel."
|
||||||
TicketExists: ":warning: This ticket cannot be opened, It already exists!"
|
EntryAlreadyExists: ":warning: Cannot complete the action, as the entry already exists."
|
||||||
|
EntryDoesNotExist: ":warning: Cannot complete the action, as the entry does not exist."
|
||||||
|
ActionFailed: ":x: **Error!** An error occurred while processing your request. Please try again later."
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+88
-41
@@ -4,15 +4,16 @@
|
|||||||
# <___/`___|| _/| _/\___/|_| |_| |___/\___/ |_|
|
# <___/`___|| _/| _/\___/|_| |_| |___/\___/ |_|
|
||||||
# |_| |_|
|
# |_| |_|
|
||||||
#
|
#
|
||||||
# SupportBot created by Emerald Development
|
# SupportBot created by Emerald Services
|
||||||
# Installed with MIT License
|
# Installed with MIT License
|
||||||
#
|
#
|
||||||
# Discord Support: https://dsc.gg/emerald-dev
|
# Discord Support: https://discord.gg/emeraldsrv
|
||||||
# Official Documentation: https://github.com/Emerald-Services/SupportBot/wiki
|
# Official Documentation: https://docs.emeraldsrv.com
|
||||||
# Third-Party Documentation: COMING SOON
|
# Addons Marketplace: https://community.emeraldsrv.com
|
||||||
|
#
|
||||||
|
# SupportBot Configuration (supportbot.yml)
|
||||||
|
|
||||||
SupportBot_Version: "v8.2"
|
SupportBot_Version: "v8.3"
|
||||||
# Do not change the version number
|
|
||||||
|
|
||||||
# -------------------------
|
# -------------------------
|
||||||
# General
|
# General
|
||||||
@@ -20,8 +21,7 @@ SupportBot_Version: "v8.2"
|
|||||||
|
|
||||||
General:
|
General:
|
||||||
Name: "SupportBot"
|
Name: "SupportBot"
|
||||||
Token: "BOT_TOKEN_HERE"
|
Token: "BOT_TOKEN"
|
||||||
# To get your Discord Bot Token, Go to https://discord.com/developers/applications
|
|
||||||
|
|
||||||
Addons:
|
Addons:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
@@ -33,11 +33,16 @@ General:
|
|||||||
Activity:
|
Activity:
|
||||||
Status: "SupportBot v8"
|
Status: "SupportBot v8"
|
||||||
Type: "Competing"
|
Type: "Competing"
|
||||||
# The Type can either be Playing, Watching, Listening or Competing
|
|
||||||
StreamingURL: "https://www.twitch.tv/YOUR_TWITCH_NAME"
|
StreamingURL: "https://www.twitch.tv/YOUR_TWITCH_NAME"
|
||||||
# Set The Type to "Streaming" for a streaming status!
|
|
||||||
|
|
||||||
# You can update the activity via Discord using: /settings
|
# Available Types:
|
||||||
|
# Playing
|
||||||
|
# Watching
|
||||||
|
# Listening
|
||||||
|
# Competing
|
||||||
|
# Streaming (URL required)
|
||||||
|
|
||||||
|
# You can also update the activity via Discord using: /settings
|
||||||
|
|
||||||
# -------------------------
|
# -------------------------
|
||||||
# Embeds
|
# Embeds
|
||||||
@@ -45,13 +50,15 @@ Activity:
|
|||||||
|
|
||||||
Embed:
|
Embed:
|
||||||
Colours:
|
Colours:
|
||||||
General: 0x0cc559 # 0x<hexcode>
|
General: 0x0cc559
|
||||||
Success: 0x04b545 # 0x<hexcode>
|
Success: 0x04b545
|
||||||
Error: 0xd13030 # 0x<hexcode>
|
Error: 0xd13030
|
||||||
Warn: 0xfca117 # 0x<hexcode>
|
Warn: 0xfca117
|
||||||
|
|
||||||
Footer: "SupportBot | Developed by Emerald Development"
|
Footer: "SupportBot | Developed by Emerald Development"
|
||||||
|
|
||||||
|
# Use 0x<hexcode> to change the colour of the embeds.
|
||||||
|
|
||||||
# -------------------------
|
# -------------------------
|
||||||
# Roles
|
# Roles
|
||||||
# -------------------------
|
# -------------------------
|
||||||
@@ -62,12 +69,14 @@ Roles:
|
|||||||
Role: "ROLE_ID"
|
Role: "ROLE_ID"
|
||||||
|
|
||||||
StaffMember:
|
StaffMember:
|
||||||
Staff: "STAFF_ROLE_ID"
|
Staff: "ROLE_ID"
|
||||||
Admin: "ADMIN_ROLE_ID"
|
Admin: "ROLE_ID"
|
||||||
|
Moderator: "ROLE_ID"
|
||||||
|
|
||||||
ModRoles:
|
Mod:
|
||||||
Muted: "MUTED_ROLE_ID"
|
AllowSupportStaff: false
|
||||||
Blacklisted: "BLACKLISTED_ROLE_ID"
|
|
||||||
|
# Allow Support Staff to use the /mod command. (More in detail customisation in commands.yml)
|
||||||
|
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
# System Messages
|
# System Messages
|
||||||
@@ -75,18 +84,18 @@ Roles:
|
|||||||
|
|
||||||
Welcome:
|
Welcome:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
Channel: "WELCOME_CHANNEL_ID"
|
Channel: "JOIN_CHANNEL_ID"
|
||||||
|
|
||||||
Leave:
|
Leave:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
Channel: "LEAVE_CHANNEL_ID"
|
Channel: "JOIN_CHANNEL_ID"
|
||||||
|
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
# Suggestions
|
# Suggestions
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
|
|
||||||
Suggestions:
|
Suggestions:
|
||||||
Channel: "SUGGEST_CHANNEL_ID"
|
Channel: "SUGGESTIONS_CHANNEL_ID"
|
||||||
UpvoteEmoji: "✅"
|
UpvoteEmoji: "✅"
|
||||||
DownvoteEmoji: "❌"
|
DownvoteEmoji: "❌"
|
||||||
|
|
||||||
@@ -94,6 +103,7 @@ Suggestions:
|
|||||||
DownvoteTitle: "No"
|
DownvoteTitle: "No"
|
||||||
|
|
||||||
OwnSuggestion: true
|
OwnSuggestion: true
|
||||||
|
|
||||||
# If false this will not allow the user to vote for a suggestion they created
|
# If false this will not allow the user to vote for a suggestion they created
|
||||||
# If true this will allow the user to vote for a suggestion they created
|
# If true this will allow the user to vote for a suggestion they created
|
||||||
|
|
||||||
@@ -120,23 +130,27 @@ Suggestions:
|
|||||||
# Tickets
|
# Tickets
|
||||||
# -------------------------
|
# -------------------------
|
||||||
|
|
||||||
Ticket:
|
VoiceTickets:
|
||||||
TicketType: "channels" # Choose how you want tickets to be created.
|
Name: "%username%'s VC"
|
||||||
# Options:
|
Category: "VOICEE_CATEGORY_ID"
|
||||||
# channels - Tickets will be created as channels in the
|
|
||||||
# threads - Tickets will be created as threads through the "TicketHome"
|
Ticket:
|
||||||
|
TicketType: "channels"
|
||||||
|
|
||||||
|
# Available TicketTypes:
|
||||||
|
# channels - Tickets will be created as channels in the TicketChannelsCategory
|
||||||
|
# threads - Tickets will be created as threads through the "TicketHome" channel.
|
||||||
|
|
||||||
# For TicketType "Threads"
|
|
||||||
TicketHome: "TICKETHOME_CHANNEL_ID"
|
TicketHome: "TICKETHOME_CHANNEL_ID"
|
||||||
|
|
||||||
# What is TicketHome?
|
# What is TicketHome?
|
||||||
# - Channel where the Ticket creation panel is sent.
|
# - Channel where the Ticket creation panel is sent.
|
||||||
# - Channel where ticket threads are made. (only if ticket threads is enabled above.)
|
# - Channel where ticket threads are made. (only if ticket threads is enabled above.)
|
||||||
|
|
||||||
# For TicketType "Channels"
|
# For TicketType: channels
|
||||||
TicketChannelsCategory: "TICKET_CATEGORY_ID" # if Type is set to channels
|
TicketChannelsCategory: "TICKETCHANNEL_CATEGORY_ID"
|
||||||
TicketChannelsCategory2: "BACKUP_TICKET_CATEGORY_ID" # This will be used if the first category has reached the max channel limit.
|
TicketChannelsCategory2: "TICKETCHANNEL_BACKUP_CATEGORY_ID" # This will be used if the first category has reached the max channel limit.
|
||||||
TicketArchiveCategory: "ARCHIVE_TICKET_CATEGORY_ID"
|
TicketArchiveCategory: "ARCHIVE_CATEGORY_ID"
|
||||||
|
|
||||||
# ---
|
# ---
|
||||||
|
|
||||||
Channel: "ticket-" # The prefix for ticket channel names
|
Channel: "ticket-" # The prefix for ticket channel names
|
||||||
@@ -144,16 +158,16 @@ Ticket:
|
|||||||
DMTranscripts: true
|
DMTranscripts: true
|
||||||
TicketReason: true
|
TicketReason: true
|
||||||
# If ticket reason is set to false, It will not prompt to create a reason
|
# If ticket reason is set to false, It will not prompt to create a reason
|
||||||
|
|
||||||
TicketSubject: "description"
|
TicketSubject: "description"
|
||||||
|
|
||||||
|
# Available TicketSubjects:
|
||||||
# "description" will display the ticket reason within the channel description
|
# "description" will display the ticket reason within the channel description
|
||||||
# "embed" will display the ticket reason within the ticket embed
|
# "embed" will display the ticket reason within the ticket embed
|
||||||
|
|
||||||
Timeout: 5
|
Timeout: 5
|
||||||
# Set to true to allow all staff to access the ticket
|
# Set to true to allow all staff to access the ticket
|
||||||
|
|
||||||
Log:
|
|
||||||
TicketDataLog: "TICKET_LOG_ID"
|
|
||||||
TickeDataTitle: "Ticket Data"
|
|
||||||
|
|
||||||
Close:
|
Close:
|
||||||
StaffOnly: true
|
StaffOnly: true
|
||||||
# Set to false is anyone is able to close a ticket.
|
# Set to false is anyone is able to close a ticket.
|
||||||
@@ -164,7 +178,7 @@ Ticket:
|
|||||||
|
|
||||||
Title: "Are you sure you want to close this ticket?"
|
Title: "Are you sure you want to close this ticket?"
|
||||||
|
|
||||||
# Available Button Styles
|
# Available Button Styles:
|
||||||
# 1 - Blurple
|
# 1 - Blurple
|
||||||
# 2 - Grey
|
# 2 - Grey
|
||||||
# 3 - Green
|
# 3 - Green
|
||||||
@@ -176,7 +190,7 @@ Ticket:
|
|||||||
ClaimTickets:
|
ClaimTickets:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
Channel: "CLAIM_TICKETS_CHANNEL_ID"
|
Channel: "CLAIM_TICKETS_CHANNEL_ID"
|
||||||
# Channel name or Channel ID
|
# Channel where the ticket assignments are sent for staff to claim.
|
||||||
|
|
||||||
ClaimAcceptTime: 120000
|
ClaimAcceptTime: 120000
|
||||||
# How long the assigned user has to accept the ticket before it gets re-assigned to someone else.
|
# How long the assigned user has to accept the ticket before it gets re-assigned to someone else.
|
||||||
@@ -196,10 +210,34 @@ Ticket:
|
|||||||
# 3 - Green
|
# 3 - Green
|
||||||
# 4 - Red
|
# 4 - Red
|
||||||
|
|
||||||
|
ReviewSystem:
|
||||||
|
Enabled: true
|
||||||
|
Channel: "REVIEW_CHANNEL_ID"
|
||||||
|
UseModal: true
|
||||||
|
|
||||||
|
Questions:
|
||||||
|
Enabled: true # Set to false to disable ticket questions
|
||||||
|
List:
|
||||||
|
- "Please provide your account email?"
|
||||||
|
- "What is the nature of your issue?"
|
||||||
|
- "Have you tried resolving the issue yourself?"
|
||||||
|
|
||||||
|
Log:
|
||||||
|
TicketDataLog: "TICKET_DATA_LOG_ID"
|
||||||
|
TickeDataTitle: "Ticket Data"
|
||||||
|
TicketBlacklistLog: "TICKET_BLACKLIST_LOG_ID"
|
||||||
|
|
||||||
# -------------------------
|
# -------------------------
|
||||||
# Buttons
|
# Buttons
|
||||||
# -------------------------
|
# -------------------------
|
||||||
Buttons:
|
Buttons:
|
||||||
|
General:
|
||||||
|
Delete: "🗑️"
|
||||||
|
DeleteStyle: "4"
|
||||||
|
|
||||||
|
Voice:
|
||||||
|
TicketDeleteText: "Delete Voice Channel"
|
||||||
|
|
||||||
Tickets:
|
Tickets:
|
||||||
ClaimEmoji: "🧑💻"
|
ClaimEmoji: "🧑💻"
|
||||||
ClaimStyle: "1"
|
ClaimStyle: "1"
|
||||||
@@ -230,13 +268,22 @@ SelectMenus:
|
|||||||
ArchiveEmoji: "♻️"
|
ArchiveEmoji: "♻️"
|
||||||
LockEmoji: "🔐"
|
LockEmoji: "🔐"
|
||||||
RenameEmoji: "🏷️"
|
RenameEmoji: "🏷️"
|
||||||
|
VCEmoji: "🔊"
|
||||||
|
|
||||||
EnableInvitesEmoji: "📤"
|
EnableInvitesEmoji: "📤"
|
||||||
DisableInvitesEmoji: "📥"
|
DisableInvitesEmoji: "📥"
|
||||||
|
|
||||||
# -------------------------
|
# -------------------------
|
||||||
# Misc
|
# Logging
|
||||||
# -------------------------
|
# -------------------------
|
||||||
|
|
||||||
|
MessageDelete:
|
||||||
|
Channel: "MESSAGE_DELETE_CHANNEL_ID""
|
||||||
|
Colour: "#d92525"
|
||||||
|
|
||||||
|
MessageUpdate:
|
||||||
|
Channel: "MESSAGE_UPDATE_CHANNEL_ID"
|
||||||
|
Colour: "#f2a024"
|
||||||
|
|
||||||
Translate:
|
Translate:
|
||||||
TranslateLog: "TRANSLATE_LOG_ID"
|
TranslateLog: "TRANSLATE_LOG_ID"
|
||||||
@@ -7,8 +7,11 @@
|
|||||||
# SupportBot created by Emerald Services
|
# SupportBot created by Emerald Services
|
||||||
# Installed with MIT License
|
# Installed with MIT License
|
||||||
#
|
#
|
||||||
# Discord Support: https://emeraldsrv.dev/discord
|
# Discord Support: https://discord.gg/emeraldsrv
|
||||||
# Community Resources: https://emeraldsrv.dev/resources
|
# Official Documentation: https://docs.emeraldsrv.com
|
||||||
|
# Addons Marketplace: https://community.emeraldsrv.com
|
||||||
|
#
|
||||||
|
# Ticket Creation Panel Configuration (ticket-panel.yml)
|
||||||
|
|
||||||
# -------------------------------
|
# -------------------------------
|
||||||
# Ticket Panel Configuration
|
# Ticket Panel Configuration
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"blacklistedUsers": []
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"tickets": [
|
"tickets": [
|
||||||
|
{}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"id": 0,
|
"id": 0,
|
||||||
"TicketPanelID": "0000000000000000000"
|
"TicketPanelID": "000000000000000"
|
||||||
}
|
}
|
||||||
+141
-1
@@ -130,7 +130,6 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (interaction.isStringSelectMenu()) {
|
if (interaction.isStringSelectMenu()) {
|
||||||
if (interaction.customId === "ticketcontrolpanel") {
|
if (interaction.customId === "ticketcontrolpanel") {
|
||||||
const selectedOption = interaction.values[0];
|
const selectedOption = interaction.values[0];
|
||||||
@@ -146,6 +145,89 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "supportvc":
|
||||||
|
const ticketData = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8"));
|
||||||
|
const ticketInfo = ticketData.tickets.find(t => t.id === interaction.channel.id);
|
||||||
|
|
||||||
|
if (ticketInfo) {
|
||||||
|
const user = interaction.guild.members.cache.get(ticketInfo.user) || ticketInfo.user;
|
||||||
|
const claimedBy = interaction.guild.members.cache.get(ticketInfo.claimedBy) || ticketInfo.claimedBy;
|
||||||
|
const Admin = await interaction.guild.roles.fetch(supportbot.Roles.StaffMember.Admin);
|
||||||
|
const Staff = await interaction.guild.roles.fetch(supportbot.Roles.StaffMember.Staff);
|
||||||
|
|
||||||
|
const VCcategory = interaction.guild.channels.cache.find(
|
||||||
|
(c) => c.name === supportbot.VoiceTickets.Category || c.id === supportbot.VoiceTickets.Category
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!VCcategory) {
|
||||||
|
return interaction.reply({
|
||||||
|
content: "The Support VC category does not exist!",
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let username = user ? user.user.username : "Unknown User";
|
||||||
|
|
||||||
|
let SupportVoice = await interaction.guild.channels.create({
|
||||||
|
name: supportbot.VoiceTickets.Name.replace(/%username%/g, username),
|
||||||
|
type: Discord.ChannelType.GuildVoice,
|
||||||
|
parent: VCcategory,
|
||||||
|
permissionOverwrites: [
|
||||||
|
{
|
||||||
|
id: user,
|
||||||
|
allow: [
|
||||||
|
Discord.PermissionsBitField.Flags.Connect,
|
||||||
|
Discord.PermissionsBitField.Flags.Speak,
|
||||||
|
Discord.PermissionsBitField.Flags.ViewChannel,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: Staff,
|
||||||
|
allow: [
|
||||||
|
Discord.PermissionsBitField.Flags.Connect,
|
||||||
|
Discord.PermissionsBitField.Flags.Speak,
|
||||||
|
Discord.PermissionsBitField.Flags.ViewChannel,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: Admin,
|
||||||
|
allow: [
|
||||||
|
Discord.PermissionsBitField.Flags.Connect,
|
||||||
|
Discord.PermissionsBitField.Flags.Speak,
|
||||||
|
Discord.PermissionsBitField.Flags.ViewChannel,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: interaction.guild.id,
|
||||||
|
deny: [Discord.PermissionsBitField.Flags.ViewChannel],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
ticketInfo.voiceChannelId = SupportVoice.id;
|
||||||
|
|
||||||
|
fs.writeFileSync("./Data/TicketData.json", JSON.stringify(ticketData, null, 2));
|
||||||
|
|
||||||
|
const VCDeleteButton = new Discord.ButtonBuilder()
|
||||||
|
.setCustomId("deleteSupportVC")
|
||||||
|
.setLabel(supportbot.Buttons.Voice.TicketDeleteText)
|
||||||
|
.setEmoji(supportbot.Buttons.General.Delete)
|
||||||
|
.setStyle(supportbot.Buttons.General.DeleteStyle);
|
||||||
|
|
||||||
|
const VCDeleteRow = new Discord.ActionRowBuilder().addComponents(VCDeleteButton);
|
||||||
|
|
||||||
|
const vcmadeembed = new Discord.EmbedBuilder()
|
||||||
|
.setDescription(`> **Support VC Created:** <#${SupportVoice.id}>`)
|
||||||
|
.setColor(supportbot.Embed.Colours.Success);
|
||||||
|
|
||||||
|
await interaction.reply({
|
||||||
|
embeds: [vcmadeembed],
|
||||||
|
components: [VCDeleteRow],
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case "archiveticket":
|
case "archiveticket":
|
||||||
const arcembed = new Discord.EmbedBuilder()
|
const arcembed = new Discord.EmbedBuilder()
|
||||||
.setDescription(msgconfig.Ticket.TicketArchived)
|
.setDescription(msgconfig.Ticket.TicketArchived)
|
||||||
@@ -306,6 +388,64 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (interaction.customId === 'deleteSupportVC') {
|
||||||
|
const ticketData = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8"));
|
||||||
|
const ticketInfo = ticketData.tickets.find(t => t.id === interaction.channel.id);
|
||||||
|
|
||||||
|
if (ticketInfo && ticketInfo.voiceChannelId) {
|
||||||
|
try {
|
||||||
|
const voiceChannel = await interaction.guild.channels.fetch(ticketInfo.voiceChannelId).catch(() => null);
|
||||||
|
|
||||||
|
if (voiceChannel) {
|
||||||
|
await voiceChannel.delete();
|
||||||
|
|
||||||
|
const successEmbed = new Discord.EmbedBuilder()
|
||||||
|
.setDescription('✅ Support voice channel has been successfully deleted!')
|
||||||
|
.setColor(supportbot.Embed.Colours.Success);
|
||||||
|
|
||||||
|
await interaction.reply({
|
||||||
|
embeds: [successEmbed],
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
ticketInfo.voiceChannelId = null;
|
||||||
|
fs.writeFileSync("./Data/TicketData.json", JSON.stringify(ticketData, null, 2));
|
||||||
|
} else {
|
||||||
|
const errorEmbed = new Discord.EmbedBuilder()
|
||||||
|
.setDescription('❌ The associated voice channel could not be found.')
|
||||||
|
.setColor(supportbot.Embed.Colours.Error);
|
||||||
|
|
||||||
|
await interaction.reply({
|
||||||
|
embeds: [errorEmbed],
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting the Voice Channel:', error);
|
||||||
|
|
||||||
|
const errorEmbed = new Discord.EmbedBuilder()
|
||||||
|
.setDescription('❌ **There was an error trying to delete this voice channel. Please try again later.**')
|
||||||
|
.setColor(supportbot.Embed.Colours.Error);
|
||||||
|
|
||||||
|
await interaction.reply({
|
||||||
|
embeds: [errorEmbed],
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const errorEmbed = new Discord.EmbedBuilder()
|
||||||
|
.setDescription('⚠️ This button is not linked to a valid voice channel.')
|
||||||
|
.setColor(supportbot.Embed.Colours.Warn);
|
||||||
|
|
||||||
|
await interaction.reply({
|
||||||
|
embeds: [errorEmbed],
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (interaction.isButton()) {
|
if (interaction.isButton()) {
|
||||||
if (interaction.customId === "unarchiveTicket") {
|
if (interaction.customId === "unarchiveTicket") {
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
const { EmbedBuilder } = require('discord.js');
|
||||||
|
const fs = require("fs");
|
||||||
|
const yaml = require("js-yaml");
|
||||||
|
const Event = require("../Structures/Event.js");
|
||||||
|
|
||||||
|
// Load the configuration files
|
||||||
|
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
|
||||||
|
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
|
||||||
|
|
||||||
|
module.exports = new Event("messageDelete", async (client, message) => {
|
||||||
|
|
||||||
|
// Check if the message is from a guild and not from a bot
|
||||||
|
if (!message.guild || message.author.bot) return;
|
||||||
|
|
||||||
|
// Get the delete log channel either by ID or by name
|
||||||
|
const deleteLogChannel = message.guild.channels.cache.get(supportbot.MessageDelete.Channel) ||
|
||||||
|
message.guild.channels.cache.find(channel => channel.name === supportbot.MessageDelete.Channel);
|
||||||
|
|
||||||
|
// Create the embed for the deleted message
|
||||||
|
const deletedMessageEmbed = new EmbedBuilder()
|
||||||
|
.setColor(supportbot.MessageDelete.Colour)
|
||||||
|
.setTitle("Message Deleted")
|
||||||
|
.setDescription(`> **Channel:** <#${message.channel.id}>\n> **Message ID** [${message.id}](https://discord.com/channels/${message.guild.id}/${message.channel.id}/${message.id})\n> **Message author:** ${message.author.tag} (${message.author.id})\n> **Message Crated:** <t:${Math.floor(message.createdTimestamp / 1000)}:F>`)
|
||||||
|
.addFields(
|
||||||
|
{ name: 'Message', value: message.content || '*No content*', inline: false }
|
||||||
|
)
|
||||||
|
.setThumbnail(message.author.displayAvatarURL())
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
// If the message contains an attachment, add it to the embed
|
||||||
|
if (message.attachments.size > 0) {
|
||||||
|
const attachment = message.attachments.first();
|
||||||
|
deletedMessageEmbed.addFields({ name: 'Attachment', value: `[${attachment.name}](${attachment.proxyURL})` });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the embed to the delete log channel if the channel is found
|
||||||
|
if (deleteLogChannel) {
|
||||||
|
deleteLogChannel.send({
|
||||||
|
embeds: [deletedMessageEmbed]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
const { EmbedBuilder } = require('discord.js');
|
||||||
|
const fs = require("fs");
|
||||||
|
const yaml = require("js-yaml");
|
||||||
|
const Event = require("../Structures/Event.js");
|
||||||
|
|
||||||
|
// Load the configuration files
|
||||||
|
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
|
||||||
|
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
|
||||||
|
|
||||||
|
module.exports = new Event("messageUpdate", async (client, oldMessage, newMessage) => {
|
||||||
|
|
||||||
|
// Check if the message is from a guild and not from a bot
|
||||||
|
if (!newMessage.guild || newMessage.author.bot) return;
|
||||||
|
|
||||||
|
// Fetch old message if it's not cached (important for message content)
|
||||||
|
if (!oldMessage.content && oldMessage.partial) {
|
||||||
|
try {
|
||||||
|
oldMessage = await oldMessage.fetch();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error fetching old message:', err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback if content is missing
|
||||||
|
const oldMessageContent = oldMessage.content || (oldMessage.attachments.size > 0 ? 'Contains attachment(s)' : '*No content*');
|
||||||
|
const newMessageContent = newMessage.content || (newMessage.attachments.size > 0 ? 'Contains attachment(s)' : '*No content*');
|
||||||
|
|
||||||
|
// Get the update log channel either by ID or by name
|
||||||
|
const updateLogChannel = newMessage.guild.channels.cache.get(supportbot.MessageUpdate.Channel) ||
|
||||||
|
newMessage.guild.channels.cache.find(channel => channel.name === supportbot.MessageUpdate.Channel);
|
||||||
|
|
||||||
|
// Create the embed for the updated message
|
||||||
|
const updatedMessageEmbed = new EmbedBuilder()
|
||||||
|
.setColor(supportbot.MessageUpdate.Colour)
|
||||||
|
.setTitle("Message Updated")
|
||||||
|
.setDescription(`> **Channel:** <#${newMessage.channel.id}>\n> **Message ID:** [${newMessage.id}](https://discord.com/channels/${newMessage.guild.id}/${newMessage.channel.id}/${newMessage.id})\n> **Message author:** ${newMessage.author.tag} (${newMessage.author.id})\n> **Message Created:** <t:${Math.floor(oldMessage.createdTimestamp / 1000)}:F>`)
|
||||||
|
.addFields(
|
||||||
|
{ name: 'Old Message', value: oldMessageContent, inline: false },
|
||||||
|
{ name: 'New Message', value: newMessageContent, inline: false }
|
||||||
|
)
|
||||||
|
.setThumbnail(newMessage.author.displayAvatarURL())
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
// If the new message contains an attachment, add it to the embed
|
||||||
|
if (newMessage.attachments.size > 0) {
|
||||||
|
const attachment = newMessage.attachments.first();
|
||||||
|
updatedMessageEmbed.addFields({ name: 'Attachment', value: `[${attachment.name}](${attachment.proxyURL})` });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the embed to the update log channel if the channel is found
|
||||||
|
if (updateLogChannel) {
|
||||||
|
updateLogChannel.send({
|
||||||
|
embeds: [updatedMessageEmbed]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
+1
-3
@@ -89,9 +89,7 @@ module.exports = new Event("ready", async (client, interaction) => {
|
|||||||
|
|
||||||
const roles = [
|
const roles = [
|
||||||
supportbot.Roles.StaffMember.Admin,
|
supportbot.Roles.StaffMember.Admin,
|
||||||
supportbot.Roles.StaffMember.Staff,
|
supportbot.Roles.StaffMember.Staff
|
||||||
supportbot.Roles.ModRoles.Blacklisted,
|
|
||||||
supportbot.Roles.ModRoles.Muted
|
|
||||||
];
|
];
|
||||||
|
|
||||||
if (supportbot.Roles.AutoRole.Role) roles.push(supportbot.Roles.AutoRole.Role);
|
if (supportbot.Roles.AutoRole.Role) roles.push(supportbot.Roles.AutoRole.Role);
|
||||||
|
|||||||
@@ -13,9 +13,10 @@ class Client extends Discord.Client {
|
|||||||
intents: [
|
intents: [
|
||||||
GatewayIntentBits.Guilds,
|
GatewayIntentBits.Guilds,
|
||||||
GatewayIntentBits.GuildMembers,
|
GatewayIntentBits.GuildMembers,
|
||||||
GatewayIntentBits.GuildMessages
|
GatewayIntentBits.GuildMessages,
|
||||||
|
GatewayIntentBits.MessageContent
|
||||||
],
|
],
|
||||||
partials: [Partials.Channel]
|
partials: [Partials.Message, Partials.Channel]
|
||||||
});
|
});
|
||||||
|
|
||||||
this.commands = new Discord.Collection();
|
this.commands = new Discord.Collection();
|
||||||
@@ -105,6 +106,10 @@ class Client extends Discord.Client {
|
|||||||
tempCommandFiles = tempCommandFiles.filter(item => item !== "settings.js");
|
tempCommandFiles = tempCommandFiles.filter(item => item !== "settings.js");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cmdconfig.Mod.Enabled === false) {
|
||||||
|
tempCommandFiles = tempCommandFiles.filter(item => item !== "mod.js");
|
||||||
|
}
|
||||||
|
|
||||||
const commandFiles = tempCommandFiles;
|
const commandFiles = tempCommandFiles;
|
||||||
const commands = commandFiles.map((file) => require(`../Commands/${file}`));
|
const commands = commandFiles.map((file) => require(`../Commands/${file}`));
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const supportbot = yaml.load(
|
|||||||
|
|
||||||
const Client = require("./Structures/Client.js");
|
const Client = require("./Structures/Client.js");
|
||||||
const client = new Client({
|
const client = new Client({
|
||||||
intents: ['Guilds', 'GuildMembers', 'GuildMessages']
|
intents: ['Guilds', 'GuildMembers', 'GuildMessages', 'MessageContent']
|
||||||
});
|
});
|
||||||
|
|
||||||
client.start(supportbot.General.Token);
|
client.start(supportbot.General.Token);
|
||||||
|
|||||||
Generated
-1671
File diff suppressed because it is too large.
Load diff
+3
-4
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "supportbot",
|
"name": "supportbot",
|
||||||
"version": "8.0.1",
|
"version": "8.3",
|
||||||
"description": "SupportBot is an open-sourced support system for discord. It's completely free to use with high amounts of customization to match your own brand.",
|
"description": "SupportBot is an open-sourced support system for discord. It's completely free to use with high amounts of customization to match your own brand.",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -16,17 +16,16 @@
|
|||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/Emerald-Services/SupportBot/issues"
|
"url": "https://github.com/Emerald-Services/SupportBot/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/Emerald-Services/SupportBot#readme",
|
"homepage": "https://community.emeraldsrv.dev",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@paypal/checkout-server-sdk": "^1.0.3",
|
"@paypal/checkout-server-sdk": "^1.0.3",
|
||||||
"@vitalets/google-translate-api": "^8.0.0",
|
"@vitalets/google-translate-api": "^8.0.0",
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
"chatgpt": "^5.2.5",
|
|
||||||
"discord.js": "^14.13.0",
|
"discord.js": "^14.13.0",
|
||||||
"js": "^0.1.0",
|
"js": "^0.1.0",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"moment": "^2.29.3",
|
"moment": "^2.29.3",
|
||||||
"ms": "^2.1.3",
|
"ms": "^2.1.3",
|
||||||
"openai": "^4.50.0",
|
"openai": "^4.50.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user