8.4 DEV 1

Changed JSON to SQLlite
Added Suggestion Forums
Added /suggestadmin
This commit is contained in:
1tzEmerald committed 2025-09-20 23:09:22 +01:00
1 parent c6db929d12
commit a05d65099b
40 files changed
+1562 -1522

No files matched your search

@@ -5,7 +5,7 @@ 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 Command = require("../Structures/Command.js");
const Command = require("../../Structures/Command.js");
module.exports = new Command({
name: cmdconfig.Help.Command,
@@ -55,7 +55,7 @@ module.exports = new Command({
});
interaction.reply({
ephemeral: true,
flags: Discord.MessageFlags.Ephemeral,
embeds: [HelpEmbed1],
});
},
@@ -18,7 +18,7 @@ const msgconfig = yaml.load(
fs.readFileSync("./Configs/messages.yml", "utf8")
);
const Command = require("../Structures/Command.js");
const Command = require("../../Structures/Command.js");
module.exports = new Command({
name: cmdconfig.Info.Command,
@@ -10,7 +10,7 @@ const supportbot = yaml.load(
);
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
const Command = require("../Structures/Command.js");
const Command = require("../../Structures/Command.js");
module.exports = new Command({
name: cmdconfig.Ping.Command,
+296
View File
@@ -0,0 +1,296 @@
const {
EmbedBuilder,
ModalBuilder,
TextInputBuilder,
TextInputStyle,
ApplicationCommandType,
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
ApplicationCommandOptionType
} = require("discord.js");
const fs = require("fs");
const yaml = require("js-yaml");
const db = require("../../Structures/Database.js");
const Command = require("../../Structures/Command.js");
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 clockedInUsers = new Set();
function updateClockedInStatus(embed, clockedIn) {
embed.data.fields = embed.data.fields.map(f =>
f.name === "Clocked In Status"
? { ...f, value: clockedIn ? "✅ Clocked In" : "❌ Clocked Out" }
: f
);
return embed;
}
module.exports = new Command({
name: cmdconfig.Profile.Command,
description: cmdconfig.Profile.Description,
type: ApplicationCommandType.ChatInput,
permissions: cmdconfig.Profile.Permission,
options: [
{
name: "user",
description: "The user to view the profile of",
type: ApplicationCommandOptionType.User,
required: false
}
],
async run(interaction) {
await interaction.deferReply({ ephemeral: true });
const viewingUser = interaction.options.getUser("user") || interaction.user;
const viewingUserId = viewingUser.id;
const interactionUserId = interaction.user.id;
const member = await interaction.guild.members.fetch(viewingUserId);
const { getRole } = interaction.client;
const Staff = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild);
const Admin = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild);
if (!Staff || !Admin) {
return interaction.editReply({
content: "Some roles seem to be missing!\nPlease check for errors when starting the bot.",
ephemeral: true
});
}
const isStaff = member.roles.cache.has(Staff.id) || member.roles.cache.has(Admin.id);
const isOwnProfile = interactionUserId === viewingUserId;
const profileData = db.getProfile(viewingUserId);
let { bio, timezone, clockedIn } = profileData;
clockedIn = Boolean(clockedIn);
let profileEmbed = new EmbedBuilder()
.setTitle(`${viewingUser.username}'s Profile`)
.setColor(supportbot.Embed.Colours.General)
.setThumbnail(viewingUser.displayAvatarURL())
.addFields(
{ name: "Bio", value: bio || "No bio set.", inline: false },
{ name: "Timezone", value: timezone || "No timezone set.", inline: true }
);
if (isStaff) {
profileEmbed.addFields({
name: "Clocked In Status",
value: clockedIn ? "✅ Clocked In" : "❌ Clocked Out",
inline: true
});
}
const buttonRow = new ActionRowBuilder();
if (isOwnProfile) {
buttonRow.addComponents(
new ButtonBuilder()
.setCustomId("editProfile")
.setLabel("Edit Profile")
.setStyle(ButtonStyle.Secondary)
);
if (isStaff) {
buttonRow.addComponents(
new ButtonBuilder()
.setCustomId("clockInOut")
.setLabel(clockedIn ? "Clock Out" : "Clock In")
.setStyle(ButtonStyle.Secondary)
);
}
}
if (isStaff && supportbot.Ticket.ClaimTickets.Enabled) {
buttonRow.addComponents(
new ButtonBuilder()
.setCustomId("viewTicketStats")
.setLabel("View Ticket Stats")
.setStyle(ButtonStyle.Secondary)
);
}
await interaction.editReply({
embeds: [profileEmbed],
components: buttonRow.components.length ? [buttonRow] : []
});
const filter = i =>
["editProfile", "clockInOut", "viewTicketStats", "showOpenTickets", "backToStats", "backToProfile"]
.includes(i.customId) &&
i.user.id === interactionUserId;
const collector = interaction.channel.createMessageComponentCollector({ filter, time: 60000 });
collector.on("collect", async i => {
if (i.customId === "editProfile") {
const modal = new ModalBuilder()
.setCustomId("editProfileModal")
.setTitle("Edit Profile")
.addComponents(
new ActionRowBuilder().addComponents(
new TextInputBuilder()
.setCustomId("bio")
.setLabel("Bio")
.setStyle(TextInputStyle.Paragraph)
.setPlaceholder("Enter your bio")
.setValue(String(bio || ""))
.setRequired(false)
),
new ActionRowBuilder().addComponents(
new TextInputBuilder()
.setCustomId("timezone")
.setLabel("Timezone")
.setStyle(TextInputStyle.Short)
.setPlaceholder("Enter your timezone")
.setValue(String(timezone || ""))
.setRequired(false)
)
);
return i.showModal(modal);
}
if (i.customId === "clockInOut") {
const newStatus = !clockedIn;
db.setClockedIn(viewingUserId, newStatus);
clockedIn = newStatus;
profileEmbed = updateClockedInStatus(profileEmbed, newStatus);
if (newStatus) clockedInUsers.add(viewingUserId);
else clockedInUsers.delete(viewingUserId);
const updatedRow = new ActionRowBuilder().addComponents(
new ButtonBuilder()
.setCustomId("editProfile")
.setLabel("Edit Profile")
.setStyle(ButtonStyle.Secondary)
);
if (isStaff) {
updatedRow.addComponents(
new ButtonBuilder()
.setCustomId("clockInOut")
.setLabel(newStatus ? "Clock Out" : "Clock In")
.setStyle(ButtonStyle.Secondary)
);
if (supportbot.Ticket.ClaimTickets.Enabled) {
updatedRow.addComponents(
new ButtonBuilder()
.setCustomId("viewTicketStats")
.setLabel("View Ticket Stats")
.setStyle(ButtonStyle.Secondary)
);
}
}
await i.update({ embeds: [profileEmbed], components: [updatedRow] });
const clockEmbed = new EmbedBuilder()
.setTitle(newStatus ? "Clocked In!" : "Clocked Out!")
.setDescription(newStatus ? "You have successfully clocked in." : "You have successfully clocked out.")
.setColor(supportbot.Embed.Colours.General);
return interaction.followUp({ embeds: [clockEmbed], ephemeral: true });
}
if (i.customId === "viewTicketStats") {
const TicketData = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8"));
const ticketsClaimed = TicketData.tickets.filter(t => t.claimedBy === viewingUserId);
const ticketsOpen = ticketsClaimed.filter(t => t.open);
const totalTickets = ticketsClaimed.length;
let totalResponseTime = 0;
let responseCount = 0;
ticketsClaimed.forEach(t => {
if (t.claimedAt && t.createdAt) {
totalResponseTime += new Date(t.claimedAt) - new Date(t.createdAt);
responseCount++;
}
});
const avgMinutes = responseCount ? Math.round(totalResponseTime / responseCount / 60000) : 0;
const statsEmbed = new EmbedBuilder()
.setTitle(msgconfig.TicketStats.Title)
.setDescription(`> Ticket stats for <@${viewingUserId}>`)
.setColor(supportbot.Embed.Colours.General)
.addFields(
{ name: msgconfig.TicketStats.OpenTickets, value: `${ticketsOpen.length}`, inline: true },
{ name: msgconfig.TicketStats.TotalTickets, value: `${totalTickets}`, inline: true },
{ name: msgconfig.TicketStats.ResponseTime, value: `${avgMinutes} minutes`, inline: false }
);
const statsRow = new ActionRowBuilder().addComponents(
new ButtonBuilder().setCustomId("showOpenTickets").setLabel("Show Open Tickets").setStyle(ButtonStyle.Secondary),
new ButtonBuilder().setCustomId("backToProfile").setLabel("Back to Profile").setStyle(ButtonStyle.Secondary)
);
return i.update({ embeds: [statsEmbed], components: [statsRow] });
}
if (i.customId === "showOpenTickets") {
const TicketData = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8"));
const ticketsOpen = TicketData.tickets.filter(t => t.claimedBy === viewingUserId && t.open);
const openEmbed = new EmbedBuilder()
.setTitle("Active Tickets")
.setDescription(ticketsOpen.map(t => `<#${t.id}>`).join("\n") || "No active tickets.")
.setColor(supportbot.Embed.Colours.General);
const backRow = new ActionRowBuilder().addComponents(
new ButtonBuilder().setCustomId("backToStats").setLabel("Back to Stats").setStyle(ButtonStyle.Secondary)
);
return i.update({ embeds: [openEmbed], components: [backRow] });
}
if (i.customId === "backToStats") {
const TicketData = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8"));
const ticketsClaimed = TicketData.tickets.filter(t => t.claimedBy === viewingUserId);
const ticketsOpen = ticketsClaimed.filter(t => t.open);
const totalTickets = ticketsClaimed.length;
let totalResponseTime = 0;
let responseCount = 0;
ticketsClaimed.forEach(t => {
if (t.claimedAt && t.createdAt) {
totalResponseTime += new Date(t.claimedAt) - new Date(t.createdAt);
responseCount++;
}
});
const avgMinutes = responseCount ? Math.round(totalResponseTime / responseCount / 60000) : 0;
const statsEmbed = new EmbedBuilder()
.setTitle(msgconfig.TicketStats.Title)
.setDescription(`> Ticket stats for <@${viewingUserId}>`)
.setColor(supportbot.Embed.Colours.General)
.addFields(
{ name: msgconfig.TicketStats.OpenTickets, value: `${ticketsOpen.length}`, inline: true },
{ name: msgconfig.TicketStats.TotalTickets, value: `${totalTickets}`, inline: true },
{ name: msgconfig.TicketStats.ResponseTime, value: `${avgMinutes} minutes`, inline: false }
);
const statsRow = new ActionRowBuilder().addComponents(
new ButtonBuilder().setCustomId("showOpenTickets").setLabel("Show Open Tickets").setStyle(ButtonStyle.Secondary),
new ButtonBuilder().setCustomId("backToProfile").setLabel("Back to Profile").setStyle(ButtonStyle.Secondary)
);
return i.update({ embeds: [statsEmbed], components: [statsRow] });
}
if (i.customId === "backToProfile") {
return i.update({
embeds: [profileEmbed],
components: buttonRow.components.length ? [buttonRow] : []
});
}
});
}
});
module.exports.clockedInUsers = clockedInUsers;
@@ -18,7 +18,7 @@ const msgconfig = yaml.load(
fs.readFileSync("./Configs/messages.yml", "utf8")
)
const Command = require("../Structures/Command.js");
const Command = require("../../Structures/Command.js");
const translate = require("@vitalets/google-translate-api");
module.exports = new Command({
@@ -89,7 +89,7 @@ module.exports = new Command({
"Click the title to see which 2 letter language codes are valid."
),
],
ephemeral: true,
flags: Discord.MessageFlags.Ephemeral,
});
}
});
+147
View File
@@ -0,0 +1,147 @@
const fs = require("fs");
const Discord = require("discord.js");
const yaml = require("js-yaml");
const path = require("path");
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");
const DB = require("../../Structures/Database.js");
module.exports = new Command({
name: "suggest",
description: cmdconfig.Suggestion?.Description || "Submit a new suggestion",
type: Discord.ApplicationCommandType.ChatInput,
options: [
{
name: "text",
description: "Your suggestion text",
type: Discord.ApplicationCommandOptionType.String,
required: true
}
],
async run(interaction) {
const suggestion = interaction.options.getString("text");
const mode = supportbot.Suggestions.Mode || "Channel";
const embed = new Discord.EmbedBuilder()
.addFields(
{ name: "Suggestion", value: suggestion, inline: true },
{ name: "From", value: `<@${interaction.user.id}>` },
{ name: `${supportbot.Suggestions.UpvoteEmoji} ${supportbot.Suggestions.UpvoteTitle}`, value: "0", inline: true },
{ name: `${supportbot.Suggestions.DownvoteEmoji} ${supportbot.Suggestions.DownvoteTitle}`, value: "0", inline: true }
)
.setThumbnail(interaction.user.displayAvatarURL())
.setColor(supportbot.Embed.Colours.General);
const row = new Discord.ActionRowBuilder().addComponents(
new Discord.ButtonBuilder()
.setCustomId("upvote")
.setEmoji(supportbot.Suggestions.UpvoteEmoji)
.setStyle(supportbot.Suggestions.Buttons.Upvote),
new Discord.ButtonBuilder()
.setCustomId("downvote")
.setEmoji(supportbot.Suggestions.DownvoteEmoji)
.setStyle(supportbot.Suggestions.Buttons.Downvote),
new Discord.ButtonBuilder()
.setCustomId("removevote")
.setLabel(supportbot.Suggestions.Buttons.RemoveVote_Title)
.setStyle(supportbot.Suggestions.Buttons.RemoveVote)
);
if (mode === "Forum") {
const forum = await interaction.guild.channels
.fetch(supportbot.Suggestions.ForumChannel)
.catch(() => null);
if (!forum || forum.type !== Discord.ChannelType.GuildForum) {
return interaction.reply({
embeds: [
new Discord.EmbedBuilder()
.setTitle("⚠ Missing Forum Channel")
.setDescription(msgconfig.Error.MissingChannel)
.setColor(supportbot.Embed.Colours.Error)
],
flags: Discord.MessageFlags.Ephemeral,
});
}
const appliedTags = supportbot.Suggestions.Tags?.Created
? [supportbot.Suggestions.Tags.Created]
: [];
const thread = await forum.threads.create({
name: suggestion.slice(0, 100),
message: {
content: "\u200B",
embeds: [embed],
components: [row]
},
appliedTags,
autoArchiveDuration: Discord.ThreadAutoArchiveDuration.OneDay,
reason: "New Suggestion"
});
embed.setFooter({
text: `${supportbot.Embed.Footer} | Suggestion ID: ${thread.id}`,
iconURL: interaction.user.displayAvatarURL()
});
const starter = await thread.fetchStarterMessage();
await starter.edit({ embeds: [embed], components: [row] });
DB.add(thread.id, interaction.user.id, suggestion);
return interaction.reply({
embeds: [
new Discord.EmbedBuilder()
.setTitle(msgconfig.Suggestions.Sent_Title)
.setDescription(`${msgconfig.Suggestions.Sent}`)
.addFields({ name: 'Posted in:', value: `<#${forum.id}>` })
.setColor(supportbot.Embed.Colours.Success)
],
flags: Discord.MessageFlags.Ephemeral,
});
}
// ---------- Channel Mode ----------
const channel = await interaction.guild.channels
.fetch(supportbot.Suggestions.Channel)
.catch(() => null);
if (!channel) {
return interaction.reply({
embeds: [
new Discord.EmbedBuilder()
.setTitle("⚠ Missing Suggestion Channel")
.setDescription(msgconfig.Error.MissingChannel)
.setColor(supportbot.Embed.Colours.Error)
],
flags: Discord.MessageFlags.Ephemeral,
});
}
const message = await channel.send({ embeds: [embed], components: [row] });
embed.setFooter({
text: `${supportbot.Embed.Footer} | Suggestion ID: ${message.id}`,
iconURL: interaction.user.displayAvatarURL()
});
await message.edit({ embeds: [embed], components: [row] });
DB.add(message.id, interaction.user.id, suggestion);
return interaction.reply({
embeds: [
new Discord.EmbedBuilder()
.setTitle(msgconfig.Suggestions.Sent_Title)
.setDescription(`${msgconfig.Suggestions.Sent}`)
.addFields({ name: 'Posted in:', value: `<#${channel.id}>` })
.setColor(supportbot.Embed.Colours.Success)
],
flags: Discord.MessageFlags.Ephemeral,
});
}
});
+183
View File
@@ -0,0 +1,183 @@
const Discord = require("discord.js");
const fs = require("fs");
const yaml = require("js-yaml");
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
const SuggestionDB = require("../../Structures/Database.js");
const Command = require("../../Structures/Command.js");
module.exports = new Command({
name: cmdconfig.SuggestionAdmin.Command,
description: cmdconfig.SuggestionAdmin.Description,
type: Discord.ApplicationCommandType.ChatInput,
permissions: cmdconfig.SuggestionAdmin.Permission,
options: [
{
name: "status",
description: "New status",
type: Discord.ApplicationCommandOptionType.String,
required: true,
choices: [
{ name: "Solved", value: "Solved" },
{ name: "Denied", value: "Denied" },
{ name: "Considering", value: "Considering" }
]
},
{
name: "id",
description: "Message ID (required in Channel mode)",
type: Discord.ApplicationCommandOptionType.String,
required: false
}
],
async run(interaction) {
const status = interaction.options.getString("status");
const idOption = interaction.options.getString("id");
const mode = supportbot.Suggestions.Mode || "Channel";
const { getRole } = interaction.client;
const staffRole = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild);
const adminRole = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild);
if (
!interaction.member.roles.cache.has(staffRole?.id) &&
!interaction.member.roles.cache.has(adminRole?.id)
) {
return interaction.reply({
embeds: [
new Discord.EmbedBuilder()
.setTitle("Invalid Permissions!")
.setDescription(
`${msgconfig.Error.IncorrectPerms}\nRequired: ${supportbot.Roles.StaffMember.Staff} or ${supportbot.Roles.StaffMember.Admin}`
)
.setColor(supportbot.Embed.Colours.Warn)
],
flags: Discord.MessageFlags.Ephemeral,
});
}
if (mode === "Forum") {
const thread = interaction.channel;
if (!thread.isThread() || thread.parentId !== supportbot.Suggestions.ForumChannel) {
return interaction.reply({
embeds: [
new Discord.EmbedBuilder()
.setTitle("Not a Suggestion Thread")
.setDescription(msgconfig.Error.MissingChannel)
.setColor(supportbot.Embed.Colours.Error)
],
flags: Discord.MessageFlags.Ephemeral,
});
}
const tagMap = {
Solved: supportbot.Suggestions.Tags.Solved,
Denied: supportbot.Suggestions.Tags.Denied,
Considering: supportbot.Suggestions.Tags.Considering
};
const tagId = tagMap[status];
if (!tagId) {
return interaction.reply({
embeds: [
new Discord.EmbedBuilder()
.setTitle("Tag Missing")
.setDescription("No forum tag configured for this status.")
.setColor(supportbot.Embed.Colours.Error)
],
flags: Discord.MessageFlags.Ephemeral,
});
}
await thread.setAppliedTags([tagId]).catch(() => null);
SuggestionDB.setStatus(thread.id, status);
return interaction.reply({
embeds: [
new Discord.EmbedBuilder()
.setTitle("Suggestion Updated")
.setDescription(`Status set to **${status}**.`)
.setColor(supportbot.Embed.Colours.Success)
],
flags: Discord.MessageFlags.Ephemeral,
});
}
if (!idOption) {
return interaction.reply({
embeds: [
new Discord.EmbedBuilder()
.setTitle("Message ID Required")
.setDescription("Provide the suggestion’s message ID when in Channel mode.")
.setColor(supportbot.Embed.Colours.Error)
],
flags: Discord.MessageFlags.Ephemeral,
});
}
const channel = await interaction.guild.channels
.fetch(supportbot.Suggestions.Channel)
.catch(() => null);
if (!channel) {
return interaction.reply({
embeds: [
new Discord.EmbedBuilder()
.setTitle("Missing Suggestion Channel")
.setDescription(msgconfig.Error.MissingChannel)
.setColor(supportbot.Embed.Colours.Error)
],
flags: Discord.MessageFlags.Ephemeral,
});
}
const targetMsg = await channel.messages.fetch(idOption).catch(() => null);
if (!targetMsg) {
return interaction.reply({
embeds: [
new Discord.EmbedBuilder()
.setTitle("Message Not Found")
.setDescription("Could not fetch a suggestion with that ID.")
.setColor(supportbot.Embed.Colours.Error)
],
flags: Discord.MessageFlags.Ephemeral,
});
}
const oldEmbed = targetMsg.embeds[0];
if (!oldEmbed) {
return interaction.reply({
embeds: [
new Discord.EmbedBuilder()
.setTitle("No Embed Found")
.setDescription("The target message has no suggestion embed.")
.setColor(supportbot.Embed.Colours.Error)
],
flags: Discord.MessageFlags.Ephemeral,
});
}
const colorMap = {
Solved: supportbot.Embed.Colours.Success,
Denied: supportbot.Embed.Colours.Error,
Considering: supportbot.Embed.Colours.Warn
};
const newEmbed = Discord.EmbedBuilder.from(oldEmbed)
.setColor(colorMap[status])
.addFields({ name: "Status", value: status, inline: true });
await targetMsg.edit({ embeds: [newEmbed], components: targetMsg.components });
SuggestionDB.setStatus(idOption, status);
return interaction.reply({
embeds: [
new Discord.EmbedBuilder()
.setTitle("Suggestion Updated")
.setDescription(`Suggestion **${idOption}** marked as **${status}**.`)
.setColor(supportbot.Embed.Colours.Success)
],
flags: Discord.MessageFlags.Ephemeral,
});
}
});
+119
View File
@@ -0,0 +1,119 @@
const fs = require("fs");
const {
EmbedBuilder,
ApplicationCommandOptionType,
ApplicationCommandType,
ChannelType,
MessageFlags,
} = require("discord.js");
const yaml = require("js-yaml");
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
const Command = require("../../Structures/Command.js");
const db = require("../../Structures/Database.js");
module.exports = new Command({
name: cmdconfig.ForceAddUser.Command,
description: cmdconfig.ForceAddUser.Description,
type: ApplicationCommandType.ChatInput,
options: [
{
name: "user",
description: "The user to forcefully add",
type: ApplicationCommandOptionType.User,
required: true,
},
],
permissions: cmdconfig.ForceAddUser.Permission,
async run(interaction) {
const userToAdd = interaction.options.getUser("user");
const ticketChannel = interaction.channel;
if (
(supportbot.Ticket.TicketType === "threads" && !ticketChannel.isThread()) ||
(supportbot.Ticket.TicketType === "channels" && ticketChannel.type !== ChannelType.GuildText)
) {
const onlyInTicket = new EmbedBuilder()
.setTitle(msgconfig.ForceAddUser.NotInTicket_Title)
.setDescription(msgconfig.ForceAddUser.NotInTicket_Description)
.setColor(supportbot.Embed.Colours.Error);
return interaction.reply({
embeds: [onlyInTicket],
flags: MessageFlags.Ephemeral,
});
}
const ticketRow = db.getTicket(ticketChannel.id);
if (!ticketRow) {
const notFound = new EmbedBuilder()
.setTitle(msgconfig.ForceAddUser.NotInTicket_Title)
.setDescription(msgconfig.ForceAddUser.NotInTicket_Description)
.setColor(supportbot.Embed.Colours.Error);
return interaction.reply({
embeds: [notFound],
flags: Discord.MessageFlags.Ephemeral,
});
}
try {
if (supportbot.Ticket.TicketType === "threads") {
const botMember = await ticketChannel.members.fetch(interaction.client.user.id).catch(() => null);
if (!botMember) await ticketChannel.join();
await new Promise(res => setTimeout(res, 1000));
await ticketChannel.members.add(userToAdd.id);
} else if (supportbot.Ticket.TicketType === "channels") {
await ticketChannel.permissionOverwrites.create(userToAdd.id, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
}
db.addUserToTicket(ticketChannel.id, userToAdd.id);
const addedEmbed = new EmbedBuilder()
.setTitle(msgconfig.ForceAddUser.Added_Title)
.setDescription(
msgconfig.ForceAddUser.Added_Description.replace("%username%", userToAdd.username)
)
.setColor(supportbot.Embed.Colours.Success);
await interaction.reply({
embeds: [addedEmbed],
flags: Discord.MessageFlags.Ephemeral,
});
const addedToTicketEmbed = new EmbedBuilder()
.setTitle(msgconfig.ForceAddUser.AddedToTicket_Title)
.setDescription(
msgconfig.ForceAddUser.AddedToTicket_Description.replace(
"%channel_link%",
`[${ticketChannel.name}](https://discord.com/channels/${ticketChannel.guild.id}/${ticketChannel.id})`
)
)
.setColor(supportbot.Embed.Colours.General);
await userToAdd.send({
embeds: [addedToTicketEmbed],
});
} catch (err) {
console.error("Error adding user to the ticket:", err);
const errorEmbed = new EmbedBuilder()
.setTitle(msgconfig.ForceAddUser.Error_Title)
.setDescription(msgconfig.ForceAddUser.Error_Adding)
.setColor(supportbot.Embed.Colours.Error);
await interaction.reply({
embeds: [errorEmbed],
flags: Discord.MessageFlags.Ephemeral,
});
}
},
});
+20 -40
View File
@@ -2,11 +2,13 @@ const fs = require("fs");
const Discord = require("discord.js");
const yaml = require("js-yaml");
const db = require("../../Structures/Database.js");
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");
const Command = require("../../Structures/Command.js");
module.exports = new Command({
name: cmdconfig.CloseTicket.Command,
@@ -57,18 +59,9 @@ module.exports = new Command({
await interaction.deferReply();
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 ticket = tickets.tickets[TicketData];
if (TicketData === -1) {
const ticket = db.getTicket(interaction.channel.id);
if (!ticket) {
const Exists = new Discord.EmbedBuilder()
.setTitle("No Ticket Found!")
.setDescription(msgconfig.Error.NoValidTicket)
@@ -120,9 +113,9 @@ module.exports = new Command({
await interaction.followUp({ embeds: [reviewEmbed] });
}
await handleCloseTicket(interaction, reason, ticket, TicketData);
await handleCloseTicket(interaction, reason, ticket);
} else {
await handleCloseTicket(interaction, reason, ticket, TicketData);
await handleCloseTicket(interaction, reason, ticket);
}
},
});
@@ -150,7 +143,7 @@ async function collectReviewRating(interaction, ticketUserId) {
content: `<@${ticketUserId}>`,
embeds: [reviewPrompt],
components: [stars],
ephemeral: true,
flags: Discord.MessageFlags.Ephemeral,
});
const starRating = await interaction.channel.awaitMessageComponent({
@@ -170,7 +163,7 @@ async function collectReviewComment(interaction, ticketUserId) {
await interaction.followUp({
content: `<@${ticketUserId}>`,
embeds: [commentPrompt],
ephemeral: true,
flags: Discord.MessageFlags.Ephemeral,
});
const filter = (response) => response.author.id === ticketUserId;
@@ -179,18 +172,10 @@ async function collectReviewComment(interaction, ticketUserId) {
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) {
const { getChannel } = interaction.client;
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_id);
let transcriptChannel = await getChannel(supportbot.Ticket.Log.TicketDataLog, interaction.guild);
if (!transcriptChannel) {
@@ -199,7 +184,6 @@ async function handleCloseTicket(interaction, reason, ticket, TicketData) {
}
try {
// Fetch all messages from the channel
let allMessages = [];
let lastId = null;
@@ -218,10 +202,8 @@ async function handleCloseTicket(interaction, reason, ticket, TicketData) {
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,
@@ -239,10 +221,7 @@ async function handleCloseTicket(interaction, reason, ticket, TicketData) {
}))
}));
// Update ticket status
tickets.tickets[TicketData].open = false;
tickets.tickets[TicketData].messages = transcriptData;
fs.writeFileSync("./Data/TicketData.json", JSON.stringify(tickets, null, 4));
db.updateTicketStatus(interaction.channel.id, "closed");
const transcriptEmbed = new Discord.EmbedBuilder()
.setTitle(msgconfig.TicketLog.Title)
@@ -250,7 +229,7 @@ async function handleCloseTicket(interaction, reason, ticket, TicketData) {
.setFooter({ text: supportbot.Embed.Footer, iconURL: interaction.user.displayAvatarURL() })
.setDescription(
`> **Ticket:** ${interaction.channel.name} (\`${interaction.channel.id}\`)\n` +
`> **User:** ${tUser?.tag || "Unknown User"} (\`${tUser?.id || ticket.user}\`)\n` +
`> **User:** ${tUser?.tag || "Unknown User"} (\`${tUser?.id || ticket.user_id}\`)` +
`> **Closed by:** <@${interaction.user.id}>\n` +
`> **Message Count:** ${transcriptData.length}`
)
@@ -264,6 +243,12 @@ async function handleCloseTicket(interaction, reason, ticket, TicketData) {
},
reason
);
const transcriptDir = "./Data/Transcripts";
if (!fs.existsSync(transcriptDir)) {
fs.mkdirSync(transcriptDir, { recursive: true });
}
const fileName = `${interaction.channel.id}-transcript.html`;
fs.writeFileSync(`./Data/Transcripts/${fileName}`, html);
@@ -288,19 +273,14 @@ async function handleCloseTicket(interaction, reason, ticket, TicketData) {
}
function parseMarkdown(content) {
// Convert **bold** text to <strong> tags
content = content.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
// Convert _italic_ text to <em> tags
content = content.replace(/_(.*?)_/g, '<em>$1</em>');
// Convert inline `code` to <code> tags
content = content.replace(/`(.*?)`/g, '<code>$1</code>');
// Convert [text](link) to <a> tags
content = content.replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2" target="_blank">$1</a>');
// Convert multiline code blocks ```code``` to <pre><code> tags
content = content.replace(/```(.*?)```/gs, '<pre><code>$1</code></pre>');
return content;
@@ -4,6 +4,7 @@ const {
ApplicationCommandOptionType,
ApplicationCommandType,
ChannelType,
MessageFlags,
} = require("discord.js");
const yaml = require("js-yaml");
@@ -11,7 +12,8 @@ const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8")
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
const Command = require("../Structures/Command.js");
const Command = require("../../Structures/Command.js");
const db = require("../../Structures/Database.js");
module.exports = new Command({
name: cmdconfig.ForceAddUser.Command,
@@ -30,7 +32,6 @@ module.exports = new Command({
async run(interaction) {
const userToAdd = interaction.options.getUser("user");
const ticketChannel = interaction.channel;
const ticketDataPath = "./Data/TicketData.json";
if (
(supportbot.Ticket.TicketType === "threads" && !ticketChannel.isThread()) ||
@@ -43,54 +44,38 @@ module.exports = new Command({
return interaction.reply({
embeds: [onlyInTicket],
ephemeral: true,
flags: MessageFlags.Ephemeral,
});
}
const ticketRow = db.getTicket(ticketChannel.id);
if (!ticketRow) {
const notFound = new EmbedBuilder()
.setTitle(msgconfig.ForceAddUser.NotInTicket_Title)
.setDescription(msgconfig.ForceAddUser.NotInTicket_Description)
.setColor(supportbot.Embed.Colours.Error);
return interaction.reply({
embeds: [notFound],
flags: MessageFlags.Ephemeral,
});
}
try {
if (supportbot.Ticket.TicketType === "threads") {
try {
// Explicitly ensure the bot is in the thread
const botMember = await ticketChannel.members.fetch(interaction.client.user.id).catch(() => null);
if (!botMember) {
await ticketChannel.join();
}
// Delay to ensure API has processed the bot's membership
await new Promise(res => setTimeout(res, 1000));
// Attempt to add the user to the thread
await ticketChannel.members.add(userToAdd.id);
console.log("User successfully added to the thread.");
} catch (err) {
console.error("Error adding user to the thread:", err.message);
const errorEmbed = new EmbedBuilder()
.setTitle(msgconfig.ForceAddUser.Error_Title)
.setDescription(`An error occurred: ${err.message}`)
.setColor(supportbot.Embed.Colours.Error);
return interaction.reply({
embeds: [errorEmbed],
ephemeral: true,
});
}
const botMember = await ticketChannel.members.fetch(interaction.client.user.id).catch(() => null);
if (!botMember) await ticketChannel.join();
await new Promise(res => setTimeout(res, 1000));
await ticketChannel.members.add(userToAdd.id);
} else if (supportbot.Ticket.TicketType === "channels") {
await ticketChannel.permissionOverwrites.create(userToAdd.id, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
});
}
const ticketData = JSON.parse(fs.readFileSync(ticketDataPath, "utf8"));
const ticketIndex = ticketData.tickets.findIndex((t) => t.id === ticketChannel.id);
if (ticketIndex !== -1) {
ticketData.tickets[ticketIndex].subUsers.push(userToAdd.id);
fs.writeFileSync(ticketDataPath, JSON.stringify(ticketData, null, 4));
}
db.addUserToTicket(ticketChannel.id, userToAdd.id);
const addedEmbed = new EmbedBuilder()
.setTitle(msgconfig.ForceAddUser.Added_Title)
@@ -101,7 +86,7 @@ module.exports = new Command({
await interaction.reply({
embeds: [addedEmbed],
ephemeral: true,
flags: MessageFlags.Ephemeral,
});
const addedToTicketEmbed = new EmbedBuilder()
@@ -127,7 +112,7 @@ module.exports = new Command({
await interaction.reply({
embeds: [errorEmbed],
ephemeral: true,
flags: MessageFlags.Ephemeral,
});
}
},
+108
View File
@@ -0,0 +1,108 @@
// SupportBot | Emerald Services
// Remove user from a ticket
const {
EmbedBuilder,
ApplicationCommandOptionType,
ApplicationCommandType,
ChannelType,
MessageFlags,
} = require("discord.js");
const yaml = require("js-yaml");
const fs = require("fs");
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
const Command = require("../../Structures/Command.js");
const db = require("../../Structures/Database.js");
module.exports = new Command({
name: cmdconfig.RemoveUser.Command,
description: cmdconfig.RemoveUser.Description,
type: ApplicationCommandType.ChatInput,
options: [
{
name: "user",
description: "The user to remove",
type: ApplicationCommandOptionType.User,
required: true,
},
],
permissions: cmdconfig.RemoveUser.Permission,
async run(interaction) {
const { getRole } = interaction.client;
const SupportStaff = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild);
const Admin = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild);
if (!SupportStaff || !Admin) {
return interaction.reply("Missing staff/admin roles in config.");
}
if (
!interaction.member.roles.cache.has(SupportStaff.id) &&
!interaction.member.roles.cache.has(Admin.id)
) {
const NoPerms = new EmbedBuilder()
.setTitle("Invalid Permissions!")
.setDescription(
`${msgconfig.Error.IncorrectPerms}\n\nRole Required: \`${supportbot.Roles.StaffMember.Staff}\` or \`${supportbot.Roles.StaffMember.Admin}\``
)
.setColor(supportbot.Embed.Colours.Warn);
return interaction.reply({ embeds: [NoPerms], flags: MessageFlags.Ephemeral });
}
const userToRemove = interaction.options.getUser("user");
const ticketChannel = interaction.channel;
if (
(supportbot.Ticket.TicketType === "threads" && !ticketChannel.isThread()) ||
(supportbot.Ticket.TicketType === "channels" && ticketChannel.type !== ChannelType.GuildText)
) {
const onlyInTicket = new EmbedBuilder()
.setTitle(msgconfig.RemoveUser.NotInTicket_Title)
.setDescription(msgconfig.RemoveUser.NotInTicket_Description)
.setColor(supportbot.Embed.Colours.Error);
return interaction.reply({ embeds: [onlyInTicket], flags: MessageFlags.Ephemeral });
}
try {
if (supportbot.Ticket.TicketType === "threads") {
await ticketChannel.members.remove(userToRemove.id);
} else {
await ticketChannel.permissionOverwrites.delete(userToRemove.id);
}
db.removeUserFromTicket(ticketChannel.id, userToRemove.id);
const removedEmbed = new EmbedBuilder()
.setTitle(msgconfig.RemoveUser.Removed_Title)
.setDescription(msgconfig.RemoveUser.Removed_Message)
.setColor(supportbot.Embed.Colours.Success);
await interaction.reply({ embeds: [removedEmbed], flags: MessageFlags.Ephemeral });
const removedFromTicketEmbed = new EmbedBuilder()
.setTitle(msgconfig.RemoveUser.RemovedFromTicket_Title)
.setDescription(
msgconfig.RemoveUser.RemovedFromTicket_Description.replace(
"%channel_link%",
`[${ticketChannel.name}](https://discord.com/channels/${ticketChannel.guild.id}/${ticketChannel.id})`
)
)
.setColor(supportbot.Embed.Colours.General);
await userToRemove.send({ embeds: [removedFromTicketEmbed] }).catch(() => {});
} catch (err) {
console.error("Error removing user from the ticket:", err);
const errorEmbed = new EmbedBuilder()
.setTitle(msgconfig.RemoveUser.Error_Title)
.setDescription(msgconfig.RemoveUser.Error_Removing)
.setColor(supportbot.Embed.Colours.Error);
await interaction.reply({ embeds: [errorEmbed], flags: MessageFlags.Ephemeral });
}
},
});
@@ -10,16 +10,19 @@ const {
StringSelectMenuOptionBuilder,
ButtonStyle,
MessageCollector,
MessageFlags,
} = require("discord.js");
const yaml = require("js-yaml");
const db = require("../../Structures/Database.js");
const panelconfig = yaml.load(fs.readFileSync("./Configs/ticket-panel.yml", "utf8"));
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");
const TicketNumberID = require("../Structures/TicketID.js");
const Command = require("../../Structures/Command.js");
const TicketNumberID = require("../../Structures/TicketID.js");
async function isBlacklisted(userId) {
let blacklistData;
@@ -40,16 +43,11 @@ async function isBlacklisted(userId) {
async function getClockedInUsers(guild) {
const clockedInUsers = new Set();
const profilesDir = './Data/Profiles';
const profileFiles = fs.readdirSync(profilesDir);
for (const file of profileFiles) {
const profileData = JSON.parse(fs.readFileSync(`${profilesDir}/${file}`, "utf8"));
if (profileData.clockedIn) {
const userId = file.replace('.json', '');
const member = await guild.members.fetch(userId);
if (member) clockedInUsers.add(member.id);
const rows = guild.members.cache.map(m => m.id);
for (const id of rows) {
const profile = db.getProfile(id);
if (profile && profile.clockedIn) {
clockedInUsers.add(id);
}
}
return clockedInUsers;
@@ -61,14 +59,11 @@ async function askTicketQuestions(ticketChannel, interaction, questions) {
const sendNextQuestion = async () => {
if (questionIndex < questions.length) {
const questionsEmbed = new EmbedBuilder()
.setDescription(`**${questionIndex + 1}.** ${questions[questionIndex]}`)
.setColor(supportbot.Embed.Colours.General)
.setColor(supportbot.Embed.Colours.General);
const questionMessage = await ticketChannel.send({
embeds: [questionsEmbed],
});
const questionMessage = await ticketChannel.send({ embeds: [questionsEmbed] });
const collector = new MessageCollector(ticketChannel, {
filter: (m) => m.author.id === interaction.user.id,
@@ -83,12 +78,12 @@ async function askTicketQuestions(ticketChannel, interaction, questions) {
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))
.setDescription(
msgconfig.Ticket.TicketQuestions.Details.Description.replace("%user%", interaction.user.id)
)
.setColor(supportbot.Embed.Colours.General)
.addFields(
answers.map((a) => ({
@@ -99,18 +94,14 @@ async function askTicketQuestions(ticketChannel, interaction, questions) {
)
.setTimestamp();
await ticketChannel.send({
embeds: [summaryEmbed]
});
await ticketChannel.send({ embeds: [summaryEmbed] });
}
};
// Start asking the first question
sendNextQuestion();
}
async function assignTicketToUser(ticketChannel, Staff, Admin, interaction, attempts = 0) {
try {
const clockedInUsers = await getClockedInUsers(interaction.guild);
@@ -124,7 +115,7 @@ async function assignTicketToUser(ticketChannel, Staff, Admin, interaction, atte
const claimButton = new ButtonBuilder()
.setCustomId(`claimticket-${ticketChannel.id}`)
.setLabel(supportbot.Ticket.ClaimTickets.ButtonTitle || 'Claim Ticket')
.setLabel(supportbot.Ticket.ClaimTickets.ButtonTitle || "Claim Ticket")
.setStyle(ButtonStyle.Primary);
const row = new ActionRowBuilder().addComponents(claimButton);
@@ -135,20 +126,25 @@ async function assignTicketToUser(ticketChannel, Staff, Admin, interaction, atte
components: [row],
});
const filter = i => i.customId === `claimticket-${ticketChannel.id}` && i.member.roles.cache.has(Staff.id);
const filter = (i) =>
i.customId === `claimticket-${ticketChannel.id}` &&
i.member.roles.cache.has(Staff.id);
const collector = claimMessage.createMessageComponentCollector({ filter, time: 120000 });
collector.on('collect', async i => {
collector.on("collect", async (i) => {
await ticketChannel.send({ content: `<@${i.user.id}> has claimed the ticket.` });
});
collector.on('end', async collected => {
collector.on("end", async (collected) => {
if (collected.size === 0) {
await claimMessage.delete();
await claimChannel.send({
content: `Ticket is still unclaimed. \nCC: [<@&${Staff.id}> <@&${Admin.id}]`,
embeds: [new EmbedBuilder().setDescription(`Ticket is still unclaimed: <#${ticketChannel.id}>`).setColor(supportbot.Embed.Colours.Warn)],
content: `Ticket is still unclaimed. \nCC: [<@&${Staff.id}> <@&${Admin.id}>]`,
embeds: [
new EmbedBuilder()
.setDescription(`Ticket is still unclaimed: <#${ticketChannel.id}>`)
.setColor(supportbot.Embed.Colours.Warn),
],
});
}
});
@@ -161,22 +157,27 @@ async function assignTicketToUser(ticketChannel, Staff, Admin, interaction, atte
}
const clockedInArray = Array.from(clockedInUsers);
const assignedUserId = clockedInArray[Math.floor(Math.random() * clockedInArray.length)];
const assignedUserId =
clockedInArray[Math.floor(Math.random() * clockedInArray.length)];
const assignedUser = await interaction.guild.members.fetch(assignedUserId);
const claimEmbed = new EmbedBuilder()
.setTitle(msgconfig.Ticket.ClaimTickets.ClaimTitle)
.setDescription(msgconfig.Ticket.ClaimTickets.ClaimMessage.replace('%user%', interaction.user.id))
.setDescription(
msgconfig.Ticket.ClaimTickets.ClaimMessage.replace("%user%", interaction.user.id)
)
.setColor(supportbot.Embed.Colours.General);
const claimButton = new ButtonBuilder()
.setCustomId(`claimticket-${ticketChannel.id}`)
.setLabel(supportbot.Ticket.ClaimTickets.ButtonTitle || 'Claim Ticket')
.setLabel(supportbot.Ticket.ClaimTickets.ButtonTitle || "Claim Ticket")
.setStyle(ButtonStyle.Primary);
const row = new ActionRowBuilder().addComponents(claimButton);
const claimChannel = await interaction.guild.channels.cache.get(supportbot.Ticket.ClaimTickets.Channel);
const claimChannel = await interaction.guild.channels.cache.get(
supportbot.Ticket.ClaimTickets.Channel
);
if (!claimChannel) {
console.error("Claim channel not found");
return null;
@@ -188,21 +189,28 @@ async function assignTicketToUser(ticketChannel, Staff, Admin, interaction, atte
components: [row],
});
const filter = i => i.customId === `claimticket-${ticketChannel.id}` && i.user.id === assignedUserId;
const filter = (i) =>
i.customId === `claimticket-${ticketChannel.id}` && i.user.id === assignedUserId;
const collector = claimMessage.createMessageComponentCollector({ filter, time: 120000 });
collector.on('collect', async i => {
await ticketChannel.send({ content: `<@${assignedUserId}>` }).then(msg => setTimeout(() => msg.delete(), 5000));
collector.on("collect", async (i) => {
await ticketChannel
.send({ content: `<@${assignedUserId}>` })
.then((msg) => setTimeout(() => msg.delete(), 5000));
});
collector.on('end', async collected => {
collector.on("end", async (collected) => {
if (collected.size === 0) {
await claimMessage.delete();
if (attempts >= 1) {
await claimChannel.send({
content: `No one claimed the ticket. Tagging roles: <@&${Staff.id}> <@&${Admin.id}>`,
embeds: [new EmbedBuilder().setDescription(`Ticket is still unclaimed: <#${ticketChannel.id}>`).setColor(supportbot.Embed.Colours.Warn)],
embeds: [
new EmbedBuilder()
.setDescription(`Ticket is still unclaimed: <#${ticketChannel.id}>`)
.setColor(supportbot.Embed.Colours.Warn),
],
});
const claimEmbed = new EmbedBuilder()
@@ -223,17 +231,24 @@ async function assignTicketToUser(ticketChannel, Staff, Admin, interaction, atte
components: [row],
});
const filter = i => i.customId === `claimticket-${ticketChannel.id}` && i.member.roles.cache.has(Staff.id);
const collector = claimMessage.createMessageComponentCollector({ filter, time: 120000 });
const filter = (i) =>
i.customId === `claimticket-${ticketChannel.id}` &&
i.member.roles.cache.has(Staff.id);
const collector = claimMessage.createMessageComponentCollector({
filter,
time: 120000,
});
collector.on('collect', async i => {
collector.on("collect", async (i) => {
console.log(`Ticket ${ticketChannel.id} claimed by user ${i.user.id}`);
await ticketChannel.member.add(i.user.id);
});
collector.on('end', async collected => {
collector.on("end", async (collected) => {
if (collected.size === 0) {
console.log(`No one claimed the ticket (${ticketChannel.id}), notifying all staff`);
console.log(
`No one claimed the ticket (${ticketChannel.id}), notifying all staff`
);
await claimMessage.delete();
await claimChannel.send({
@@ -244,7 +259,9 @@ async function assignTicketToUser(ticketChannel, Staff, Admin, interaction, atte
} else {
const ReassigningEmbed = new EmbedBuilder()
.setTitle(msgconfig.Ticket.ClaimTickets.ReassigningTitle)
.setDescription(msgconfig.Ticket.ClaimTickets.ReassigningMessage.replace('%user%', assignedUserId))
.setDescription(
msgconfig.Ticket.ClaimTickets.ReassigningMessage.replace("%user%", assignedUserId)
)
.setColor(supportbot.Embed.Colours.General);
await claimChannel.send({ embeds: [ReassigningEmbed], components: [] });
@@ -256,9 +273,6 @@ async function assignTicketToUser(ticketChannel, Staff, Admin, interaction, atte
return assignedUserId;
} catch (error) {
console.error("Error in assignTicketToUser function:", error);
if (error instanceof CombinedError) {
console.error("CombinedError details:", error.errors);
}
return null;
}
}
@@ -283,16 +297,17 @@ module.exports = new Command({
if (await isBlacklisted(interaction.user.id)) {
return interaction.reply({
content: msgconfig.Ticket.Blacklisted,
ephemeral: true,
flags: MessageFlags.Ephemeral
});
}
let TicketReason = null;
if (supportbot.Ticket.TicketReason) {
TicketReason = interaction.reason || null; // Retrieve the reason from the interaction object
TicketReason = interaction.reason || null;
}
let TicketData = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8"));
let TicketData = { tickets: db.getAllTickets() };
const { getRole, getChannel } = interaction.client;
let User = interaction.guild.members.cache.get(interaction.user.id);
@@ -308,7 +323,7 @@ module.exports = new Command({
color: supportbot.Embed.Colours.Warn,
},
],
ephemeral: true,
flags: MessageFlags.Ephemeral
});
}
@@ -326,7 +341,7 @@ module.exports = new Command({
) {
return await interaction.reply({
embeds: [TicketExists],
ephemeral: true,
flags: MessageFlags.Ephemeral
});
}
@@ -335,7 +350,7 @@ module.exports = new Command({
if (!Staff || !Admin)
return interaction.reply({
content: "Some roles seem to be missing!\nPlease check for errors when starting the bot.",
ephemeral: true,
flags: MessageFlags.Ephemeral
});
let ticketChannel;
@@ -361,7 +376,7 @@ module.exports = new Command({
if (!category) {
return interaction.reply({
content: "The ticket category does not exist!",
ephemeral: true,
flags: MessageFlags.Ephemeral
});
}
@@ -372,7 +387,6 @@ module.exports = new Command({
reason: 'support ticket',
});
// Move to secondary category if the primary one is full
if (category.children.size >= 50) {
const secondaryCategory = interaction.guild.channels.cache.find(
(c) => c.name === supportbot.Ticket.TicketChannelsCategory2 || c.id === supportbot.Ticket.TicketChannelsCategory2
@@ -383,13 +397,12 @@ module.exports = new Command({
} else {
return interaction.reply({
content: "The secondary ticket category does not exist!",
ephemeral: true,
flags: MessageFlags.Ephemeral
});
}
}
}
// Assign ticket to a user if claiming tickets is enabled
if (supportbot.Ticket.ClaimTickets.Enabled) {
const assignedUserId = await assignTicketToUser(ticketChannel, Staff, Admin, interaction);
if (!assignedUserId) {
@@ -418,17 +431,8 @@ module.exports = new Command({
ViewChannel: false,
});
}
// 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
TicketData.tickets.push({
db.addTicket({
id: ticketChannel.id,
name: ticketChannel.name,
user: interaction.user.id,
@@ -439,16 +443,8 @@ module.exports = new Command({
createdAt: new Date().toISOString(),
claimedAt: null,
});
TicketData.tickets = db.getAllTickets();
fs.writeFileSync(
"./Data/TicketData.json",
JSON.stringify(TicketData, null, 4),
(err) => {
if (err) console.error(err);
}
);
// Notify the user that the ticket was created
const CreatedTicket = new EmbedBuilder()
.setDescription(
msgconfig.Ticket.TicketCreatedAlert.replace(/%ticketauthor%/g, interaction.user.id)
@@ -457,9 +453,8 @@ module.exports = new Command({
.replace(/%ticketreason%/g, TicketSubject)
)
.setColor(supportbot.Embed.Colours.General);
await interaction.reply({ embeds: [CreatedTicket], ephemeral: true });
await interaction.reply({ embeds: [CreatedTicket], flags: MessageFlags.Ephemeral });
// Create the ticket message
const TicketMessage = new EmbedBuilder()
.setAuthor({
name: msgconfig.Ticket.TicketAuthorTitle.replace(/%ticketauthor%/g, interaction.user.id)
@@ -486,7 +481,6 @@ module.exports = new Command({
);
}
// Create the control panel
const SelectMenus = new StringSelectMenuBuilder()
.setCustomId("ticketcontrolpanel")
.setPlaceholder("Ticket Control Panel")
@@ -542,6 +536,13 @@ module.exports = new Command({
components: [row2],
});
if (supportbot.Ticket.Questions.Enabled) {
const questions = supportbot.Ticket.Questions.List || [];
if (questions.length > 0) {
await askTicketQuestions(ticketChannel, interaction, questions);
}
}
} catch (error) {
console.error("Error in run method:", error);
+113
View File
@@ -0,0 +1,113 @@
const fs = require("fs");
const {
EmbedBuilder,
ApplicationCommandType,
ActionRowBuilder,
ButtonBuilder,
MessageFlags,
} = require("discord.js");
const yaml = require("js-yaml");
const panelconfig = yaml.load(fs.readFileSync("./Configs/ticket-panel.yml", "utf8"));
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
const Command = require("../../Structures/Command.js");
const db = require("../../Structures/Database.js");
module.exports = new Command({
name: cmdconfig.TicketPanel.Command,
description: cmdconfig.TicketPanel.Description,
type: ApplicationCommandType.ChatInput,
options: [],
permissions: cmdconfig.TicketPanel.Permission,
async run(interaction) {
if (!panelconfig.Panel) {
console.log(
"\u001b[31m",
`[TICKET PANEL]`,
"\u001b[33m",
"Ticket Panel is not setup, You can set this in", `\u001b[31m`, '/Configs/ticket-panel.yml', `\n `,
);
return interaction.reply({
content: "Ticket Panel is not set up. Please configure it in `/Configs/ticket-panel.yml`.",
flags: MessageFlags.Ephemeral
});
}
const { getChannel } = interaction.client;
const channel = await getChannel(
supportbot.Ticket.TicketHome,
interaction.guild
);
if (!channel) {
console.log(`[TICKET PANEL] ${channelName} channel not found. Please check your config file.`);
return interaction.reply({
content: `${channelName} channel not found. Please check your config file.`,
flags: MessageFlags.Ephemeral
});
}
const panelRow = db.getTicketPanel();
if (panelRow) {
try {
await channel.messages.fetch(panelRow.message_id);
return interaction.reply({
content: "Ticket panel message already exists.",
flags: MessageFlags.Ephemeral
});
} catch (err) {}
}
let embed = new EmbedBuilder()
.setTitle(panelconfig.PanelTitle)
.setColor(panelconfig.PanelColour)
.setFooter({
text: supportbot.Embed.Footer,
iconURL: interaction.user.displayAvatarURL(),
});
if (panelconfig.TicketPanel_Description) {
embed.setDescription(panelconfig.PanelMessage);
}
if (panelconfig.TicketPanel_Thumbnail) {
embed.setThumbnail(panelconfig.PanelThumbnail);
}
if (panelconfig.TicketPanel_Image) {
embed.setImage(panelconfig.PanelImage);
}
const createTicketButton = new ButtonBuilder()
.setCustomId("createticket")
.setLabel(panelconfig.Button.Text)
.setEmoji(panelconfig.Button.Emoji)
.setStyle(panelconfig.Button.Color);
let row = new ActionRowBuilder().addComponents(createTicketButton);
try {
const message = await channel.send({
embeds: [embed],
components: [row],
});
db.saveTicketPanel(message.id, channel.id);
return interaction.reply({
content: "Ticket panel message has been sent!",
flags: MessageFlags.Ephemeral
});
} catch (e) {
console.log("Error sending message:", e);
return interaction.reply({
content: "An error occurred while sending the ticket panel message.",
flags: MessageFlags.Ephemeral
});
}
},
});
+7 -18
View File
@@ -9,9 +9,10 @@ const {
TextInputBuilder,
TextInputStyle,
ButtonStyle,
ComponentType
ComponentType,
MessageFlags,
} = require('discord.js');
const Command = require('../Structures/Command.js');
const Command = require('../../Structures/Command.js');
const fs = require('fs');
const yaml = require('js-yaml');
@@ -19,7 +20,6 @@ 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'));
// Helper function to validate and convert hex color
function parseColor(color) {
if (!color) return 0x5865F2;
if (typeof color === 'number') return color;
@@ -46,7 +46,6 @@ module.exports = new Command({
permissions: cmdconfig.Embed.Permission,
async run(interaction) {
// Permission check
const { getRole } = interaction.client;
let SupportStaff = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild);
let Admin = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild);
@@ -61,12 +60,11 @@ module.exports = new Command({
.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 });
return interaction.reply({ embeds: [NoPerms], flags: MessageFlags.Ephemeral });
}
const channel = interaction.options.getChannel('channel');
// Initial message state
const messageData = {
type: 'embed',
title: 'New Embed',
@@ -79,7 +77,6 @@ module.exports = new Command({
timestamp: false
};
// Create initial embed
function createEmbed() {
const embed = new EmbedBuilder()
.setTitle(messageData.title)
@@ -105,7 +102,6 @@ module.exports = new Command({
return embed;
}
// Create type selector
const typeRow = new ActionRowBuilder()
.addComponents(
new StringSelectMenuBuilder()
@@ -125,12 +121,10 @@ module.exports = new Command({
])
);
// 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()
@@ -173,7 +167,6 @@ module.exports = new Command({
buttons.push(row1, row2);
} else {
// Text mode only shows edit text button
const textRow = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
@@ -198,7 +191,6 @@ module.exports = new Command({
return buttons;
}
// Function to update the message preview
async function updatePreview(i) {
if (messageData.type === 'embed') {
await i.update({
@@ -207,7 +199,6 @@ module.exports = new Command({
components: [typeRow, ...createButtons('embed')]
});
} else {
// For text mode, just show the text directly
await i.update({
content: messageData.description,
embeds: [],
@@ -216,15 +207,13 @@ module.exports = new Command({
}
}
// 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
flags: MessageFlags.Ephemeral
});
// Create collector for interactions
const collector = message.createMessageComponentCollector({
time: 900000
});
@@ -233,7 +222,7 @@ module.exports = new Command({
if (i.user.id !== interaction.user.id) {
return i.reply({
content: 'You cannot edit this message.',
ephemeral: true
flags: MessageFlags.Ephemeral
});
}
@@ -422,7 +411,7 @@ module.exports = new Command({
} catch (error) {
await i.reply({
content: 'Failed to send message. Please check channel permissions.',
ephemeral: true
flags: MessageFlags.Ephemeral
});
}
break;
+10 -10
View File
@@ -1,11 +1,11 @@
const fs = require("fs");
const { ApplicationCommandOptionType, ApplicationCommandType, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, ComponentType } = require("discord.js");
const { ApplicationCommandOptionType, ApplicationCommandType, EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, ComponentType, MessageFlags } = 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");
const Command = require("../../Structures/Command.js");
// Function to chunk array into smaller arrays
function chunkArray(arr, size) {
@@ -116,7 +116,7 @@ module.exports = new Command({
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 });
return interaction.reply({ embeds: [alreadyBlacklistedEmbed], flags: MessageFlags.Ephemeral });
}
blacklistedUsers.push(user.id);
@@ -141,7 +141,7 @@ module.exports = new Command({
return interaction.reply({
embeds: [successEmbed],
ephemeral: true
flags: MessageFlags.Ephemeral
});
} else if (subcommand === cmdconfig.Mod.TicketBlacklist.Remove.Command) {
@@ -149,7 +149,7 @@ module.exports = new Command({
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 });
return interaction.reply({ embeds: [notBlacklistedEmbed], flags: MessageFlags.Ephemeral });
}
blacklistedUsers = blacklistedUsers.filter(id => id !== user.id);
@@ -172,14 +172,14 @@ module.exports = new Command({
blacklistChannel.send({ embeds: [blacklistLogEmbed] });
}
return interaction.reply({ embeds: [removedEmbed], ephemeral: true });
return interaction.reply({ embeds: [removedEmbed], flags: MessageFlags.Ephemeral });
} 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 });
return interaction.reply({ embeds: [noBlacklistedUsersEmbed], flags: MessageFlags.Ephemeral });
}
const chunkedUsers = chunkArray(blacklistedUsers, 5);
@@ -210,7 +210,7 @@ module.exports = new Command({
const response = await interaction.reply({
embeds: [createEmbed(currentPage)],
components: chunkedUsers.length > 1 ? [buttons] : [],
ephemeral: true
flags: MessageFlags.Ephemeral
});
if (chunkedUsers.length <= 1) return;
@@ -224,7 +224,7 @@ module.exports = new Command({
if (i.user.id !== interaction.user.id) {
return i.reply({
content: 'You cannot use these buttons.',
ephemeral: true
flags: MessageFlags.Ephemeral
});
}
@@ -255,7 +255,7 @@ module.exports = new Command({
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 });
interaction.reply({ embeds: [errorEmbed], flags: MessageFlags.Ephemeral });
}
}
});
@@ -1,33 +1,35 @@
const { Command } = require('../Structures/Addon.js');
const { Command } = require('../../Structures/Addon.js');
const Discord = require("discord.js");
const fs = require("fs");
const yaml = require("js-yaml");
const path = require("path");
const db = require("../../Structures/Database.js");
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
const settingsFilePath = path.resolve(__dirname, '../Data/settings.json');
function loadSettings() {
try {
if (fs.existsSync(settingsFilePath)) {
const data = fs.readFileSync(settingsFilePath, 'utf8');
return JSON.parse(data);
} else {
return {};
}
} catch (error) {
console.error("Error reading settings file:", error);
return {};
}
async function loadSettings() {
return new Promise((resolve, reject) => {
db.get("SELECT data FROM settings WHERE id = 1", (err, row) => {
if (err) return reject(err);
if (!row) return resolve({});
try {
resolve(JSON.parse(row.data));
} catch (e) {
resolve({});
}
});
});
}
function saveSettings(settings) {
try {
fs.writeFileSync(settingsFilePath, JSON.stringify(settings, null, 2), 'utf8');
} catch (error) {
console.error("Error writing settings file:", error);
}
async function saveSettings(settings) {
const data = JSON.stringify(settings);
return new Promise((resolve, reject) => {
db.run(
"INSERT INTO settings (id, data) VALUES (1, ?) ON CONFLICT(id) DO UPDATE SET data = excluded.data",
[data],
(err) => (err ? reject(err) : resolve())
);
});
}
module.exports = new Command({
@@ -119,7 +121,7 @@ module.exports = new Command({
async run(interaction) {
const subcommand = interaction.options.getSubcommand();
const settings = loadSettings();
const settings = await loadSettings();
if (subcommand === 'setstatus') {
const status = interaction.options.getString('status');
@@ -131,15 +133,15 @@ module.exports = new Command({
});
settings.status = status;
saveSettings(settings);
await saveSettings(settings);
const embed = new Discord.EmbedBuilder()
.setDescription(`Status set to **${status.charAt(0).toUpperCase() + status.slice(1)}**`)
.setColor(supportbot.Embed.Colours.General);
return interaction.reply({ embeds: [embed], ephemeral: true });
return interaction.reply({ embeds: [embed], flags: Discord.MessageFlags.Ephemeral });
} catch (error) {
return interaction.reply({ content: `Failed to set status: ${error.message}`, ephemeral: true });
return interaction.reply({ content: `Failed to set status: ${error.message}`, flags: Discord.MessageFlags.Ephemeral });
}
}
@@ -154,15 +156,15 @@ module.exports = new Command({
});
settings.activity = { type: activityType, message: activityMessage };
saveSettings(settings);
await saveSettings(settings);
const embed = new Discord.EmbedBuilder()
.setDescription(`Activity set to **${activityType.toLowerCase()} ${activityMessage}**`)
.setColor(supportbot.Embed.Colours.General);
return interaction.reply({ embeds: [embed], ephemeral: true });
return interaction.reply({ embeds: [embed], flags: Discord.MessageFlags.Ephemeral });
} catch (error) {
return interaction.reply({ content: `Failed to set activity: ${error.message}`, ephemeral: true });
return interaction.reply({ content: `Failed to set activity: ${error.message}`, flags: Discord.MessageFlags.Ephemeral });
}
} else if (subcommand === 'streammode') {
const toggle = interaction.options.getString('toggle');
@@ -170,7 +172,7 @@ module.exports = new Command({
if (toggle === 'on') {
if (!twitchUrl) {
return interaction.reply({ content: 'Please provide a Twitch URL to stream.', ephemeral: true });
return interaction.reply({ content: 'Please provide a Twitch URL to stream.', flags: Discord.MessageFlags.Ephemeral });
}
try {
@@ -180,15 +182,15 @@ module.exports = new Command({
});
settings.streaming = { active: true, url: twitchUrl };
saveSettings(settings);
await saveSettings(settings);
const embed = new Discord.EmbedBuilder()
.setDescription(`**Streaming mode enabled!**\nStreaming: [Twitch](${twitchUrl})`)
.setColor(supportbot.Embed.Colours.General);
return interaction.reply({ embeds: [embed], ephemeral: true });
return interaction.reply({ embeds: [embed], flags: Discord.MessageFlags.Ephemeral });
} catch (error) {
return interaction.reply({ content: `Failed to enable streaming mode: ${error.message}`, ephemeral: true });
return interaction.reply({ content: `Failed to enable streaming mode: ${error.message}`, flags: Discord.MessageFlags.Ephemeral });
}
} else if (toggle === 'off') {
try {
@@ -198,15 +200,15 @@ module.exports = new Command({
});
settings.streaming = { active: false };
saveSettings(settings);
await saveSettings(settings);
const embed = new Discord.EmbedBuilder()
.setDescription(`**Streaming mode disabled!**`)
.setColor(supportbot.Embed.Colours.General);
return interaction.reply({ embeds: [embed], ephemeral: true });
return interaction.reply({ embeds: [embed], flags: Discord.MessageFlags.Ephemeral });
} catch (error) {
return interaction.reply({ content: `Failed to disable streaming mode: ${error.message}`, ephemeral: true });
return interaction.reply({ content: `Failed to disable streaming mode: ${error.message}`, flags: Discord.MessageFlags.Ephemeral });
}
}
} else if (subcommand === 'setnickname') {
@@ -218,18 +220,18 @@ module.exports = new Command({
await botMember.setNickname(nickname);
settings.nickname = nickname;
saveSettings(settings);
await saveSettings(settings);
const embed = new Discord.EmbedBuilder()
.setDescription(`Bot nickname set to **${nickname}**`)
.setColor(supportbot.Embed.Colours.General);
return interaction.reply({ embeds: [embed], ephemeral: true });
return interaction.reply({ embeds: [embed], flags: Discord.MessageFlags.Ephemeral });
} catch (error) {
return interaction.reply({ content: `Failed to set nickname: ${error.message}`, ephemeral: true });
return interaction.reply({ content: `Failed to set nickname: ${error.message}`, flags: Discord.MessageFlags.Ephemeral });
}
} else {
return interaction.reply({ content: 'Invalid command usage.', ephemeral: true });
return interaction.reply({ content: 'Invalid command usage.', flags: Discord.MessageFlags.Ephemeral });
}
},
});
});
-217
View File
@@ -1,217 +0,0 @@
const fs = require("fs");
const {
EmbedBuilder,
ApplicationCommandOptionType,
ApplicationCommandType,
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
ChannelType,
} = require("discord.js");
const yaml = require("js-yaml");
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
const Command = require("../Structures/Command.js");
module.exports = new Command({
name: cmdconfig.AddUser.Command,
description: cmdconfig.AddUser.Description,
type: ApplicationCommandType.ChatInput,
options: [
{
name: "user",
description: "The user to add",
type: ApplicationCommandOptionType.User,
required: true,
},
],
permissions: cmdconfig.AddUser.Permission,
async run(interaction) {
const userToAdd = interaction.options.getUser("user");
const ticketChannel = interaction.channel;
const ticketDataPath = "./Data/TicketData.json";
if (
(supportbot.Ticket.TicketType === "threads" && !ticketChannel.isThread()) ||
(supportbot.Ticket.TicketType === "channels" && ticketChannel.type !== ChannelType.GuildText)
) {
const onlyInTicket = new EmbedBuilder()
.setTitle(msgconfig.AddUser.NotInTicket_Title)
.setDescription(msgconfig.AddUser.NotInTicket_Description)
.setColor(supportbot.Embed.Colours.Error);
return interaction.reply({
embeds: [onlyInTicket],
ephemeral: true,
});
}
const ticketInviteEmbed = new EmbedBuilder()
.setTitle(msgconfig.AddUser.Invite_Title)
.setDescription(msgconfig.AddUser.Invite_Description.replace("%username%", userToAdd.username))
.setColor(supportbot.Embed.Colours.General);
await interaction.reply({
embeds: [ticketInviteEmbed],
ephemeral: true,
});
try {
const dmEmbed = new EmbedBuilder()
.setTitle(msgconfig.AddUser.DM_Title)
.setDescription(msgconfig.AddUser.DM_Description.replace("%channelname%", ticketChannel.name))
.setColor(supportbot.Embed.Colours.General);
const acceptButton = new ButtonBuilder()
.setCustomId("accept_invite")
.setLabel(msgconfig.AddUser.Accept_Label)
.setEmoji("✅")
.setStyle(ButtonStyle.Primary);
const rejectButton = new ButtonBuilder()
.setCustomId("reject_invite")
.setLabel(msgconfig.AddUser.Reject_Label)
.setEmoji("❌")
.setStyle(ButtonStyle.Secondary);
const row = new ActionRowBuilder().addComponents(acceptButton, rejectButton);
const dmMessage = await userToAdd.send({
embeds: [dmEmbed],
components: [row],
});
const filter = (i) => i.user.id === userToAdd.id;
const collector = dmMessage.createMessageComponentCollector({
filter,
time: 60000,
});
collector.on("collect", async (i) => {
if (i.customId === "accept_invite") {
try {
// Add user to thread or channel based on TicketType
if (supportbot.Ticket.TicketType === "threads") {
await ticketChannel.members.add(userToAdd.id); // For threads
} else if (supportbot.Ticket.TicketType === "channels") {
await ticketChannel.permissionOverwrites.create(userToAdd.id, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
}); // For channels
}
const ticketData = JSON.parse(fs.readFileSync(ticketDataPath, "utf8"));
const ticketIndex = ticketData.tickets.findIndex((t) => t.id === ticketChannel.id);
if (ticketIndex !== -1) {
ticketData.tickets[ticketIndex].subUsers.push(userToAdd.id);
fs.writeFileSync(ticketDataPath, JSON.stringify(ticketData, null, 4));
}
const acceptedEmbed = new EmbedBuilder()
.setTitle(msgconfig.AddUser.Accepted_Title)
.setDescription(msgconfig.AddUser.Accepted_Message)
.setColor(supportbot.Embed.Colours.Success);
await i.update({
embeds: [acceptedEmbed],
components: [],
});
// Send the added user ticket message in the ticket channel
const addeduserTicket = new EmbedBuilder()
.setTitle(msgconfig.AddUser.Added_Title)
.setDescription(msgconfig.AddUser.Added_Description.replace('%username%', userToAdd.username))
.setColor(supportbot.Embed.Colours.General);
await ticketChannel.send({
embeds: [addeduserTicket],
});
const addedToTicketEmbed = new EmbedBuilder()
.setTitle(msgconfig.AddUser.AddedToTicket_Title)
.setDescription(msgconfig.AddUser.AddedToTicket_Description.replace(
"%channel_link%",
`[${ticketChannel.name}](https://discord.com/channels/${ticketChannel.guild.id}/${ticketChannel.id})`
))
.setColor(supportbot.Embed.Colours.General);
const ticketLinkButton = new ButtonBuilder()
.setLabel(msgconfig.AddUser.ViewTicket_Label)
.setURL(`https://discord.com/channels/${ticketChannel.guild.id}/${ticketChannel.id}`)
.setStyle(ButtonStyle.Link);
await userToAdd.send({
embeds: [addedToTicketEmbed],
components: [new ActionRowBuilder().addComponents(ticketLinkButton)],
});
} catch (err) {
console.error("Error adding user to the ticket:", err);
const errorEmbed = new EmbedBuilder()
.setTitle(msgconfig.AddUser.Error_Title)
.setDescription(msgconfig.AddUser.Error_Adding)
.setColor(supportbot.Embed.Colours.Error);
await i.update({
embeds: [errorEmbed],
components: [],
});
}
} else {
const declinedEmbed = new EmbedBuilder()
.setTitle(msgconfig.AddUser.Declined_Title)
.setDescription(msgconfig.AddUser.Declined_Message)
.setColor(supportbot.Embed.Colours.Error);
await i.update({
embeds: [declinedEmbed],
components: [],
});
}
});
collector.on("end", (collected) => {
if (collected.size === 0) {
userToAdd.send({
embeds: [
new EmbedBuilder()
.setTitle(msgconfig.AddUser.NoResponse_Title)
.setDescription(msgconfig.AddUser.NoResponse_Message)
.setColor(supportbot.Embed.Colours.Warning),
],
});
}
});
} catch (error) {
if (error.code === 50007) {
const errorEmbed = new EmbedBuilder()
.setTitle(msgconfig.AddUser.DM_Error_Title)
.setDescription(msgconfig.AddUser.DM_Error_Description.replace("%username%", userToAdd.username))
.setColor(supportbot.Embed.Colours.Error);
await ticketChannel.send({
embeds: [errorEmbed],
});
} else {
console.error("Error sending DM:", error);
const unexpectedErrorEmbed = new EmbedBuilder()
.setTitle(msgconfig.AddUser.Unexpected_Error_Title)
.setDescription(msgconfig.AddUser.Unexpected_Error_Description.replace("%error%", error.message))
.setColor(supportbot.Embed.Colours.Error);
await ticketChannel.send({
embeds: [unexpectedErrorEmbed],
});
}
}
},
});
-320
View File
@@ -1,320 +0,0 @@
const { EmbedBuilder, ModalBuilder, TextInputBuilder, TextInputStyle, ApplicationCommandType, ActionRowBuilder, ButtonBuilder, ButtonStyle, ApplicationCommandOptionType } = require("discord.js");
const fs = require("fs");
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");
const clockedInUsers = new Set();
function updateClockedInStatus(profileEmbed, clockedIn) {
profileEmbed.data.fields = profileEmbed.data.fields.map(field => {
if (field.name === 'Clocked In Status') {
field.value = clockedIn ? '✅ Clocked In' : '❌ Clocked Out';
}
return field;
});
return profileEmbed;
}
module.exports = new Command({
name: cmdconfig.Profile.Command,
description: cmdconfig.Profile.Description,
type: ApplicationCommandType.ChatInput,
permissions: cmdconfig.Profile.Permission,
options: [
{
name: 'user',
description: 'The user to view the profile of',
type: ApplicationCommandOptionType.User,
required: false
}
],
async run(interaction) {
await interaction.deferReply({ ephemeral: true });
const userOption = interaction.options.getUser('user');
const viewingUser = userOption || interaction.user;
const viewingUserId = viewingUser.id;
const interactionUserId = interaction.user.id;
const user = interaction.guild.members.cache.get(viewingUserId);
const { getRole, getChannel } = interaction.client;
const Staff = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild);
const Admin = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild);
if (!Staff || !Admin) {
return interaction.editReply({
content: "Some roles seem to be missing!\nPlease check for errors when starting the bot.",
ephemeral: true,
});
}
const isStaff = user.roles.cache.has(Staff.id) || user.roles.cache.has(Admin.id);
const isOwnProfile = interactionUserId === viewingUserId;
const profilePath = `./Data/Profiles/${viewingUserId}.json`;
let profileData;
if (fs.existsSync(profilePath)) {
profileData = JSON.parse(fs.readFileSync(profilePath, "utf8"));
} else {
profileData = {
bio: "",
timezone: "",
clockedIn: false
};
fs.writeFileSync(profilePath, JSON.stringify(profileData, null, 2));
}
const { bio, timezone, clockedIn } = profileData;
let profileEmbed = new EmbedBuilder()
.setTitle(`${viewingUser.username}'s Profile`)
.setColor(supportbot.Embed.Colours.General)
.setThumbnail(viewingUser.displayAvatarURL())
.addFields(
{ name: 'Bio', value: bio || 'No bio set.', inline: false },
{ name: 'Timezone', value: timezone || 'No timezone set.', inline: true }
);
if (isStaff) {
profileEmbed.addFields({ name: 'Clocked In Status', value: clockedIn ? '✅ Clocked In' : '❌ Clocked Out', inline: true });
}
const buttonRow = new ActionRowBuilder();
if (isOwnProfile) {
buttonRow.addComponents(
new ButtonBuilder()
.setCustomId('editProfile')
.setLabel('Edit Profile')
.setStyle(ButtonStyle.Secondary)
);
if (isStaff) {
buttonRow.addComponents(
new ButtonBuilder()
.setCustomId('clockInOut')
.setLabel(clockedIn ? 'Clock Out' : 'Clock In')
.setStyle(ButtonStyle.Secondary)
);
}
}
if (isStaff && supportbot.Ticket.ClaimTickets.Enabled) {
buttonRow.addComponents(
new ButtonBuilder()
.setCustomId('viewTicketStats')
.setLabel('View Ticket Stats')
.setStyle(ButtonStyle.Secondary)
);
}
await interaction.editReply({ embeds: [profileEmbed], components: buttonRow.components.length > 0 ? [buttonRow] : [] });
const filter = i => (
['editProfile', 'clockInOut', 'viewTicketStats', 'showOpenTickets', 'backToStats', 'backToProfile'].includes(i.customId)
&& i.user.id === interactionUserId
);
const collector = interaction.channel.createMessageComponentCollector({ filter, time: 60000 });
collector.on('collect', async i => {
if (i.customId === 'editProfile') {
const modal = new ModalBuilder()
.setCustomId('editProfileModal')
.setTitle('Edit Profile')
.addComponents(
new ActionRowBuilder().addComponents(
new TextInputBuilder()
.setCustomId('bio')
.setLabel('Bio')
.setStyle(TextInputStyle.Paragraph)
.setPlaceholder('Enter your bio')
.setValue(String(bio))
.setRequired(false)
),
new ActionRowBuilder().addComponents(
new TextInputBuilder()
.setCustomId('timezone')
.setLabel('Timezone')
.setStyle(TextInputStyle.Short)
.setPlaceholder('Enter your timezone')
.setValue(String(timezone))
.setRequired(false)
)
);
await i.showModal(modal);
}
if (i.customId === 'clockInOut') {
profileData.clockedIn = !profileData.clockedIn;
fs.writeFileSync(profilePath, JSON.stringify(profileData, null, 2));
profileEmbed = updateClockedInStatus(profileEmbed, profileData.clockedIn);
if (profileData.clockedIn) {
clockedInUsers.add(viewingUserId);
} else {
clockedInUsers.delete(viewingUserId);
}
const updatedButtonRow = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId('editProfile')
.setLabel('Edit Profile')
.setStyle(ButtonStyle.Secondary)
);
if (isStaff) {
updatedButtonRow.addComponents(
new ButtonBuilder()
.setCustomId('clockInOut')
.setLabel(profileData.clockedIn ? 'Clock Out' : 'Clock In')
.setStyle(ButtonStyle.Secondary)
);
if (supportbot.Ticket.ClaimTickets.Enabled) {
updatedButtonRow.addComponents(
new ButtonBuilder()
.setCustomId('viewTicketStats')
.setLabel('View Ticket Stats')
.setStyle(ButtonStyle.Secondary)
);
}
}
await i.update({ embeds: [profileEmbed], components: [updatedButtonRow] });
let clockedinout = new EmbedBuilder()
.setTitle(profileData.clockedIn ? 'Clocked In!' : 'Clocked Out!')
.setDescription(profileData.clockedIn ? 'You have successfully clocked in.' : 'You have successfully clocked out.')
.setColor(supportbot.Embed.Colours.General)
await interaction.followUp({ embeds: [clockedinout], ephemeral: true });
}
if (i.customId === 'viewTicketStats') {
const TicketData = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8"));
const ticketsClaimed = TicketData.tickets.filter(t => t.claimedBy === viewingUserId);
const ticketsOpen = ticketsClaimed.filter(t => t.open);
const totalTickets = ticketsClaimed.length;
let totalResponseTime = 0;
let responseCount = 0;
ticketsClaimed.forEach(ticket => {
if (ticket.claimedAt && ticket.createdAt) {
const responseTime = new Date(ticket.claimedAt) - new Date(ticket.createdAt);
totalResponseTime += responseTime;
responseCount++;
}
});
const averageResponseTime = responseCount ? totalResponseTime / responseCount : 0;
const averageResponseTimeMinutes = Math.round(averageResponseTime / 60000);
const statsEmbed = new EmbedBuilder()
.setTitle(msgconfig.TicketStats.Title)
.setDescription(`> Ticket stats for <@${viewingUserId}>`)
.setColor(supportbot.Embed.Colours.General)
.addFields(
{ name: msgconfig.TicketStats.OpenTickets, value: `${ticketsOpen.length}`, inline: true },
{ name: msgconfig.TicketStats.TotalTickets, value: `${totalTickets}`, inline: true },
{ name: msgconfig.TicketStats.ResponseTime, value: `${averageResponseTimeMinutes} minutes`, inline: false }
);
const statsButtonRow = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId('showOpenTickets')
.setLabel('Show Open Tickets')
.setStyle(ButtonStyle.Secondary),
new ButtonBuilder()
.setCustomId('backToProfile')
.setLabel('Back to Profile')
.setStyle(ButtonStyle.Secondary)
);
await i.update({ embeds: [statsEmbed], components: [statsButtonRow] });
}
if (i.customId === 'showOpenTickets') {
const TicketData = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8"));
const ticketsOpen = TicketData.tickets.filter(t => t.claimedBy === viewingUserId && t.open);
const openTicketsEmbed = new EmbedBuilder()
.setTitle('Active Tickets')
.setDescription(ticketsOpen.map(t => `<#${t.id}>`).join('\n') || 'No active tickets.')
.setColor(supportbot.Embed.Colours.General);
const openTicketsButtonRow = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId('backToStats')
.setLabel('Back to Stats')
.setStyle(ButtonStyle.Secondary)
);
await i.update({ embeds: [openTicketsEmbed], components: [openTicketsButtonRow] });
}
if (i.customId === 'backToStats') {
const TicketData = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8"));
const ticketsClaimed = TicketData.tickets.filter(t => t.claimedBy === viewingUserId);
const ticketsOpen = ticketsClaimed.filter(t => t.open);
const totalTickets = ticketsClaimed.length;
let totalResponseTime = 0;
let responseCount = 0;
ticketsClaimed.forEach(ticket => {
if (ticket.claimedAt && ticket.createdAt) {
const responseTime = new Date(ticket.claimedAt) - new Date(ticket.createdAt);
totalResponseTime += responseTime;
responseCount++;
}
});
const averageResponseTime = responseCount ? totalResponseTime / responseCount : 0;
const averageResponseTimeMinutes = Math.round(averageResponseTime / 60000);
const statsEmbed = new EmbedBuilder()
.setTitle(msgconfig.TicketStats.Title)
.setDescription(`> Ticket stats for <@${viewingUserId}>`)
.setColor(supportbot.Embed.Colours.General)
.addFields(
{ name: msgconfig.TicketStats.OpenTickets, value: `${ticketsOpen.length}`, inline: true },
{ name: msgconfig.TicketStats.TotalTickets, value: `${totalTickets}`, inline: true },
{ name: msgconfig.TicketStats.ResponseTime, value: `${averageResponseTimeMinutes} minutes`, inline: false }
);
const statsButtonRow = new ActionRowBuilder()
.addComponents(
new ButtonBuilder()
.setCustomId('showOpenTickets')
.setLabel('Show Open Tickets')
.setStyle(ButtonStyle.Secondary),
new ButtonBuilder()
.setCustomId('backToProfile')
.setLabel('Back to Profile')
.setStyle(ButtonStyle.Secondary)
);
await i.update({ embeds: [statsEmbed], components: [statsButtonRow] });
}
if (i.customId === 'backToProfile') {
await i.update({ embeds: [profileEmbed], components: buttonRow.components.length > 0 ? [buttonRow] : [] });
}
});
}
});
module.exports.clockedInUsers = clockedInUsers;
-134
View File
@@ -1,134 +0,0 @@
const fs = require("fs");
const {
EmbedBuilder,
ApplicationCommandOptionType,
ApplicationCommandType,
ChannelType,
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
} = require("discord.js");
const yaml = require("js-yaml");
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
const Command = require("../Structures/Command.js");
module.exports = new Command({
name: cmdconfig.RemoveUser.Command,
description: cmdconfig.RemoveUser.Description,
type: ApplicationCommandType.ChatInput,
options: [
{
name: "user",
description: "The user to remove",
type: ApplicationCommandOptionType.User,
required: true,
},
],
permissions: cmdconfig.RemoveUser.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);
if (!SupportStaff || !Admin) {
return interaction.reply(
"Some roles seem to be missing!\nPlease check for errors when starting the bot."
);
}
const NoPerms = new EmbedBuilder()
.setTitle("Invalid Permissions!")
.setDescription(
`${msgconfig.Error.IncorrectPerms}\n\nRole Required: \`${supportbot.Roles.StaffMember.Staff}\` or \`${supportbot.Roles.StaffMember.Admin}\``
)
.setColor(supportbot.Embed.Colours.Warn);
if (
!interaction.member.roles.cache.has(SupportStaff.id) &&
!interaction.member.roles.cache.has(Admin.id)
) {
return interaction.reply({ embeds: [NoPerms] });
}
const userToRemove = interaction.options.getUser("user");
const ticketChannel = interaction.channel;
const ticketDataPath = "./Data/TicketData.json";
// Check if the channel is a ticket thread or text channel based on TicketType
if (
(supportbot.Ticket.TicketType === "threads" && !ticketChannel.isThread()) ||
(supportbot.Ticket.TicketType === "channels" && ticketChannel.type !== ChannelType.GuildText)
) {
const onlyInTicket = new EmbedBuilder()
.setTitle(msgconfig.RemoveUser.NotInTicket_Title)
.setDescription(msgconfig.RemoveUser.NotInTicket_Description)
.setColor(supportbot.Embed.Colours.Error);
return interaction.reply({
embeds: [onlyInTicket],
ephemeral: true,
});
}
try {
// Remove user from thread or channel based on TicketType
if (supportbot.Ticket.TicketType === "threads") {
await ticketChannel.members.remove(userToRemove.id); // For threads
} else if (supportbot.Ticket.TicketType === "channels") {
await ticketChannel.permissionOverwrites.delete(userToRemove.id); // For channels
}
// Update the ticket data
const ticketData = JSON.parse(fs.readFileSync(ticketDataPath, "utf8"));
const ticketIndex = ticketData.tickets.findIndex((t) => t.id === ticketChannel.id);
if (ticketIndex !== -1) {
const subUsers = ticketData.tickets[ticketIndex].subUsers;
const userIndex = subUsers.indexOf(userToRemove.id);
if (userIndex !== -1) {
subUsers.splice(userIndex, 1);
fs.writeFileSync(ticketDataPath, JSON.stringify(ticketData, null, 4));
}
}
const removedEmbed = new EmbedBuilder()
.setTitle(msgconfig.RemoveUser.Removed_Title)
.setDescription(msgconfig.RemoveUser.Removed_Message)
.setColor(supportbot.Embed.Colours.Success);
await interaction.reply({
embeds: [removedEmbed],
ephemeral: true,
});
const removedFromTicketEmbed = new EmbedBuilder()
.setTitle(msgconfig.RemoveUser.RemovedFromTicket_Title)
.setDescription(msgconfig.RemoveUser.RemovedFromTicket_Description.replace(
"%channel_link%",
`[${ticketChannel.name}](https://discord.com/channels/${ticketChannel.guild.id}/${ticketChannel.id})`
))
.setColor(supportbot.Embed.Colours.General);
await userToRemove.send({
embeds: [removedFromTicketEmbed],
});
} catch (err) {
console.error("Error removing user from the ticket:", err);
const errorEmbed = new EmbedBuilder()
.setTitle(msgconfig.RemoveUser.Error_Title)
.setDescription(msgconfig.RemoveUser.Error_Removing)
.setColor(supportbot.Embed.Colours.Error);
await interaction.reply({
embeds: [errorEmbed],
ephemeral: true,
});
}
},
});
-179
View File
@@ -1,179 +0,0 @@
const fs = require("fs");
const Discord = require("discord.js");
const yaml = require("js-yaml");
// Load configurations
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");
// Ensure suggestions file exists and is correctly loaded
let suggestions;
try {
suggestions = require("../Data/SuggestionData.json");
} catch (error) {
suggestions = {};
}
module.exports = new Command({
name: cmdconfig.Suggestion.Command,
description: cmdconfig.Suggestion.Description,
type: Discord.ApplicationCommandType.ChatInput,
options: [
{
name: "suggestion",
description: "Create a Suggestion",
type: Discord.ApplicationCommandOptionType.String,
required: true,
},
],
permissions: cmdconfig.Suggestion.Permission,
async run(interaction) {
const { getChannel } = interaction.client;
const mode = supportbot.Suggestions.Mode || "Standard";
let suggestion = interaction.options.getString("suggestion");
if (mode === "Forum") {
const forumChannel = await interaction.guild.channels.fetch(supportbot.Suggestions.ForumChannel).catch(() => null);
const NoChannel = new Discord.EmbedBuilder()
.setTitle("Missing Forum Channel!")
.setDescription(msgconfig.Error.MissingChannel)
.setColor(supportbot.Embed.Colours.Error);
if (!forumChannel || forumChannel.type !== Discord.ChannelType.GuildForum) {
return interaction.reply({ embeds: [NoChannel], ephemeral: true });
}
const SuggestForumEmbed = new Discord.EmbedBuilder()
.addFields(
{ name: "Suggestion", value: suggestion, inline: true },
{ name: "From", value: `<@${interaction.user.id}>` },
{ name: `${supportbot.Suggestions.UpvoteEmoji} ${supportbot.Suggestions.UpvoteTitle}`, value: "0", inline: true },
{ name: `${supportbot.Suggestions.DownvoteEmoji} ${supportbot.Suggestions.DownvoteTitle}`, value: "0", inline: true }
)
.setThumbnail(interaction.user.displayAvatarURL())
.setFooter({
text: supportbot.Embed.Footer,
iconURL: interaction.user.displayAvatarURL(),
})
.setColor(supportbot.Embed.Colours.General);
const UpvoteForumButton = new Discord.ButtonBuilder()
.setCustomId("upvote")
.setEmoji(supportbot.Suggestions.UpvoteEmoji)
.setStyle(supportbot.Suggestions.Buttons.Upvote);
const DownvoteForumButton = new Discord.ButtonBuilder()
.setCustomId("downvote")
.setEmoji(supportbot.Suggestions.DownvoteEmoji)
.setStyle(supportbot.Suggestions.Buttons.Downvote);
const RemoveVoteForumButton = new Discord.ButtonBuilder()
.setCustomId("removevote")
.setLabel(supportbot.Suggestions.Buttons.RemoveVote_Title)
.setStyle(supportbot.Suggestions.Buttons.RemoveVote);
const row = new Discord.ActionRowBuilder().addComponents(UpvoteForumButton, DownvoteForumButton, RemoveVoteForumButton);
const post = await forumChannel.threads.create({
name: suggestion.slice(0, 100),
message: {
embeds: [SuggestForumEmbed],
components: [row],
},
reason: "New suggestion submitted",
autoArchiveDuration: 1440,
});
suggestions[post.id] = {
suggestion: suggestion,
author: interaction.user.id,
upvotes: [],
downvotes: []
};
fs.writeFileSync("./Data/SuggestionData.json", JSON.stringify(suggestions, null, 2));
const successEmbed = new Discord.EmbedBuilder()
.setTitle(msgconfig.Suggestions.Sent_Title)
.setDescription(msgconfig.Suggestions.Sent)
.addFields({ name: "Posted in forum:", value: `<#${forumChannel.id}>` })
.setColor(supportbot.Embed.Colours.Success);
return interaction.reply({ embeds: [successEmbed], ephemeral: true });
}
const suggestChannel = await getChannel(supportbot.Suggestions.Channel, interaction.guild);
const NoChannel = new Discord.EmbedBuilder()
.setTitle("Missing Channel!")
.setDescription(msgconfig.Error.MissingChannel)
.setColor(supportbot.Embed.Colours.Error);
if (!suggestChannel) return interaction.reply({ embeds: [NoChannel] });
const SuggestEmbed = new Discord.EmbedBuilder()
.addFields(
{ name: "Suggestion", value: suggestion, inline: true },
{ name: "From", value: `<@${interaction.user.id}>` },
{ name: `${supportbot.Suggestions.UpvoteEmoji} ${supportbot.Suggestions.UpvoteTitle}`, value: "0", inline: true },
{ name: `${supportbot.Suggestions.DownvoteEmoji} ${supportbot.Suggestions.DownvoteTitle}`, value: "0", inline: true }
)
.setThumbnail(interaction.user.displayAvatarURL())
.setFooter({
text: supportbot.Embed.Footer,
iconURL: interaction.user.displayAvatarURL(),
})
.setColor(supportbot.Embed.Colours.General);
const UpvoteButton = new Discord.ButtonBuilder()
.setCustomId("upvote")
.setEmoji(supportbot.Suggestions.UpvoteEmoji)
.setStyle(supportbot.Suggestions.Buttons.Upvote);
const DownvoteButton = new Discord.ButtonBuilder()
.setCustomId("downvote")
.setEmoji(supportbot.Suggestions.DownvoteEmoji)
.setStyle(supportbot.Suggestions.Buttons.Downvote);
const RemoveVoteButton = new Discord.ButtonBuilder()
.setCustomId("removevote")
.setLabel(supportbot.Suggestions.Buttons.RemoveVote_Title)
.setStyle(supportbot.Suggestions.Buttons.RemoveVote);
const row = new Discord.ActionRowBuilder().addComponents(UpvoteButton, DownvoteButton, RemoveVoteButton);
const suggestionMsg = await suggestChannel.send({ embeds: [SuggestEmbed], components: [row] });
suggestions[suggestionMsg.id] = {
suggestion: suggestion,
author: interaction.user.id,
upvotes: [],
downvotes: []
};
fs.writeFileSync("./Data/SuggestionData.json", JSON.stringify(suggestions, null, 2));
if (supportbot.Suggestions.Threads.Enabled) {
await suggestionMsg.startThread({
name: supportbot.Suggestions.Threads.Title,
autoArchiveDuration: 60,
type: Discord.ChannelType.PublicThread,
reason: supportbot.Suggestions.Threads.Reason,
});
}
const Submitted = new Discord.EmbedBuilder()
.setTitle(msgconfig.Suggestions.Sent_Title)
.setDescription(msgconfig.Suggestions.Sent)
.addFields({ name: "Sent to:", value: `<#${suggestChannel.id}>` })
.setColor(supportbot.Embed.Colours.Success);
await interaction.reply({ ephemeral: true, embeds: [Submitted] });
},
});
-120
View File
@@ -1,120 +0,0 @@
// ticketpanel.js
const fs = require("fs");
const {
EmbedBuilder,
ApplicationCommandType,
ActionRowBuilder,
ButtonBuilder,
} = require("discord.js");
const yaml = require("js-yaml");
const panelconfig = yaml.load(fs.readFileSync("./Configs/ticket-panel.yml", "utf8"));
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
const Command = require("../Structures/Command.js");
module.exports = new Command({
name: cmdconfig.TicketPanel.Command,
description: cmdconfig.TicketPanel.Description,
type: ApplicationCommandType.ChatInput,
options: [],
permissions: cmdconfig.TicketPanel.Permission,
async run(interaction) {
if (!panelconfig.Panel) {
console.log(
"\u001b[31m",
`[TICKET PANEL]`,
"\u001b[33m",
"Ticket Panel is not setup, You can set this in", `\u001b[31m`, '/Configs/ticket-panel.yml', `\n `,
);
return interaction.reply({
content: "Ticket Panel is not set up. Please configure it in `/Configs/ticket-panel.yml`.",
ephemeral: true,
});
}
const { getChannel } = interaction.client;
const channel = await getChannel(
supportbot.Ticket.TicketHome,
interaction.guild
);
if (!channel) {
console.log(`[TICKET PANEL] ${channelName} channel not found. Please check your config file.`);
return interaction.reply({
content: `${channelName} channel not found. Please check your config file.`,
ephemeral: true,
});
}
const panelid = JSON.parse(fs.readFileSync("./Data/ticket-panel-id.json", "utf8"));
try {
await channel.messages.fetch(panelid.TicketPanelID);
return interaction.reply({
content: "Ticket panel message already exists.",
ephemeral: true,
});
} catch (error) {
let embed = new EmbedBuilder()
.setTitle(panelconfig.PanelTitle)
.setColor(panelconfig.PanelColour)
.setFooter({
text: supportbot.Embed.Footer,
iconURL: interaction.user.displayAvatarURL(),
});
if (panelconfig.TicketPanel_Description) {
embed.setDescription(panelconfig.PanelMessage);
}
if (panelconfig.TicketPanel_Thumbnail) {
embed.setThumbnail(panelconfig.PanelThumbnail);
}
if (panelconfig.TicketPanel_Image) {
embed.setImage(panelconfig.PanelImage);
}
const createTicketButton = new ButtonBuilder()
.setCustomId("createticket")
.setLabel(panelconfig.Button.Text)
.setEmoji(panelconfig.Button.Emoji)
.setStyle(panelconfig.Button.Color);
let row = new ActionRowBuilder().addComponents(createTicketButton);
try {
const message = await channel.send({
embeds: [embed],
components: [row],
});
let data = {
id: panelid.id,
TicketPanelID: message.id,
};
fs.writeFileSync(
"./Data/ticket-panel-id.json",
JSON.stringify(data),
"utf8"
);
return interaction.reply({
content: "Ticket panel message has been sent!",
ephemeral: true,
});
} catch (e) {
console.log("Error sending message:", e);
return interaction.reply({
content: "An error occurred while sending the ticket panel message.",
ephemeral: true,
});
}
}
},
});
+6
View File
@@ -73,6 +73,12 @@ Suggestion:
Permission: ["SendMessages"]
Enabled: true
SuggestionAdmin:
Command: "suggestadmin"
Description: "Update a suggestion’s status."
Permission: ['SendMessages']
# Disable "Suggestion" to disable this command.
Embed:
Command: "embed"
Description: "Build a discord embed."
+12 -6
View File
@@ -21,7 +21,7 @@ SupportBot_Version: "v8.4-DEV-1"
General:
Name: "SupportBot"
Token: "DISCORD_BOT_TOKEN"
Token: "BOT_TOKEN"
Addons:
Enabled: true
@@ -95,7 +95,7 @@ Leave:
# ----------------------------
Suggestions:
Mode: "Forum" # or Forum
Mode: "Channel" # Channel or Forum
ForumChannel: "1402655496477081671"
Channel: "1295788900094316706"
@@ -129,13 +129,19 @@ Suggestions:
# 3 - Green
# 4 - Red
Tags:
Created: "1418937323118137435"
Considering: "1418937237156139130"
Solved: "1418937168029679697"
Denied: "1418937185469464647"
# -------------------------
# Tickets
# -------------------------
VoiceTickets:
Name: "%username%'s VC"
Category: "1297539636159315999"
Category: "1419062819688550460"
Ticket:
TicketType: "channels"
@@ -151,9 +157,9 @@ Ticket:
# - Channel where ticket threads are made. (only if ticket threads is enabled above.)
# For TicketType: channels
TicketChannelsCategory: "1295789095070728253"
TicketChannelsCategory2: "1295789210040537088" # This will be used if the first category has reached the max channel limit.
TicketArchiveCategory: "1295789286322343946"
TicketChannelsCategory: "1418988408193155112"
TicketChannelsCategory2: "1418988456167870615" # This will be used if the first category has reached the max channel limit.
TicketArchiveCategory: "1418988434600759348"
# ---
Channel: "ticket-" # The prefix for ticket channel names
-1
View File
@@ -1 +0,0 @@
{}
-29
View File
@@ -1,29 +0,0 @@
{
"bio": "",
"timezone": "",
"clockedIn": true,
"workingHours": 0,
"lastClockInTime": null,
"schedule": {
"monday": {
"start": "09:00",
"end": "17:00"
},
"tuesday": {
"start": "09:00",
"end": "17:00"
},
"wednesday": {
"start": "09:00",
"end": "17:00"
},
"thursday": {
"start": "09:00",
"end": "17:00"
},
"friday": {
"start": "09:00",
"end": "17:00"
}
}
}
-5
View File
@@ -1,5 +0,0 @@
{
"bio": "",
"timezone": "",
"clockedIn": false
}
-1
View File
@@ -1 +0,0 @@
{}
-1
View File
@@ -1 +0,0 @@
{}
-5
View File
@@ -1,5 +0,0 @@
{
"tickets": [
{}
]
}
-5
View File
@@ -1,5 +0,0 @@
{
"bio": "",
"timezone": "",
"clockedIn": false
}
-1
View File
@@ -1 +0,0 @@
{}
Binary file not shown.
-4
View File
@@ -1,4 +0,0 @@
{
"id": 0,
"TicketPanelID": "000000000000000"
}
+99 -198
View File
@@ -7,13 +7,7 @@ const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8"));
const Event = require("../Structures/Event.js");
let suggestions;
try {
suggestions = require("../Data/SuggestionData.json");
} catch (error) {
suggestions = {};
}
const db = require("../Structures/Database.js");
module.exports = new Event("interactionCreate", async (client, interaction) => {
@@ -83,7 +77,7 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
interaction.customId = `createticket-${reason}`;
const cmd = client.commands.get(cmdconfig.OpenTicket.Command);
if (cmd) {
interaction.reason = reason; // Attach the reason to the interaction object
interaction.reason = reason;
await cmd.run(interaction);
} else {
console.error("Command not found.");
@@ -93,31 +87,24 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
if (interaction.customId === "renameTicketModal") {
const newTicketName = interaction.fields.getTextInputValue("renameTicket");
// Fixing the ticketChannelId extraction and lookup
const ticketChannelId = interaction.channel.id;
let ticketChannel;
if (supportbot.Ticket.TicketType === "threads") {
ticketChannel = interaction.guild.channels.cache.get(ticketChannelId);
} else if (supportbot.Ticket.TicketType === "channels") {
ticketChannel = interaction.guild.channels.cache.get(ticketChannelId);
}
if (!ticketChannel) {
return interaction.reply({
content: "Ticket channel not found.",
ephemeral: true,
});
}
try {
await ticketChannel.setName(newTicketName);
const renamedsuccess = new Discord.EmbedBuilder()
.setDescription(msgconfig.Ticket.RenamedSuccess)
.setColor(supportbot.Embed.Colours.Success);
return interaction.reply({
embeds: [renamedsuccess],
ephemeral: true,
@@ -146,137 +133,107 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
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],
},
],
const ticketInfo = db.getTicket(interaction.channel.id);
if (!ticketInfo) {
return interaction.reply({
content: "⚠️ This channel isn’t a valid ticket.",
flags: Discord.MessageFlags.Ephemeral
});
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;
const user = interaction.guild.members.cache.get(ticketInfo.user_id) || ticketInfo.user_id;
const username = user?.user?.username || "Unknown User";
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,
});
}
const SupportVoice = await interaction.guild.channels.create({
name: supportbot.VoiceTickets.Name.replace(/%username%/g, username),
type: Discord.ChannelType.GuildVoice,
parent: VCcategory,
permissionOverwrites: [
{
id: interaction.guild.id,
deny: [Discord.PermissionsBitField.Flags.Connect]
},
{
id: user.id || user, // works if user is a Member or just ID
allow: [
Discord.PermissionsBitField.Flags.Connect,
Discord.PermissionsBitField.Flags.Speak,
Discord.PermissionsBitField.Flags.ViewChannel,
],
},
],
});
db.updateTicketVoice(ticketInfo.ticket_id, SupportVoice.id);
const vcmadeembed = new Discord.EmbedBuilder()
.setDescription(`> **Support VC Created:** <#${SupportVoice.id}>`)
.setColor(supportbot.Embed.Colours.Success);
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);
await interaction.reply({
embeds: [vcmadeembed],
components: [VCDeleteRow],
});
break;
case "archiveticket":
const arcembed = new Discord.EmbedBuilder()
.setDescription(msgconfig.Ticket.TicketArchived)
.setColor(supportbot.Embed.Colours.Success);
const CloseButton = new Discord.ButtonBuilder()
.setCustomId("confirmCloseTicket")
.setLabel(supportbot.Ticket.Close.Confirmation_Button)
.setEmoji(supportbot.Ticket.Close.Confirmation_Emoji)
.setStyle(supportbot.Ticket.Close.Confirmation_Style);
const unarchiveButton = new Discord.ButtonBuilder()
.setCustomId("unarchiveTicket")
.setLabel(supportbot.Buttons.Tickets.Unarchive)
.setEmoji(supportbot.Buttons.Tickets.Unarchive_Emoji)
.setStyle(supportbot.Buttons.Tickets.Unarchive_Style);
const ArchivedOptions = new Discord.ActionRowBuilder()
.addComponents(CloseButton, unarchiveButton);
await interaction.channel.send({
embeds: [arcembed],
components: [ArchivedOptions],
});
if (supportbot.Ticket.TicketType === "threads") {
await interaction.channel.setArchived(true);
}
if (supportbot.Ticket.TicketType === "channels") {
const { getRole, getChannel } = interaction.client;
await interaction.channel.setParent(supportbot.Ticket.TicketArchiveCategory);
const ticketData = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8"));
const ticketInfo = ticketData.tickets.find(t => t.id === interaction.channel.id);
const tickets = db.getAllTickets();
const ticketInfo = tickets.find(t => t.ticket_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 user = interaction.guild.members.cache.get(ticketInfo.user_id) || ticketInfo.user_id;
const claimedBy = interaction.guild.members.cache.get(ticketInfo.claimed_by) || ticketInfo.claimed_by;
const Admin = await interaction.guild.roles.fetch(supportbot.Roles.StaffMember.Admin);
const Staff = await interaction.guild.roles.fetch(supportbot.Roles.StaffMember.Staff);
if (user && user.id) {
await interaction.channel.permissionOverwrites.edit(user.id, {
ViewChannel: false,
SendMessages: false,
});
}
if (supportbot.ClaimTickets === true) {
if (claimedBy && claimedBy.id) {
await interaction.channel.permissionOverwrites.edit(claimedBy.id, {
@@ -286,7 +243,6 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
});
}
}
if (supportbot.ClaimTickets === false) {
if (Staff && Staff.id) {
await interaction.channel.permissionOverwrites.edit(claimedBy.id, {
@@ -296,7 +252,6 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
});
}
}
if (Admin && Admin.id) {
await interaction.channel.permissionOverwrites.edit(Admin.id, {
ViewChannel: true,
@@ -304,59 +259,47 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
ReadMessageHistory: true,
});
}
await interaction.channel.permissionOverwrites.edit(interaction.guild.roles.everyone.id, {
ViewChannel: false,
SendMessages: false,
ReadMessageHistory: false,
});
}
}
break;
case "lockticket":
const lockembed = new Discord.EmbedBuilder()
.setDescription(msgconfig.Ticket.TicketLocked)
.setColor(supportbot.Embed.Colours.Success);
const unlockButton = new Discord.ButtonBuilder()
.setCustomId("unlockTicket")
.setLabel(supportbot.Buttons.Tickets.Unlock)
.setEmoji(supportbot.Buttons.Tickets.Unlock_Emoji)
.setStyle(supportbot.Buttons.Tickets.Unlock_Style);
const LockedOptions = new Discord.ActionRowBuilder()
.addComponents(unlockButton);
await interaction.channel.send({
embeds: [lockembed],
components: [LockedOptions],
ephemeral: false,
});
if (supportbot.Ticket.TicketType === "threads") {
await interaction.channel.setLocked(true);
}
break;
case "unlockTicket":
const unlockEmbed = new Discord.EmbedBuilder()
.setDescription(msgconfig.Ticket.TicketUnlocked)
.setColor(supportbot.Embed.Colours.Success);
await interaction.channel.send({
embeds: [unlockEmbed],
ephemeral: false,
});
if (supportbot.Ticket.TicketType === "threads") {
await interaction.channel.setLocked(false);
}
break;
case "enableinvites":
@@ -383,38 +326,29 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
);
await interaction.showModal(modal);
break;
}
}
}
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);
const ticketData = db.getAllTickets();
const ticketInfo = ticketData.find(t => t.ticket_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,
@@ -422,11 +356,9 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
}
} 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,
@@ -436,36 +368,28 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
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.customId === "unarchiveTicket") {
if (supportbot.Ticket.TicketType === "threads") {
await interaction.channel.setArchived(false);
}
if (supportbot.Ticket.TicketType === "channels") {
const { getRole, getChannel } = interaction.client;
await interaction.channel.setParent(supportbot.Ticket.TicketChannelsCategory);
const ticketData = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8"));
const ticketInfo = ticketData.tickets.find(t => t.id === interaction.channel.id);
const ticketData = db.getAllTickets();
const ticketInfo = ticketData.find(t => t.ticket_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 user = interaction.guild.members.cache.get(ticketInfo.user_id) || ticketInfo.user_id;
const claimedBy = interaction.guild.members.cache.get(ticketInfo.claimed_by) || ticketInfo.claimed_by;
const Admin = await interaction.guild.roles.fetch(supportbot.Roles.StaffMember.Admin);
const Staff = await interaction.guild.roles.fetch(supportbot.Roles.StaffMember.Staff);
if (user && user.id) {
await interaction.channel.permissionOverwrites.edit(user.id, {
ViewChannel: true,
@@ -473,7 +397,6 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
ReadMessageHistory: true,
});
}
if (supportbot.ClaimTickets === true) {
if (claimedBy && claimedBy.id) {
await interaction.channel.permissionOverwrites.edit(claimedBy.id, {
@@ -483,7 +406,6 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
});
}
}
if (supportbot.ClaimTickets === false) {
if (Staff && Staff.id) {
await interaction.channel.permissionOverwrites.edit(claimedBy.id, {
@@ -493,7 +415,6 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
});
}
}
if (Admin && Admin.id) {
await interaction.channel.permissionOverwrites.edit(Admin.id, {
ViewChannel: true,
@@ -501,7 +422,6 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
ReadMessageHistory: true,
});
}
await interaction.channel.permissionOverwrites.edit(interaction.guild.roles.everyone.id, {
ViewChannel: false,
SendMessages: false,
@@ -509,16 +429,13 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
});
}
}
const unrcembed = new Discord.EmbedBuilder()
.setDescription(msgconfig.Ticket.TicketUnarchived)
.setColor(supportbot.Embed.Colours.Success);
await interaction.update({
embeds: [unrcembed],
components: [],
});
}
if (interaction.customId === "createticket") {
@@ -535,7 +452,6 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
.setRequired(true)
)
);
await interaction.showModal(modal);
} else {
try {
@@ -566,7 +482,6 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
.setRequired(true)
)
);
await interaction.showModal(modal);
} else {
try {
@@ -589,7 +504,7 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
});
}
const { getRole, getChannel } = interaction.client;
const { getRole } = interaction.client;
const Staff = await getRole(supportbot.Roles.StaffMember.Staff, interaction.guild);
const Admin = await getRole(supportbot.Roles.StaffMember.Admin, interaction.guild);
@@ -601,10 +516,7 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
.setColor(supportbot.Embed.Colours.Warn);
if (!Staff || !Admin) {
return interaction.reply({
embeds: [RolePerms],
ephemeral: true,
});
return interaction.reply({ embeds: [RolePerms], ephemeral: true });
}
const NoPermsClaim = new Discord.EmbedBuilder()
@@ -615,65 +527,55 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
.setColor(supportbot.Embed.Colours.Error);
if (!interaction.member.roles.cache.has(Staff.id) && !interaction.member.roles.cache.has(Admin.id)) {
return interaction.reply({
embeds: [NoPermsClaim],
ephemeral: true,
});
return interaction.reply({ embeds: [NoPermsClaim], ephemeral: true });
}
if (supportbot.Ticket.TicketType === "threads") {
await ticketChannel.members.add(interaction.user.id);
} else if (supportbot.Ticket.TicketType === "channels") {
await ticketChannel.members.add(interaction.user.id);
} else {
await ticketChannel.permissionOverwrites.create(interaction.user.id, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
await ticketChannel.permissionOverwrites.create(Admin, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
await ticketChannel.permissionOverwrites.create(interaction.guild.roles.everyone, {
ViewChannel: false,
});
}
});
}
const pingclaimedstaff = await ticketChannel.send(`<@${interaction.user.id}>`);
setTimeout(() => pingclaimedstaff.delete(), 2000);
setTimeout(() => {
pingclaimedstaff.delete();
}, 2000);
const TicketData = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8"));
const ticketIndex = TicketData.tickets.findIndex((t) => t.id === ticketChannelId);
if (ticketIndex !== -1) {
TicketData.tickets[ticketIndex].claimedBy = interaction.user.id;
TicketData.tickets[ticketIndex].claimedAt = new Date().toISOString();
fs.writeFileSync("./Data/TicketData.json", JSON.stringify(TicketData, null, 4));
const ticketInfo = db.getTicket(ticketChannelId);
if (ticketInfo) {
db.updateTicketStatus(ticketChannelId, ticketInfo.status);
ticketInfo.claimed_by = interaction.user.id;
}
const claimMessage = await interaction.message.fetch();
const claimedEmbed = new Discord.EmbedBuilder(claimMessage.embeds[0].data)
.setTitle(msgconfig.Ticket.ClaimTickets.ClaimedTitle)
.setDescription(
msgconfig.Ticket.ClaimTickets.ClaimMessage_Edit.replace("%user%", interaction.user.id).replace("%channel%", ticketChannel.id)
msgconfig.Ticket.ClaimTickets.ClaimMessage_Edit
.replace("%user%", `${interaction.user.id}`)
.replace("%channel%", `${ticketChannel.id}`)
)
.setColor(supportbot.Embed.Colours.Success);
const successfullyClaimed = new Discord.EmbedBuilder()
.setTitle(msgconfig.Ticket.ClaimTickets.ClaimedTitle)
.setDescription(msgconfig.Ticket.ClaimTickets.Claimed.replace("%channel%", ticketChannel.id))
.setDescription(msgconfig.Ticket.ClaimTickets.Claimed
.replace("%channel%", `${ticketChannel.id}`)
)
.setColor(supportbot.Embed.Colours.Success);
await claimMessage.edit({ embeds: [claimedEmbed], components: [] });
await interaction.reply({
embeds: [successfullyClaimed],
ephemeral: true,
});
await interaction.reply({ embeds: [successfullyClaimed], ephemeral: true });
}
// SUGGESTION INTERACTIONS [START]
@@ -777,7 +679,6 @@ module.exports = new Event("interactionCreate", async (client, interaction) => {
});
}
// Modal submission handler for editing profile
if (interaction.type === Discord.InteractionType.ModalSubmit && interaction.customId === "editProfileModal") {
const userId = interaction.user.id;
const profilePath = `./Data/Profiles/${userId}.json`;
+2 -4
View File
@@ -60,9 +60,7 @@ const logStartup = async (client) => {
// Links section
console.log(gradient.summer("┏━━━━━━━━━━━━━━━━━━━ Important Links ━━━━━━━━━━━━━━━━━━━┓"));
console.log(gradient.summer("┃ ┃"));
console.log(gradient.summer("┃ ") + "🌐 Website : " + gradient.cristal("https://emeraldsrv.com ┃"));
console.log(gradient.summer("┃ ") + "📚 Docs : " + gradient.cristal("https://docs.emeraldsrv.com ┃"));
console.log(gradient.summer("┃ ") + "💬 Community : " + gradient.cristal("https://community.emeraldsrv.com ┃"));
console.log(gradient.summer("┃ ") + "📚 Docs : " + gradient.cristal("https://emerald-services.gitbook.io/ ┃"));
console.log(gradient.summer("┃ ") + "🤝 Discord : " + gradient.cristal("https://dsc.gg/emerald-dev ┃"));
console.log(gradient.summer("┃ ┃"));
console.log(gradient.summer("┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n"));
@@ -123,7 +121,7 @@ const checkConfigs = async (client) => {
console.log(gradient.morning("┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n"));
};
module.exports = new Event("ready", async (client) => {
module.exports = new Event("clientReady", async (client) => {
setActivity(client, supportbot?.Activity?.Type, supportbot?.Activity?.Status);
await logStartup(client);
await checkConfigs(client);
+41 -27
View File
@@ -1,12 +1,27 @@
const fs = require("fs");
const path = require("path");
const Discord = require("discord.js");
const { GatewayIntentBits, Partials } = require('discord.js');
const yaml = require("js-yaml");
const { Command, Event } = require('./Addon.js'); // Adjust the path as needed
const { Command, Event } = require('./Addon.js');
const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8"));
const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8"));
function walk(dir, ext = ".js", fileList = []) {
const files = fs.readdirSync(dir);
for (const file of files) {
const full = `${dir}/${file}`;
const stat = fs.statSync(full);
if (stat.isDirectory()) {
walk(full, ext, fileList);
} else if (file.endsWith(ext)) {
fileList.push(full);
}
}
return fileList;
}
class Client extends Discord.Client {
constructor() {
super({
@@ -23,7 +38,7 @@ class Client extends Discord.Client {
}
async getChannel(channel, guild) {
if (!channel) return null; // Add check for undefined channel
if (!channel) return null;
return guild.channels.cache.find(
(c) =>
(c.type === Discord.ChannelType.GuildText || c.type === Discord.ChannelType.GuildNews) &&
@@ -32,14 +47,14 @@ class Client extends Discord.Client {
}
async getRole(role, guild) {
if (!role) return null; // Add check for undefined role
if (!role) return null;
return guild.roles.cache.find(
(r) => r.id === role || (r.name && r.name.toLowerCase() === role.toLowerCase())
);
}
async getCategory(category, guild) {
if (!category) return null; // Add check for undefined category
if (!category) return null;
return guild.channels.cache.find(
(c) =>
c.type === Discord.ChannelType.GuildCategory &&
@@ -48,72 +63,75 @@ class Client extends Discord.Client {
}
async start(token) {
let tempCommandFiles = fs.readdirSync("./Commands").filter((file) => file.endsWith(".js"));
let tempCommandFiles = walk("./Commands");
if (cmdconfig.Suggestion.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => item !== "suggest.js");
tempCommandFiles = tempCommandFiles.filter(item => path.basename(item) !== "suggest.js");
}
if (cmdconfig.Suggestion.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => path.basename(item) !== "suggestadmin.js");
}
if (cmdconfig.Help.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => item !== "help.js");
tempCommandFiles = tempCommandFiles.filter(item => path.basename(item) !== "help.js");
}
if (cmdconfig.Info.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => item !== "info.js");
tempCommandFiles = tempCommandFiles.filter(item => path.basename(item) !== "info.js");
}
if (cmdconfig.OpenTicket.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => item !== "ticket.js");
tempCommandFiles = tempCommandFiles.filter(item => path.basename(item) !== "ticket.js");
}
if (cmdconfig.CloseTicket.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => item !== "close.js");
tempCommandFiles = tempCommandFiles.filter(item => path.basename(item) !== "close.js");
}
if (cmdconfig.Embed.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => item !== "embed.js");
tempCommandFiles = tempCommandFiles.filter(item => path.basename(item) !== "embed.js");
}
if (cmdconfig.Translate.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => item !== "translate.js");
tempCommandFiles = tempCommandFiles.filter(item => path.basename(item) !== "translate.js");
}
if (cmdconfig.UserInfo.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => item !== "userinfo.js");
tempCommandFiles = tempCommandFiles.filter(item => path.basename(item) !== "userinfo.js");
}
if (cmdconfig.Ping.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => item !== "Ping.js");
tempCommandFiles = tempCommandFiles.filter(item => path.basename(item) !== "Ping.js");
}
if (cmdconfig.AddUser.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => item !== "addUser.js");
tempCommandFiles = tempCommandFiles.filter(item => path.basename(item) !== "addUser.js");
}
if (cmdconfig.RemoveUser.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => item !== "removeUser.js");
tempCommandFiles = tempCommandFiles.filter(item => path.basename(item) !== "removeUser.js");
}
if (cmdconfig.ForceAddUser.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => item !== "forceaddUser.js");
tempCommandFiles = tempCommandFiles.filter(item => path.basename(item) !== "forceaddUser.js");
}
if (cmdconfig.Profile.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => item !== "profile.js");
tempCommandFiles = tempCommandFiles.filter(item => path.basename(item) !== "profile.js");
}
if (cmdconfig.Settings.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => item !== "settings.js");
tempCommandFiles = tempCommandFiles.filter(item => path.basename(item) !== "settings.js");
}
if (cmdconfig.Mod.Enabled === false) {
tempCommandFiles = tempCommandFiles.filter(item => item !== "mod.js");
tempCommandFiles = tempCommandFiles.filter(item => path.basename(item) !== "mod.js");
}
const commandFiles = tempCommandFiles;
const commands = commandFiles.map((file) => require(`../Commands/${file}`));
const commands = commandFiles.map((file) => require(`../${file}`));
// Load main commands
console.log(`\u001b[33m`, "――――――――――――――――――――――――――――――――――――――――――――");
commands.forEach((cmd) => {
@@ -121,7 +139,6 @@ class Client extends Discord.Client {
this.commands.set(cmd.name, cmd);
});
// Load addon commands and events
console.log(`\u001b[33m`, "▬▬▬▬▬▬▬ Commands ▬▬▬▬▬▬▬");
if (supportbot.General.Addons.Enabled) {
@@ -133,7 +150,6 @@ class Client extends Discord.Client {
return addon;
});
// Addons
console.log(" ");
console.log(`\u001b[33m`, "▬▬▬▬▬▬▬ Addons ▬▬▬▬▬▬▬");
@@ -163,8 +179,6 @@ class Client extends Discord.Client {
console.log(`\u001b[33m`, "▬▬▬▬▬▬▬ Addons ▬▬▬▬▬▬▬");
}
// Register Slash Commands
this.once("ready", async () => {
await this.guilds.cache.first()?.commands.set(this.commands);
console.log(
@@ -196,4 +210,4 @@ class Client extends Discord.Client {
}
}
module.exports = Client;
module.exports = Client;
+227
View File
@@ -0,0 +1,227 @@
// SupportBot | Emerald Services
// Database Structure
const Database = require("better-sqlite3");
const path = require("path");
const db = new Database(path.join(__dirname, "../Data/supportbot.db"));
// ----- Core tables -----
db.prepare(`
CREATE TABLE IF NOT EXISTS suggestions (
thread_id TEXT PRIMARY KEY,
author_id TEXT,
text TEXT,
status TEXT DEFAULT 'Created'
)
`).run();
db.prepare(`
CREATE TABLE IF NOT EXISTS profiles (
user_id TEXT PRIMARY KEY,
bio TEXT,
timezone TEXT,
clockedIn INTEGER DEFAULT 0
)
`).run();
db.prepare(`
CREATE TABLE IF NOT EXISTS tickets (
ticket_id TEXT PRIMARY KEY,
user_id TEXT,
status TEXT DEFAULT 'open',
created_at INTEGER,
updated_at INTEGER,
subject TEXT,
description TEXT
)
`).run();
try {
db.prepare("ALTER TABLE tickets ADD COLUMN voiceChannelId TEXT").run();
} catch (e) {
if (!String(e).includes("duplicate column name")) {
throw e;
}
}
db.prepare(`
CREATE TABLE IF NOT EXISTS ticket_panel (
id INTEGER PRIMARY KEY AUTOINCREMENT,
message_id TEXT,
channel_id TEXT,
created_at INTEGER
)
`).run();
db.prepare(`
CREATE TABLE IF NOT EXISTS ticket_users (
ticket_id TEXT,
user_id TEXT,
PRIMARY KEY(ticket_id, user_id)
)
`).run();
/* ---- NEW table for global bot settings ---- */
db.prepare(`
CREATE TABLE IF NOT EXISTS settings (
id INTEGER PRIMARY KEY,
data TEXT
)
`).run();
module.exports = {
// ----- Suggestions -----
addSuggestion(threadId, authorId, text) {
db.prepare(`
INSERT INTO suggestions (thread_id, author_id, text)
VALUES (@threadId, @authorId, @text)
ON CONFLICT(thread_id) DO NOTHING
`).run({ threadId, authorId, text });
},
setSuggestionStatus(threadId, status) {
db.prepare(`
UPDATE suggestions SET status=@status WHERE thread_id=@threadId
`).run({ threadId, status });
},
getSuggestion(threadId) {
return db.prepare(`
SELECT * FROM suggestions WHERE thread_id=@threadId
`).get({ threadId });
},
// ----- Profiles -----
getProfile(userId) {
const row = db.prepare(`SELECT * FROM profiles WHERE user_id=?`).get(userId);
if (!row) {
db.prepare(`
INSERT INTO profiles (user_id, bio, timezone, clockedIn)
VALUES (?, '', '', 0)
`).run(userId);
return { user_id: userId, bio: "", timezone: "", clockedIn: 0 };
}
return row;
},
updateProfile(userId, data) {
const { bio = "", timezone = "", clockedIn = 0 } = data;
db.prepare(`
INSERT INTO profiles (user_id, bio, timezone, clockedIn)
VALUES (@userId, @bio, @timezone, @clockedIn)
ON CONFLICT(user_id) DO UPDATE SET
bio = excluded.bio,
timezone = excluded.timezone,
clockedIn = excluded.clockedIn
`).run({ userId, bio, timezone, clockedIn });
},
setClockedIn(userId, value) {
db.prepare(`UPDATE profiles SET clockedIn=? WHERE user_id=?`)
.run(value ? 1 : 0, userId);
},
// ----- Tickets -----
addTicket(ticket) {
const now = Date.now();
db.prepare(`
INSERT INTO tickets (ticket_id, user_id, subject, description, created_at, updated_at)
VALUES (@ticket_id, @user_id, @subject, @description, @created_at, @updated_at)
ON CONFLICT(ticket_id) DO NOTHING
`).run({
ticket_id: ticket.id,
user_id: ticket.user,
subject: ticket.reason || "",
description: ticket.description || "",
created_at: now,
updated_at: now
});
},
updateTicketVoice(ticketId, voiceChannelId) {
db.prepare(`
UPDATE tickets SET voiceChannelId=@voiceChannelId, updated_at=@updatedAt WHERE ticket_id=@ticketId
`).run({ ticketId, voiceChannelId, updatedAt: Date.now() });
},
getTicketVoice(ticketId) {
return db.prepare(`SELECT voiceChannelId FROM tickets WHERE ticket_id=?`).get(ticketId);
},
deleteTicketVoice(ticketId) {
db.prepare(`
UPDATE tickets SET voiceChannelId=NULL, updated_at=@updatedAt WHERE ticket_id=@ticketId
`).run({ ticketId, updatedAt: Date.now() });
},
updateTicketStatus(ticketId, status) {
db.prepare(`
UPDATE tickets SET status=@status, updated_at=@updatedAt WHERE ticket_id=@ticketId
`).run({ status, updatedAt: Date.now(), ticketId });
},
getTicket(ticketId) {
return db.prepare(`
SELECT * FROM tickets WHERE ticket_id=@ticketId
`).get({ ticketId });
},
getAllTickets() {
return db.prepare(`SELECT * FROM tickets`).all();
},
saveTicketPanel(messageId, channelId) {
db.prepare(`
INSERT INTO ticket_panel (message_id, channel_id, created_at)
VALUES (@messageId, @channelId, @createdAt)
`).run({ messageId, channelId, createdAt: Date.now() });
},
getTicketPanel() {
return db.prepare(`
SELECT * FROM ticket_panel ORDER BY id DESC LIMIT 1
`).get();
},
// ----- Ticket participants -----
addUserToTicket(ticketId, userId) {
db.prepare(`
INSERT INTO ticket_users (ticket_id, user_id)
VALUES (?, ?)
ON CONFLICT(ticket_id, user_id) DO NOTHING
`).run(ticketId, userId);
},
removeUserFromTicket(ticketId, userId) {
db.prepare(`
DELETE FROM ticket_users WHERE ticket_id=? AND user_id=?
`).run(ticketId, userId);
},
getTicketUsers(ticketId) {
return db.prepare(`
SELECT user_id FROM ticket_users WHERE ticket_id=?
`).all(ticketId).map(r => r.user_id);
},
// ----- Global Settings -----
getSettings() {
const row = db.prepare(`SELECT data FROM settings WHERE id=1`).get();
if (!row) return {};
try {
return JSON.parse(row.data);
} catch {
return {};
}
},
saveSettings(obj) {
const data = JSON.stringify(obj);
db.prepare(`
INSERT INTO settings (id, data)
VALUES (1, @data)
ON CONFLICT(id) DO UPDATE SET data = excluded.data
`).run({ data });
}
};
+1
View File
@@ -1,5 +1,6 @@
// SupportBot | Emerald Services
// Event Structure
class Event {
constructor(event, run) {
this.event = event;
+12 -29
View File
@@ -1,47 +1,30 @@
// SupportBot | Emerald Services
// Ticket ID Structure
const fs = require("fs");
const db = require("./Database")
class TicketNumberID {
static async pad() {
let num = await this.get();
let n = num + "";
await this.set(num + 1);
return n.length >= 4 ? n : new Array(4 - n.length + 1).join("0") + n;
let num = await this.get()
let n = num + ""
await this.set(num + 1)
return n.length >= 4 ? n : new Array(4 - n.length + 1).join("0") + n
}
static async get() {
let ticketnumberID = await JSON.parse(
fs.readFileSync("./Data/ticket-panel-id.json", "utf8")
);
if (!ticketnumberID.id) {
return this.set(0);
} else {
return ticketnumberID.id;
}
const row = db.getTicketPanel()
if (!row) return await this.set(0)
return row.id
}
static async set(value) {
let ticketnumberID = await JSON.parse(
fs.readFileSync("./Data/ticket-panel-id.json", "utf8")
);
ticketnumberID.id = value;
fs.writeFileSync(
"./Data/ticket-panel-id.json",
JSON.stringify(ticketnumberID, null, 4),
(err) => {
if (err) console.error(err);
}
);
return value;
db.saveTicketPanel(`panel-${value}`, "none")
return value
}
static async reset() {
return await this.set(0);
return await this.set(0)
}
}
module.exports = TicketNumberID;
module.exports = TicketNumberID
+3
View File
@@ -21,7 +21,10 @@
"@paypal/checkout-server-sdk": "^1.0.3",
"@vitalets/google-translate-api": "^8.0.0",
"axios": "^1.7.2",
"better-sqlite3": "^12.2.0",
"discord.js": "^14.13.0",
"figlet": "^1.9.3",
"gradient-string": "^3.0.0",
"js": "^0.1.0",
"js-yaml": "^4.1.0",
"moment": "^2.29.3",