diff --git a/Addons/addon.js b/Addons/addon.js new file mode 100644 index 0000000..7609fd7 --- /dev/null +++ b/Addons/addon.js @@ -0,0 +1,26 @@ +const Discord = require("discord.js"); +const fs = require("fs"); +const yaml = require("js-yaml"); +const { Command } = require('../Structures/Addon.js'); // Adjust the path as needed +const supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8")); + +module.exports = new Command({ + name: "addon", + description: "This is a sample addon!", + options: [], + permissions: ["Administrator"], + + async run(interaction) { + let disableCommand = true; + + const PingEmbed = new Discord.EmbedBuilder() + .setDescription( + `This is a sample addon.\n\nPong! \`${interaction.client.ws.ping} ms\`` + ) + .setColor(supportbot.Embed.Colours.General); + + interaction.reply({ + embeds: [PingEmbed], + }); + }, + }); \ No newline at end of file diff --git a/Commands/addUser.js b/Commands/addUser.js new file mode 100644 index 0000000..179c410 --- /dev/null +++ b/Commands/addUser.js @@ -0,0 +1,193 @@ +const fs = require("fs"); +const { + EmbedBuilder, + ApplicationCommandOptionType, + ApplicationCommandType, + 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.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"; + + // Check if the channel is a ticket thread + if (!ticketChannel.isThread()) { + 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 { + await ticketChannel.members.add(userToAdd.id); + + 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: [], + }); + + 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], + }); + } + } + }, +}); diff --git a/Commands/close.js b/Commands/close.js index dc497ab..328d190 100644 --- a/Commands/close.js +++ b/Commands/close.js @@ -81,7 +81,8 @@ module.exports = new Command({ const CloseButton = new Discord.ButtonBuilder() .setCustomId("confirmCloseTicket") .setLabel(supportbot.Ticket.Close.Confirmation_Button) - .setStyle(Discord.ButtonStyle.Danger); + .setEmoji(supportbot.Ticket.Close.Confirmation_Emoji) + .setStyle(supportbot.Ticket.Close.Confirmation_Style); const row = new Discord.ActionRowBuilder().addComponents(CloseButton); diff --git a/Commands/forceaddUser.js b/Commands/forceaddUser.js new file mode 100644 index 0000000..43908a4 --- /dev/null +++ b/Commands/forceaddUser.js @@ -0,0 +1,119 @@ +// File: Commands/ForceAddUser.js +const fs = require("fs"); +const { + EmbedBuilder, + ApplicationCommandOptionType, + ApplicationCommandType, +} = 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.ForceAddUser.Command, + description: cmdconfig.ForceAddUser.Description, + type: ApplicationCommandType.ChatInput, + options: [ + { + name: "user", + description: "The user to add", + type: ApplicationCommandOptionType.User, + required: true, + }, + ], + permissions: cmdconfig.ForceAddUser.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 userToAdd = interaction.options.getUser("user"); + const ticketChannel = interaction.channel; + const ticketDataPath = "./Data/TicketData.json"; + + if (!ticketChannel.isThread()) { + const onlyInTicket = new EmbedBuilder() + .setTitle(msgconfig.ForceAddUser.NotInTicket_Title) + .setDescription(msgconfig.ForceAddUser.NotInTicket_Description) + .setColor(supportbot.Embed.Colours.Error); + + return interaction.reply({ + embeds: [onlyInTicket], + ephemeral: true, + }); + } + + try { + await ticketChannel.members.add(userToAdd.id); + + 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 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], + ephemeral: true, + }); + + 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], + ephemeral: true, + }); + } + }, +}); diff --git a/Commands/help.js b/Commands/help.js index edea11b..7e0f48a 100644 --- a/Commands/help.js +++ b/Commands/help.js @@ -1,17 +1,9 @@ -// SupportBot | Emerald Services -// Help Command - const fs = require("fs"); const Discord = 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 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"); @@ -32,11 +24,17 @@ module.exports = new Command({ ); } - // Fetch all commands from the client's commands collection + // Separate regular commands and addon commands let botCommands = ""; + let addonCommands = ""; + interaction.client.commands.forEach((command) => { if (command.permissions) { - botCommands += `\`${command.name}\` `; + if (command.addon) { + addonCommands += `\`${command.name}\` `; + } else { + botCommands += `\`${command.name}\` `; + } } }); @@ -51,15 +49,6 @@ module.exports = new Command({ inline: false, }); - // Conditionally add Addons section - if (supportbot.General.Addons.Enabled) { - HelpEmbed1.addFields({ - name: "🛠️ Addons\n", - value: "No addons available.", - inline: false, - }); - } - HelpEmbed1.setFooter({ text: supportbot.Embed.Footer, iconURL: interaction.user.displayAvatarURL(), diff --git a/Commands/profile.js b/Commands/profile.js new file mode 100644 index 0000000..f54ec86 --- /dev/null +++ b/Commands/profile.js @@ -0,0 +1,323 @@ +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(); + +async function getRole(roleName, guild) { + return guild.roles.cache.find(role => role.name === roleName); +} + +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 }); // Acknowledge the interaction immediately + + 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 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; diff --git a/Commands/removeUser.js b/Commands/removeUser.js new file mode 100644 index 0000000..c2fe64c --- /dev/null +++ b/Commands/removeUser.js @@ -0,0 +1,125 @@ +// File path: Commands/RemoveUser.js + +const fs = require("fs"); +const { + EmbedBuilder, + ApplicationCommandOptionType, + ApplicationCommandType, + 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 + if (!ticketChannel.isThread()) { + 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 { + await ticketChannel.members.remove(userToRemove.id); + + 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, + }); + } + }, +}); diff --git a/Commands/ticket.js b/Commands/ticket.js index ccb5c0b..04fe798 100644 --- a/Commands/ticket.js +++ b/Commands/ticket.js @@ -8,7 +8,7 @@ const { ButtonBuilder, StringSelectMenuBuilder, StringSelectMenuOptionBuilder, - GuildMemberRoleManager, + ButtonStyle, } = require("discord.js"); const yaml = require("js-yaml"); @@ -20,6 +20,187 @@ const msgconfig = yaml.load(fs.readFileSync("./Configs/messages.yml", "utf8")); const Command = require("../Structures/Command.js"); const TicketNumberID = require("../Structures/TicketID.js"); +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); + } + } + return clockedInUsers; +} + +async function assignTicketToUser(ticketChannel, Staff, Admin, interaction, attempts = 0) { + console.log(`Assigning ticket attempt ${attempts}`); + + try { + const clockedInUsers = await getClockedInUsers(interaction.guild); + console.log(`Clocked in users: ${Array.from(clockedInUsers).join(', ')}`); + + if (clockedInUsers.size === 0 || attempts >= 2) { + console.log("No clocked in users or max attempts reached"); + const claimChannel = await interaction.guild.channels.cache.get(supportbot.Ticket.ClaimTickets.Channel); + if (claimChannel) { + const claimEmbed = new EmbedBuilder() + .setTitle(msgconfig.Ticket.ClaimTickets.ClaimTitle || 'Default Claim Title') + .setDescription(msgconfig.Ticket.ClaimTickets.ClaimMessage || 'Default Claim Message') + .setColor(supportbot.Embed.Colours.General); + + const claimButton = new ButtonBuilder() + .setCustomId(`claimticket-${ticketChannel.id}`) + .setLabel(supportbot.Ticket.ClaimTickets.ButtonTitle || 'Claim Ticket') + .setStyle(ButtonStyle.Primary); + + const row = new ActionRowBuilder().addComponents(claimButton); + + const claimMessage = await claimChannel.send({ + content: `<@&${Staff.id}> <@&${Admin.id}>`, + embeds: [claimEmbed], + components: [row], + }); + + 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 => { + console.log(`Ticket claimed by user ${i.user.id}`); + await ticketChannel.send({ content: `<@${i.user.id}> has claimed the ticket.` }); + }); + + collector.on('end', async collected => { + if (collected.size === 0) { + console.log("No one claimed the ticket, notifying admins"); + await claimMessage.delete(); + + 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)], + }); + } + }); + + return null; + } else { + console.error("Claim channel not found"); + return null; + } + } + + const clockedInArray = Array.from(clockedInUsers); + const assignedUserId = clockedInArray[Math.floor(Math.random() * clockedInArray.length)]; + const assignedUser = await interaction.guild.members.fetch(assignedUserId); + console.log(`Assigned user: ${assignedUserId}`); + + const claimEmbed = new EmbedBuilder() + .setTitle(msgconfig.Ticket.ClaimTickets.ClaimTitle || 'Default Claim Title') + .setDescription(msgconfig.Ticket.ClaimTickets.ClaimMessage.replace('%user%', interaction.user.id) || 'Default Claim Message') + .setColor(supportbot.Embed.Colours.General); + + const claimButton = new ButtonBuilder() + .setCustomId(`claimticket-${ticketChannel.id}`) + .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); + if (!claimChannel) { + console.error("Claim channel not found"); + return null; + } + + const claimMessage = await claimChannel.send({ + content: `${assignedUser}`, + embeds: [claimEmbed], + components: [row], + }); + + const filter = i => i.customId === `claimticket-${ticketChannel.id}` && i.user.id === assignedUserId; + const collector = claimMessage.createMessageComponentCollector({ filter, time: 120000 }); + + collector.on('collect', async i => { + console.log(`Ticket claimed by user ${i.user.id}`); + await ticketChannel.send({ content: `<@${assignedUserId}>` }); + }); + + collector.on('end', async collected => { + if (collected.size === 0) { + console.log("No one claimed the ticket, attempting reassignment"); + 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)], + }); + + // Add a claim button for any staff member to click + const claimEmbed = new EmbedBuilder() + .setTitle(msgconfig.Ticket.ClaimTickets.ClaimTitle) + .setDescription(msgconfig.Ticket.ClaimTickets.ClaimMessage) + .setColor(supportbot.Embed.Colours.General); + + const claimButton = new ButtonBuilder() + .setCustomId(`claimticket-${ticketChannel.id}`) + .setLabel(supportbot.Ticket.ClaimTickets.ButtonTitle) + .setEmoji(supportbot.Ticket.ClaimTickets.ButtonEmoji) + .setStyle(ButtonStyle.Primary); + + const row = new ActionRowBuilder().addComponents(claimButton); + + const claimMessage = await claimChannel.send({ + content: `<@&${Staff.id}> <@&${Admin.id}>`, + embeds: [claimEmbed], + components: [row], + }); + + 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 => { + console.log(`Ticket ${ticketChannel.id} claimed by user ${i.user.id}`); + await ticketChannel.member.add(i.user.id); + }); + + collector.on('end', async collected => { + if (collected.size === 0) { + console.log(`No one claimed the ticket (${ticketChannel.id}), notifying all staff`); + await claimMessage.delete(); + + await claimChannel.send({ + content: `No one claimed the ticket. Tagging roles: <@&${Staff.id}> <@&${Admin.id}>`, + }); + } + }); + } else { + const ReassigningEmbed = new EmbedBuilder() + .setTitle(msgconfig.Ticket.ClaimTickets.ReassigningTitle) + .setDescription(msgconfig.Ticket.ClaimTickets.ReassigningMessage.replace('%user%', assignedUserId)) + .setColor(supportbot.Embed.Colours.General); + + await claimChannel.send({ embeds: [ReassigningEmbed], components: [] }); + await assignTicketToUser(ticketChannel, Staff, Admin, interaction, attempts + 1); + } + } + }); + + return assignedUserId; + } catch (error) { + console.error("Error in assignTicketToUser function:", error); + if (error instanceof CombinedError) { + console.error("CombinedError details:", error.errors); + } + return null; + } +} + module.exports = new Command({ name: cmdconfig.OpenTicket.Command, description: cmdconfig.OpenTicket.Description, @@ -34,248 +215,227 @@ module.exports = new Command({ permissions: cmdconfig.OpenTicket.Permission, async run(interaction) { - let department = interaction.customId?.split("-")[1] || null; - let TicketData = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8")); - const { getRole, getChannel } = interaction.client; - let User = interaction.guild.members.cache.get(interaction.user.id); - - if ( - supportbot.Ticket.TicketsPerUser && - TicketData.tickets.filter((t) => t.user === interaction.user.id && t.open).length >= supportbot.Ticket.TicketsPerUser - ) { - return interaction.reply({ - embeds: [ - { - title: "Too Many Tickets!", - description: `You can't have more than ${supportbot.Ticket.TicketsPerUser} open tickets!`, - color: supportbot.Embed.Colours.Warn, - }, - ], - ephemeral: true, - }); - } - - if (User.roles.cache.has(supportbot.Roles.ModRoles.Blacklisted)) { - return interaction.reply({ - content: msgconfig.Ticket.Blacklisted, - ephemeral: true, - }); - } - - if (User.roles.cache.has(supportbot.Roles.ModRoles.Muted)) { - return interaction.reply({ - content: msgconfig.Ticket.Muted, - ephemeral: true, - }); - } - - let ticketNumberID = await TicketNumberID.pad(); - const TicketSubject = interaction.options?.getString("reason") || msgconfig.Ticket.InvalidSubject; - - const TicketExists = new EmbedBuilder() - .setTitle("Ticket Exists!") - .setDescription(msgconfig.Ticket.TicketExists); - - if ( - await interaction.guild.channels.cache.find( - (ticketChannel) => ticketChannel.name === `${supportbot.Ticket.Channel}${ticketNumberID}` - ) - ) { - return await interaction.reply({ - embeds: [TicketExists], - ephemeral: true, - }); - } - - 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.reply({ - content: "Some roles seem to be missing!\nPlease check for errors when starting the bot.", - ephemeral: true, - }); - - const TicketCategory = interaction.guild.channels.cache.find( - (c) => c.name === supportbot.Ticket.TicketCategory || c.id === supportbot.Ticket.TicketCategory - ); - - const channel = await getChannel(supportbot.Ticket.TicketCategory, interaction.guild); - const ticketChannel = await channel.threads.create({ - name: `${supportbot.Ticket.Channel}${ticketNumberID}`, - type: ChannelType.PrivateThread, - parent: TicketCategory, - autoArchiveDuration: 60, - reason: 'support ticket', - }); - - if (supportbot.Ticket.ClaimTickets.Enabled === false) { - const InvStaff = await ticketChannel.send(`<@&${Staff.id}> <@&${Admin.id}>`) - - setTimeout(() => { - InvStaff.delete(); - }, 2000) - } - - // TICKET CLAIMING SYSTEM [START] - - if (supportbot.Ticket.ClaimTickets.Enabled) { - const InvStaff1 = await ticketChannel.send(`<@&${Admin.id}>`) - - setTimeout(() => { - InvStaff1.delete(); - }, 2000) - - const claimChannel = await getChannel( - supportbot.Ticket.ClaimTickets.Channel, - interaction.guild - ); - - const claimEmbed = new EmbedBuilder() - .setTitle(msgconfig.Ticket.ClaimTickets.ClaimTitle) - .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) - .setStyle(supportbot.Ticket.ClaimTickets.Button); - - const row = new ActionRowBuilder().addComponents(claimButton); - - await claimChannel.send({ - content: `<@&${Staff.id}>`, - embeds: [claimEmbed], - components: [row] - }); - - } - - // TICKET CLAIMING SYSTEM [END] - - if (!department) { - await ticketChannel.members.add(interaction.user.id); - } - - TicketData.tickets.push({ - id: ticketChannel.id, - name: ticketChannel.name, - user: interaction.user.id, - number: ticketNumberID, - reason: TicketSubject, - open: true, - subUsers: [], - createdAt: new Date().toISOString(), - claimedAt: null, // Will be updated when claimed - }); - - fs.writeFileSync( - "./Data/TicketData.json", - JSON.stringify(TicketData, null, 4), - (err) => { - if (err) console.error(err); + try { + let TicketReason = null; + if (supportbot.Ticket.TicketReason) { + TicketReason = interaction.reason || null; // Retrieve the reason from the interaction object } - ); - const CreatedTicket = new EmbedBuilder() - .setDescription( - msgconfig.Ticket.TicketCreatedAlert.replace(/%ticketauthor%/g, interaction.user.id) - .replace(/%ticketid%/g, ticketChannel.id) - .replace(/%ticketusername%/g, interaction.user.username) - ) - .setColor(supportbot.Embed.Colours.General); - await interaction.reply({ embeds: [CreatedTicket], ephemeral: true }); + let TicketData = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8")); + const { getRole, getChannel } = interaction.client; + let User = interaction.guild.members.cache.get(interaction.user.id); - if (supportbot.Ticket.AllowMentions) { - await ticketChannel.send(`${interaction.user}`); - } + if ( + supportbot.Ticket.TicketsPerUser && + TicketData.tickets.filter((t) => t.user === interaction.user.id && t.open).length >= supportbot.Ticket.TicketsPerUser + ) { + return interaction.reply({ + embeds: [ + { + title: "Too Many Tickets!", + description: `You can't have more than ${supportbot.Ticket.TicketsPerUser} open tickets!`, + color: supportbot.Embed.Colours.Warn, + }, + ], + ephemeral: true, + }); + } - const TicketMessage = new EmbedBuilder() - .setAuthor({ - name: msgconfig.Ticket.TicketAuthorTitle.replace(/%ticketauthor%/g, interaction.user.id) - .replace(/%ticketid%/g, ticketChannel.id) - .replace(/%ticketusername%/g, interaction.user.username), - iconURL: interaction.user.displayAvatarURL(), - }) - .setTitle( - msgconfig.Ticket.TicketTitle.replace(/%ticketauthor%/g, interaction.user.id) - .replace(/%ticketid%/g, ticketChannel.id) - .replace(/%ticketusername%/g, interaction.user.username) - ) - .setDescription( - msgconfig.Ticket.TicketMessage.replace(/%ticketauthor%/g, interaction.user.id) - .replace(/%ticketid%/g, ticketChannel.id) - .replace(/%ticketusername%/g, interaction.user.username) - ) - .setColor(supportbot.Embed.Colours.General); + if (User.roles.cache.has(supportbot.Roles.ModRoles.Blacklisted)) { + return interaction.reply({ + content: msgconfig.Ticket.Blacklisted, + ephemeral: true, + }); + } - const SelectMenus = new StringSelectMenuBuilder() - .setCustomId("ticketcontrolpanel") - .setPlaceholder("Ticket Control Panel") - .addOptions( - new StringSelectMenuOptionBuilder() - .setLabel("Add User") - .setDescription("Add a user to the ticket.") - .setEmoji(supportbot.SelectMenus.Tickets.AddUserEmoji) - .setValue("ticketadduser"), - new StringSelectMenuOptionBuilder() - .setLabel("Remove User") - .setDescription("Remove a user from the ticket.") - .setEmoji(supportbot.SelectMenus.Tickets.RemoveUserEmoji) - .setValue("ticketremoveuser"), - new StringSelectMenuOptionBuilder() - .setLabel("Close") - .setDescription("Close the ticket.") - .setEmoji(supportbot.SelectMenus.Tickets.CloseEmoji) - .setValue("ticketclose"), - new StringSelectMenuOptionBuilder() - .setLabel("Enable Invites") - .setDescription("Enable invites.") - .setEmoji(supportbot.SelectMenus.Tickets.EnableInvitesEmoji) - .setValue("enableinvites"), - new StringSelectMenuOptionBuilder() - .setLabel("Disable Invites") - .setDescription("Disable invites.") - .setEmoji(supportbot.SelectMenus.Tickets.DisableInvitesEmoji) - .setValue("disableinvites"), - ); + if (User.roles.cache.has(supportbot.Roles.ModRoles.Muted)) { + return interaction.reply({ + content: msgconfig.Ticket.Muted, + ephemeral: true, + }); + } - const row2 = new ActionRowBuilder().addComponents(SelectMenus); + let ticketNumberID = await TicketNumberID.pad(); + const TicketSubject = TicketReason || msgconfig.Ticket.InvalidSubject; - if (!department) { - try { - let buttons = supportbot.Departments.map((x) => - new ButtonBuilder() - .setCustomId("Department" + supportbot.Departments.indexOf(x)) - .setLabel(x.title) - .setStyle(x.color) - .setEmoji(x.emoji) - ); + const TicketExists = new EmbedBuilder() + .setTitle("Ticket Exists!") + .setDescription(msgconfig.Ticket.TicketExists); - if (supportbot.Ticket.AllowMentions) { - await ticketChannel.send("<@&" + supportbot.Ticket.RoleMention + ">"); - } + if ( + await interaction.guild.channels.cache.find( + (ticketChannel) => ticketChannel.name === `${supportbot.Ticket.Channel}${ticketNumberID}` + ) + ) { + return await interaction.reply({ + embeds: [TicketExists], + ephemeral: true, + }); + } - const row = new ActionRowBuilder().addComponents(buttons); - const m = await ticketChannel.send({ - embeds: [TicketMessage], - components: [row2], + 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.reply({ + content: "Some roles seem to be missing!\nPlease check for errors when starting the bot.", + ephemeral: true, }); + const TicketCategory = interaction.guild.channels.cache.find( + (c) => c.name === supportbot.Ticket.TicketCategory || c.id === supportbot.Ticket.TicketCategory + ); + + const channel = await getChannel(supportbot.Ticket.TicketCategory, interaction.guild); + const ticketChannel = await channel.threads.create({ + name: `${supportbot.Ticket.Channel}${ticketNumberID}`, + type: ChannelType.PrivateThread, + parent: TicketCategory, + autoArchiveDuration: 60, + reason: 'support ticket', + }); + + // 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) { + const claimChannel = await interaction.guild.channels.cache.get(supportbot.Ticket.ClaimTickets.Channel); + await claimChannel.send(`<@&${Staff.id}> <@&${Admin.id}>`); + } + } + + // Add user to the ticket channel + await ticketChannel.members.add(interaction.user.id); + + // Save ticket data + TicketData.tickets.push({ + id: ticketChannel.id, + name: ticketChannel.name, + user: interaction.user.id, + number: ticketNumberID, + reason: TicketSubject, + open: true, + subUsers: [], + createdAt: new Date().toISOString(), + claimedAt: null, + }); + + 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) + .replace(/%ticketid%/g, ticketChannel.id) + .replace(/%ticketusername%/g, interaction.user.username) + .replace(/%ticketreason%/g, TicketSubject) + ) + .setColor(supportbot.Embed.Colours.General); + await interaction.reply({ embeds: [CreatedTicket], ephemeral: true }); + + // Create the ticket message + const TicketMessage = new EmbedBuilder() + .setAuthor({ + name: msgconfig.Ticket.TicketAuthorTitle.replace(/%ticketauthor%/g, interaction.user.id) + .replace(/%ticketid%/g, ticketChannel.id) + .replace(/%ticketusername%/g, interaction.user.username), + iconURL: interaction.user.displayAvatarURL(), + }) + .setTitle( + msgconfig.Ticket.TicketTitle.replace(/%ticketauthor%/g, interaction.user.id) + .replace(/%ticketid%/g, ticketChannel.id) + .replace(/%ticketusername%/g, interaction.user.username) + ) + .setDescription( + msgconfig.Ticket.TicketMessage.replace(/%ticketauthor%/g, interaction.user.id) + .replace(/%ticketid%/g, ticketChannel.id) + .replace(/%ticketusername%/g, interaction.user.username) + .replace(/%ticketreason%/g, TicketSubject) + ) + .setColor(supportbot.Embed.Colours.General); + + // Add reason field if ticket reason is enabled + if (supportbot.Ticket.TicketReason && TicketReason) { + TicketMessage.addFields( + { name: "Reason", value: TicketReason, inline: false } + ); + } + + // Create the control panel + const SelectMenus = new StringSelectMenuBuilder() + .setCustomId("ticketcontrolpanel") + .setPlaceholder("Ticket Control Panel") + .addOptions( + new StringSelectMenuOptionBuilder() + .setLabel("Close") + .setDescription("Close the ticket.") + .setEmoji(supportbot.SelectMenus.Tickets.CloseEmoji) + .setValue("ticketclose"), + new StringSelectMenuOptionBuilder() + .setLabel("Archive") + .setDescription("Acrhive the ticket.") + .setEmoji(supportbot.SelectMenus.Tickets.ArchiveEmoji) + .setValue("archiveticket"), + new StringSelectMenuOptionBuilder() + .setLabel("Lock Ticket") + .setDescription("Lock the ticket.") + .setEmoji(supportbot.SelectMenus.Tickets.LockEmoji) + .setValue("lockticket"), + new StringSelectMenuOptionBuilder() + .setLabel("Enable Invites") + .setDescription("Enable invites.") + .setEmoji(supportbot.SelectMenus.Tickets.EnableInvitesEmoji) + .setValue("enableinvites"), + new StringSelectMenuOptionBuilder() + .setLabel("Disable Invites") + .setDescription("Disable invites.") + .setEmoji(supportbot.SelectMenus.Tickets.DisableInvitesEmoji) + .setValue("disableinvites"), + ); + + const row2 = new ActionRowBuilder().addComponents(SelectMenus); + + // Send the ticket message and control panel + await ticketChannel.send({ + embeds: [TicketMessage], + components: [row2], + }); + + // Handle department selection if not specified + if (!department) { try { + const buttons = supportbot.Departments.map((x, index) => + new ButtonBuilder() + .setCustomId("Department" + index) + .setLabel(x.title) + .setStyle(x.color) + .setEmoji(x.emoji) + ); + + const row = new ActionRowBuilder().addComponents(buttons); + + const message = await ticketChannel.send({ + content: "Please select a department:", + components: [row], + }); + const filter = (i) => i.user.id === interaction.user.id; - await m.awaitMessageComponent({ + await message.awaitMessageComponent({ filter, max: 1, componentType: "BUTTON", time: supportbot.Ticket.Timeout * 60000, }); + } catch (e) { if (e.code === "COLLECTOR_ERROR") { try { - let ticket = TicketData.tickets.findIndex((t) => t.id === ticketChannel.id); + const ticket = TicketData.tickets.findIndex((t) => t.id === ticketChannel.id); TicketData.tickets[ticket].open = false; fs.writeFileSync( "./Data/TicketData.json", @@ -284,25 +444,18 @@ module.exports = new Command({ if (err) console.error(err); } ); - interaction.user.send( - "Your ticket has timed out. Please open a new one, and select a department." + await interaction.user.send( + "Your ticket has timed out. Please open a new one and select a department." ); } catch (error) { console.error("An error occurred while handling collector:", error); } - return await ticketChannel.delete(); + await ticketChannel.delete(); } } - } catch (error) { - console.error("Error sending message:", error); } - } else { - await ticketChannel.send({ - embeds: [TicketMessage], - components: [row2], - }); - + } catch (error) { + console.error("Error in run method:", error); } - }, }); diff --git a/Commands/ticketstats.js b/Commands/ticketstats.js deleted file mode 100644 index 1afea81..0000000 --- a/Commands/ticketstats.js +++ /dev/null @@ -1,56 +0,0 @@ -const { EmbedBuilder, ApplicationCommandType } = 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"); - -module.exports = new Command({ - name: cmdconfig.TicketStats.Command, - description: cmdconfig.TicketStats.Description, - type: ApplicationCommandType.ChatInput, - permissions: ["SendMessages"], // Adjust permissions as needed - - async run(interaction) { - - if (supportbot.Ticket.ClaimTickets.Enabled) { - - const TicketData = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8")); - const userId = interaction.user.id; - - const ticketsClaimed = TicketData.tickets.filter(t => t.claimedBy === userId); - 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); // Convert to minutes - - const statsEmbed = new EmbedBuilder() - .setTitle(msgconfig.TicketStats.Title) - .setDescription(`> Ticket stats for <@${interaction.user.id}>`) - .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 } - ); - - await interaction.reply({ embeds: [statsEmbed], ephemeral: true }); - } - - } -}); diff --git a/Commands/userinfo.js b/Commands/userinfo.js deleted file mode 100644 index ef06e7e..0000000 --- a/Commands/userinfo.js +++ /dev/null @@ -1,137 +0,0 @@ -// SupportBot | Emerald Services -// User Info Command - -const fs = require("fs"); -const moment = require("moment"); - -const Discord = 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"); - -module.exports = new Command({ - name: cmdconfig.UserInfo.Command, - description: cmdconfig.UserInfo.Description, - type: Discord.ApplicationCommandType.ChatInput, - options: [ - { - type: Discord.ApplicationCommandOptionType.User, - name: "user", - description: "The user you want to get info about.", - required: true, - }, // The user input - ], - permissions: cmdconfig.UserInfo.Permission, - - async run(interaction) { - let disableCommand = true; - - 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 Discord.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] }); - - let user = await interaction.options.getUser("user") || interaction.author; // Grab the mentioned user - let member = await interaction.guild.members.cache.get(user.id) // Check for user and his ID - - const UserNotExist = new Discord.EmbedBuilder() - .setTitle("User Not Found!") - .setDescription( - `${msgconfig.Error.UserNotFound}\n\nTry Again:\`/${cmdconfig.UserInfoCommand} <@!User_ID> or @User\`` - ) - .setColor(supportbot.ErrorColour); - - if (!user) return interaction.reply({ embeds: [UserNotExist] }); - - // Flags a user/bot can have - const flags = { - DISCORD_EMPLOYEE: 'Discord Employee', - DISCORD_PARTNER: 'Discord Partner', - BUGHUNTER_LEVEL_1: 'Bug Hunter (Level 1)', - BUGHUNTER_LEVEL_2: 'Bug Hunter (Level 2)', - HYPESQUAD_EVENTS: 'HypeSquad Events', - HOUSE_BRAVERY: 'House of Bravery', - HOUSE_BRILLIANCE: 'House of Brilliance', - HOUSE_BALANCE: 'House of Balance', - EARLY_SUPPORTER: 'Early Supporter', - TEAM_USER: 'Team User', - SYSTEM: 'System', - VERIFIED_BOT: 'Verified Bot', - VERIFIED_DEVELOPER: 'Verified Bot Developer' - }; - - // Status message with icon - let status = { - online: "🟢Online", - idle: "🟡Idle", - dnd: "🔴Do Not Disturb", - offline: "⚫Offline" - }; - - const userFlags = user.flags.toArray(); - - // Embed containing the userinfo - let userembed = new Discord.EmbedBuilder() - .setTitle(`User Info for User: ${user.tag}`) - .setThumbnail(member.displayAvatarURL({ dynamic: true, size: 512 })) - .setColor(member.roles.highest.hexColor) - .addFields( - { name: "Name:", value: `${user.tag}\n<@${member.id}>`, inline: true }, - { name: "ID:", value: `${member.id}`, inline: true }, - { name: "Is Bot:", value: user.bot ? "Yes" : "No", inline: true }, - { name: "House/Badge:", value: `${userFlags.length ? userFlags.map(flag => flags[flag]).join("\n") : "None"}`, inline: true }, - { name: "Current Status:", value: `${status[member.presence ? member.presence.status : "offline"]}`, inline: true }, - { name: "Avatar URL:", value: `[Click Here](${member.displayAvatarURL()})`, inline: true }, - { name: "\u200b", value: "\u200b", inline: false} - ) - .addFields( - { name: "Creation Date:", value: `${moment(user.createdAt).format("MMMM Do YYYY, h:mm:ss a")}\n> ${moment(user.createdAt).startOf("day").fromNow()}`, inline: true }, - { name: "Joined Date:", value: `${moment(member.joinedAt).format("MMMM Do YYYY, h:mm:ss a")}\n> ${moment(member.joinedAt).startOf("day").fromNow()}`, inline: true }, - { name: "Roles Total:", value: `${member.roles.cache.size -1}`, inline: false }, - { name: "Highest Role:", value: `${member.roles.highest.id === interaction.guild.id ? "None" : member.roles.highest.toString()}`, inline: true }, - { name: "Roles:", value: `${member.roles.cache.map(r => r).join(" ").replace("@everyone", " ") || "None"}`, inline: false }, - ) - .setFooter({ text: supportbot.Footer }) - .setTimestamp(); - - // Send embed [userembed] - await interaction.reply({ embeds: [userembed] }).catch((err) => console.error("An error has occured: ", err)); - - }, - }) - -/** - * @INFO - * UserInfo command by Sypher#3415 | 321332200668790786 - * @INFO - * Solely created for SupportBot | https://github.com/Emerald-Services/SupportBot/ - **/ diff --git a/Configs/commands.yml b/Configs/commands.yml index c8aaa6b..3ab19d3 100644 --- a/Configs/commands.yml +++ b/Configs/commands.yml @@ -19,12 +19,6 @@ Help: Permission: ["SendMessages"] Enabled: true -EnableAI: - Command: "enableai" - Description: "Talk with SupportBot" - Permission: ["SendMessages"] - Enabled: true - Info: Command: "info" Description: "Get information about the **ServerName**." @@ -37,9 +31,9 @@ TicketPanel: Permission: ["Administrator"] Enabled: true -TicketStats: - Command: "ticketstats" - Description: "Shows staff members their ticket statistics" +Profile: + Command: "profile" + Description: "Shows the profile for a discord member." Permission: ["SendMessages"] # Command is automatically disabled when the TicketClaims is disabled. @@ -49,9 +43,21 @@ OpenTicket: Permission: ["SendMessages"] Enabled: true -Add: +AddUser: Command: "add" - Description: "Add a user to the ticket." + Description: "Add a user to a support ticket." + Permission: ["SendMessages"] + Enabled: true + +ForceAddUser: + Command: "forceadd" + Description: "Add a user to a support ticket by bypassing the invite system." + Permission: ["SendMessages"] + Enabled: true + +RemoveUser: + Command: "remove" + Description: "Remove a user from a support ticket." Permission: ["SendMessages"] Enabled: true diff --git a/Configs/messages.yml b/Configs/messages.yml index ce83f5a..27f6804 100644 --- a/Configs/messages.yml +++ b/Configs/messages.yml @@ -24,7 +24,7 @@ Welcome: Embed: Colour: "#f74343" Title: "Welcome to ServerName" - Message: "> :smile: %joined_user% has just landed!" + Message: ":smile: %joined_user% has just landed!" Thumbnail: "BOT" ImageEnabled: false @@ -34,7 +34,7 @@ Leave: Embed: Colour: "#f74343" Title: "Goodbye!" - Message: "> :slight_frown: %joined_user% has recently dropped out!" + Message: ":slight_frown: %joined_user% has recently dropped out!" Thumbnail: "BOT" ImageEnabled: false @@ -42,8 +42,8 @@ Leave: Suggestions: Sent_Title: "Suggestion Sent!" - Sent: "> :white_check_mark: **You have successfully sent a suggestion.**" - NoSelfVote: "> :x: **You cannot vote on your own suggestion.**" + Sent: ":white_check_mark: **You have successfully sent a suggestion.**" + NoSelfVote: ":x: **You cannot vote on your own suggestion.**" RemoveVoteButton: "Remove Vote" Ticket: @@ -56,47 +56,47 @@ Ticket: TicketAuthorTitle: "%ticketusername%'s Ticket" TicketTitle: "Support Ticket" - TicketMessage: "> Hello <@%ticketauthor%>, Thank you for creating a support ticket\nPlease wait patiently whilst a member of our team reaches out to you." + TicketMessage: "Hello <@%ticketauthor%>, Thank you for creating a support ticket\nPlease wait patiently whilst a member of our team reaches out to you." TicketThumbnail: "https://images.emojiterra.com/google/android-pie/512px/1f39f.png" - TicketCreatedAlert: "> :ticket: Ticked Created: <#%ticketid%>" - TicketExists: "> ❌ Ticket already exists" - TicketClaim: "> :white_check_mark: Ticket claimed by" #The user who claimed the ticket will be added to the end of this message - TicketUnclaim: "> :white_check_mark: Ticket unclaimed by" #The user who claimed the ticket will be added to the end of this message + TicketCreatedAlert: ":ticket: Ticked Created: <#%ticketid%>" + TicketExists: "❌ Ticket already exists" + TicketClaim: ":white_check_mark: Ticket claimed by" #The user who claimed the ticket will be added to the end of this message + TicketUnclaim: ":white_check_mark: Ticket unclaimed by" #The user who claimed the ticket will be added to the end of this message - DisableInvites: "> 🔒 Invites have been disabled for this ticket." - EnableInvites: "> 🔓 Invites have been enabled for this ticket." + TicketArchived: ":white_check_mark: Ticket archived." + TicketUnarchived: ":white_check_mark: Ticket unarchived." + TicketLocked: ":lock: Ticket is locked." + DisableInvites: "🔒 Invites have been disabled for this ticket." + EnableInvites: "🔓 Invites have been enabled for this ticket." # Available Placeholders: # <@%user%> - Tags the user who was added to the ticket. - AddUserToTicket_Title: "➕ Add User!" - AddUserToTicket: "> To add a user to this ticket, Tag them." - - RemovedUserFromTicket_Title: "➖ Removed User!" - RemovedUserFromTicket: "> <@%user%> has been `Removed` from this ticket" - - Blacklisted: "> :x: You are not allowed to open a ticket" - Muted: "> :x: You are `Muted` and therefore not allowed to open any tickets" - InvalidSubject: "> No Reason Provided!" + Blacklisted: ":x: You are not allowed to open a ticket" + Muted: ":x: You are `Muted` and therefore not allowed to open any tickets" + InvalidSubject: "No Reason Provided!" TranscriptSavedMessage: "📝 **Transcript Saved!**" TicketLockMessage: "❌ **Ticket Locked!**" TicketDeleteMessage: "⚠ **Ticket Deleting!**" - ClosingTicket: "> :warning: You have requested to close your support ticket." - ConfirmClose: "> Please confirm closing the ticket by clicking the button below." - CloseTimeout: "> Ticket close confirmation timed out." + ClosingTicket: ":warning: You have requested to close your support ticket." + ConfirmClose: "Please confirm closing the ticket by clicking the button below." + CloseTimeout: "Ticket close confirmation timed out." ClaimTickets: ClaimTitle: "Incoming Support Request!" - ClaimMessage: "> A new ticket has been created by <@!%user%>. Click the button below to claim this ticket." - ClaimMessage_Edit: "> <#%channel%> was claimed by <@!%user%>." + ClaimMessage: "A new ticket has been created by <@!%user%>. Click the button below to claim this ticket." + ClaimMessage_Edit: "<#%channel%> was claimed by <@!%user%>." ClaimedTitle: "Ticket Claimed!" - Claimed: "> You have successfully claimed this ticket. <#%channel%>" + Claimed: "You have successfully claimed this ticket. <#%channel%>" NoPermsToClaimTitle: "Error, no permissions!" - NoPermsToClaim: "> :x: **Err!** You do not permissions to claim this ticket." + NoPermsToClaim: ":x: **Err!** You do not permissions to claim this ticket." + + ReassigningTitle: "Reassigning!..." + ReassigningMessage: "<@%user%> did not claim the ticket. **Reassigning...**" TicketStats: OpenTickets: "✅ Open Tickets" @@ -105,12 +105,72 @@ TicketStats: Title: "Your Ticket Stats" +AddUser: + NotInTicket_Title: "Ticket Invitation" + NotInTicket_Description: "This command can only be used within a ticket thread." + + Invite_Title: "User Invited" + Invite_Description: "%username% has been invited to join the ticket." + + DM_Title: "Ticket Invitation" + DM_Description: "You have been invited to join the ticket %channelname%. Do you accept?" + + Accept_Label: "Accept" + Reject_Label: "Reject" + ViewTicket_Label: "Go to Ticket" + + Accepted_Title: "Invitation Accepted" + Accepted_Message: "You have been added to the ticket." + AddedToTicket_Title: "Added to Ticket" + AddedToTicket_Description: "You have been added to the ticket %channel_link%." + + Declined_Title: "Invitation Declined" + Declined_Message: "You have declined the invitation." + + NoResponse_Title: "No Response" + NoResponse_Message: "You did not respond in time. Please contact staff if you wish to join the ticket." + + Error_Title: "Error" + Error_Adding: "There was an error adding you to the ticket. Please try again." + + DM_Error_Title: "Error" + DM_Error_Description: "Unable to send a DM to %username%. Please ensure your DMs are enabled." + + Unexpected_Error_Title: "Error" + Unexpected_Error_Description: "An unexpected error occurred: %error%" + +RemoveUser: + NotInTicket_Title: "Error" + NotInTicket_Description: "This command can only be used in ticket threads." + + Removed_Title: "User Removed" + Removed_Message: "The user has been successfully removed from the ticket." + + RemovedFromTicket_Title: "Removed from Ticket" + RemovedFromTicket_Description: "You have been removed from the ticket channel %channel_link%." + + Error_Title: "Error" + Error_Removing: "There was an error removing the user from the ticket." + +ForceAddUser: + NotInTicket_Title: "Not a Ticket" + NotInTicket_Description: "This command can only be used within a ticket thread." + + Added_Title: "User Added" + Added_Description: "Successfully added %username% to the ticket." + + AddedToTicket_Title: "You have been added to a Ticket" + AddedToTicket_Description: "You have been added to the ticket channel [%channel_link%]." + + Error_Title: "Error" + Error_Adding: "There was an error adding the user to the ticket." + Error: - IncorrectPerms: "> :x: **Err!** You do not have the correct permissions to use this." - InvalidChannel: "> :x: **Err!** Channel not found. Please create it!" - UserNotFound: "> :x: **Err!** This user doesn't exist, Are they in this server?" - NoValidTicket: "> :warning: You cannot execute this command here." - TicketExists: "> :warning: This ticket cannot be opened, It already exists!" + IncorrectPerms: ":x: **Err!** You do not have the correct permissions to use this." + InvalidChannel: ":x: **Err!** Channel not found. Please create it!" + UserNotFound: ":x: **Err!** This user doesn't exist, Are they in this server?" + NoValidTicket: ":warning: You cannot execute this command here." + TicketExists: ":warning: This ticket cannot be opened, It already exists!" diff --git a/Configs/supportbot-ai.yml b/Configs/supportbot-ai.yml index e3ce7d6..e68c4ad 100644 --- a/Configs/supportbot-ai.yml +++ b/Configs/supportbot-ai.yml @@ -16,17 +16,16 @@ General: Tokens: 400 # Max tokens you can set is 400 # For more information about tokens, go to https://openai.com/api/pricing/ - - OpenAI_Key: "OPEN_AI_API_KEY" + OpenAI_Key: "OPEN_AI_KEY_HERE" # To create an OpenAI API key, go to https://platform.openai.com/api-keys - PastebinAPI_Key: "PASTEBIN_API_KEY" + PastebinAPI_Key: "PASTEBIN_API_KEY_HERE" # You can get your Pastebin API Key from https://pastebin.com PastebinAPI_URL: "https://pastebin.com/api/api_post.php" Channels: - AIChannel: "0000000000000000000" + AIChannel: "1250190338954498048" # This is the channel where AI will respond in without needing to mention the bot. Embed: diff --git a/Configs/supportbot.yml b/Configs/supportbot.yml index 71747da..539fe9c 100644 --- a/Configs/supportbot.yml +++ b/Configs/supportbot.yml @@ -11,7 +11,7 @@ # Official Documentation: https://github.com/Emerald-Services/SupportBot/wiki # Third-Party Documentation: COMING SOON -SupportBot_Version: "v8.0" +SupportBot_Version: "v8.1" # Do not change the version number # ------------------------- @@ -24,7 +24,7 @@ General: # To get your Discord Bot Token, Go to https://discord.com/developers/applications Addons: - Enabled: false + Enabled: true # ------------------------- # Activity @@ -145,17 +145,32 @@ Ticket: StaffOnly: true # Set to false is anyone is able to close a ticket. Confirmation_Button: "Confirm Close" + Confirmation_Emoji: "🗑️" + Confirmation_Style: "2" Title: "Are you sure you want to close this ticket?" + # Available Button Styles + # 1 - Blurple + # 2 - Grey + # 3 - Green + # 4 - Red + Invites: StaffOnly: true + LockedTickets: + Channel: "1267856939275124809" + ClaimTickets: Enabled: true Channel: "1249723473757077624" # Channel name or Channel ID + ClaimAcceptTime: 120000 + # How long the assigned user has to accept the ticket before it gets re-assigned to someone else. + ButtonTitle: "Claim Ticket" + ButtonEmoji: "🎫" Button: "1" # Available Button Styles @@ -184,6 +199,15 @@ Buttons: ClaimEmoji: "🧑‍💻" ClaimStyle: "1" + Unarchive: "Unarchive Ticket" + Unarchive_Emoji: "♻️" + Unarchive_Style: "2" + + Unlock: "Unlock Ticket" + Unlock_Emoji: "🔓" + Unlock_Style: "2" + + # Available Button Styles # 1 - Blurple # 2 - Grey @@ -198,14 +222,13 @@ Buttons: SelectMenus: Tickets: PanelEmoji: "⚙️" + CloseEmoji: "🗑️" ArchiveEmoji: "♻️" - LockTicketEmoji: "🔒" - UnLockTicketEmoji: "🔓" - EnableInvitesEmoji: "🔒" - DisableInvitesEmoji: "🔓" - AddUserEmoji: "➕" - RemoveUserEmoji: "➖" + LockEmoji: "🔐" + + EnableInvitesEmoji: "📤" + DisableInvitesEmoji: "📥" # ------------------------- diff --git a/Data/Profiles.json b/Data/Profiles.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/Data/Profiles.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/Data/StaffData.json b/Data/StaffData.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/Data/StaffData.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/Data/ticket-panel-id.json b/Data/ticket-panel-id.json index b0b4882..ba9c579 100644 --- a/Data/ticket-panel-id.json +++ b/Data/ticket-panel-id.json @@ -1,4 +1,4 @@ { "id": 0, - "TicketPanelID": "0000000000000000000" + "TicketPanelID": "000000000000000000" } \ No newline at end of file diff --git a/Events/interactionCreate.js b/Events/interactionCreate.js index 9f64240..f79b9d6 100644 --- a/Events/interactionCreate.js +++ b/Events/interactionCreate.js @@ -16,7 +16,8 @@ try { } module.exports = new Event("interactionCreate", async (client, interaction) => { - if (interaction.type == Discord.InteractionType.ApplicationCommand) { + + if (interaction.type === Discord.InteractionType.ApplicationCommand) { const command = client.commands.find( (cmd) => cmd.name.toLowerCase() === interaction.commandName ); @@ -24,12 +25,13 @@ module.exports = new Event("interactionCreate", async (client, interaction) => { if (interaction.user.bot || !interaction.guild) return; const NotValid = new Discord.EmbedBuilder() - .setDescription(`:x: \`Invalid Command\` `) + .setDescription(`:x: \`Invalid Command\``) .setColor(supportbot.Embed.Colours.Error); if (!command) return interaction.reply({ embeds: [NotValid], + ephemeral: true, }); const permission = interaction.member.permissions.has(command.permissions); @@ -43,8 +45,50 @@ module.exports = new Event("interactionCreate", async (client, interaction) => { if (!permission) return interaction.reply({ embeds: [ValidPerms], + ephemeral: true, }); - command.run(interaction); + + try { + if (command.name === cmdconfig.OpenTicket.Command && supportbot.Ticket.TicketReason) { + const modal = new Discord.ModalBuilder() + .setCustomId("ticketReasonModal") + .setTitle("Ticket Reason") + .addComponents( + new Discord.ActionRowBuilder().addComponents( + new Discord.TextInputBuilder() + .setCustomId("reasonInput") + .setLabel("Reason") + .setStyle(Discord.TextInputStyle.Paragraph) + .setRequired(true) + ) + ); + + await interaction.showModal(modal); + } else { + console.log("\u001b[32m", "[Executed]", "\u001b[37;1m", `${interaction.user.username} has executed ${command.name}`); + await command.run(interaction); + } + } catch (error) { + console.error("Error executing command:", error); + await interaction.reply({ + content: "An error occurred while executing the command.", + ephemeral: true, + }); + } + } + + if (interaction.isModalSubmit()) { + if (interaction.customId === "ticketReasonModal") { + const reason = interaction.fields.getTextInputValue("reasonInput"); + interaction.customId = `createticket-${reason}`; + const cmd = client.commands.get(cmdconfig.OpenTicket.Command); + if (cmd) { + interaction.reason = reason; // Attach the reason to the interaction object + await cmd.run(interaction); + } else { + console.error("Command not found."); + } + } } if (interaction.isStringSelectMenu()) { @@ -52,31 +96,60 @@ module.exports = new Event("interactionCreate", async (client, interaction) => { const selectedOption = interaction.values[0]; const { getRole, getChannel } = interaction.client; switch (selectedOption) { - case "ticketadduser": - const AddUserThreadTicket = new Discord.EmbedBuilder() - .setTitle(`${msgconfig.Ticket.AddUserToTicket}`) - .setDescription(`${msgconfig.Ticket.AddUserToTicket}`) - .setColor(supportbot.Embed.Colours.General); - - await interaction.reply({ - embeds: [AddUserThreadTicket], - ephemeral: true, - }); - break; - - case "ticketremoveuser": - await removeUserFromThread(interaction); - break; case "ticketclose": const closeCommand = client.commands.get(cmdconfig.CloseTicket.Command); if (closeCommand) { - closeCommand.run(interaction); + await closeCommand.run(interaction); } else { console.error("[X] Close Command Not Found"); } 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], + }); + + await interaction.channel.setArchived(true); + + break; + + case "lockticket": + const lockembed = new Discord.EmbedBuilder() + .setDescription(msgconfig.Ticket.TicketLocked) + .setColor(supportbot.Embed.Colours.Success); + + await interaction.channel.send({ + embeds: [lockembed], + components: [], + ephemeral: true,s + }); + + await interaction.channel.setLocked(true); + + break; + case "enableinvites": await handleInvites(interaction, true); break; @@ -89,32 +162,80 @@ module.exports = new Event("interactionCreate", async (client, interaction) => { } if (interaction.isButton()) { - if (interaction.customId === 'createticket') { - try { - const cmd = client.commands.get(cmdconfig.OpenTicket.Command); - if (!cmd) return; - cmd.run(interaction); - } catch (error) { - console.error("Error executing the ticket command:", error); - await interaction.reply({ - content: "An error occurred while creating the ticket.", - ephemeral: true, - }); + if (interaction.customId === "unarchiveTicket") { + await interaction.channel.setArchived(false); + + const unrcembed = new Discord.EmbedBuilder() + .setDescription(msgconfig.Ticket.TicketUnarchived) + .setColor(supportbot.Embed.Colours.Success); + + await interaction.update({ + embeds: [unrcembed], + components: [], + }); + + } + + if (interaction.customId === "createticket") { + if (supportbot.Ticket.TicketReason) { + const modal = new Discord.ModalBuilder() + .setCustomId("ticketReasonModal") + .setTitle("Ticket Reason") + .addComponents( + new Discord.ActionRowBuilder().addComponents( + new Discord.TextInputBuilder() + .setCustomId("reasonInput") + .setLabel("Reason") + .setStyle(Discord.TextInputStyle.Paragraph) + .setRequired(true) + ) + ); + + await interaction.showModal(modal); + } else { + try { + const cmd = client.commands.get(cmdconfig.OpenTicket.Command); + if (!cmd) return; + await cmd.run(interaction); + } catch (error) { + console.error("Error executing the ticket command:", error); + await interaction.reply({ + content: "An error occurred while creating the ticket.", + ephemeral: true, + }); + } } } if (interaction.customId === "openticket") { - try { - const cmd = client.commands.get(cmdconfig.OpenTicket); - if (!cmd) return; - cmd.run(interaction); - } catch (error) { - console.error(error); + if (supportbot.Ticket.TicketReason) { + const modal = new Discord.ModalBuilder() + .setCustomId("ticketReasonModal") + .setTitle("Ticket Reason") + .addComponents( + new Discord.ActionRowBuilder().addComponents( + new Discord.TextInputBuilder() + .setCustomId("reasonInput") + .setLabel("Reason") + .setStyle(Discord.TextInputStyle.Paragraph) + .setRequired(true) + ) + ); + + await interaction.showModal(modal); + } else { + try { + const cmd = client.commands.get(cmdconfig.OpenTicket.Command); + if (!cmd) return; + await cmd.run(interaction); + } catch (error) { + console.error(error); + } } } - if (interaction.customId.startsWith('claimticket-')) { - const ticketChannelId = interaction.customId.split('-')[1]; + if (interaction.customId.startsWith("claimticket-")) { + const ticketChannelId = interaction.customId.split("-")[1]; const ticketChannel = interaction.guild.channels.cache.get(ticketChannelId); if (!ticketChannel) { return interaction.reply({ @@ -144,7 +265,7 @@ module.exports = new Event("interactionCreate", async (client, interaction) => { const NoPermsClaim = new Discord.EmbedBuilder() .setTitle(msgconfig.Ticket.ClaimTickets.NoPermsToClaimTitle) .setDescription( - msgconfig.Ticket.ClaimTickets.NoPermsToClaim.replace('%channel%', ticketChannel.name) + msgconfig.Ticket.ClaimTickets.NoPermsToClaim.replace("%channel%", ticketChannel.name) ) .setColor(supportbot.Embed.Colours.Error); @@ -160,7 +281,7 @@ module.exports = new Event("interactionCreate", async (client, interaction) => { setTimeout(() => { pingclaimedstaff.delete(); - }, 2000) + }, 2000); const TicketData = JSON.parse(fs.readFileSync("./Data/TicketData.json", "utf8")); const ticketIndex = TicketData.tickets.findIndex((t) => t.id === ticketChannelId); @@ -174,13 +295,13 @@ module.exports = new Event("interactionCreate", async (client, interaction) => { 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: [] }); @@ -191,36 +312,41 @@ module.exports = new Event("interactionCreate", async (client, interaction) => { } // SUGGESTION INTERACTIONS [START] - - if (interaction.customId === 'upvote' || interaction.customId === 'downvote' || interaction.customId === 'removevote') { + + if ( + interaction.customId === "upvote" || + interaction.customId === "downvote" || + interaction.customId === "removevote" + ) { const suggestion = suggestions[interaction.message.id]; - if (!suggestion) return interaction.reply({ content: 'Suggestion not found.', ephemeral: true }); + if (!suggestion) + return interaction.reply({ content: "Suggestion not found.", ephemeral: true }); const userId = interaction.user.id; const hasUpvoted = suggestion.upvotes.includes(userId); const hasDownvoted = suggestion.downvotes.includes(userId); - if (interaction.customId === 'upvote') { + if (interaction.customId === "upvote") { if (hasUpvoted) { - suggestion.upvotes = suggestion.upvotes.filter(id => id !== userId); + suggestion.upvotes = suggestion.upvotes.filter((id) => id !== userId); } else { suggestion.upvotes.push(userId); if (hasDownvoted) { - suggestion.downvotes = suggestion.downvotes.filter(id => id !== userId); + suggestion.downvotes = suggestion.downvotes.filter((id) => id !== userId); } } - } else if (interaction.customId === 'downvote') { + } else if (interaction.customId === "downvote") { if (hasDownvoted) { - suggestion.downvotes = suggestion.downvotes.filter(id => id !== userId); + suggestion.downvotes = suggestion.downvotes.filter((id) => id !== userId); } else { suggestion.downvotes.push(userId); if (hasUpvoted) { - suggestion.upvotes = suggestion.upvotes.filter(id => id !== userId); + suggestion.upvotes = suggestion.upvotes.filter((id) => id !== userId); } } - } else if (interaction.customId === 'removevote') { - suggestion.upvotes = suggestion.upvotes.filter(id => id !== userId); - suggestion.downvotes = suggestion.downvotes.filter(id => id !== userId); + } else if (interaction.customId === "removevote") { + suggestion.upvotes = suggestion.upvotes.filter((id) => id !== userId); + suggestion.downvotes = suggestion.downvotes.filter((id) => id !== userId); } fs.writeFileSync("./Data/SuggestionData.json", JSON.stringify(suggestions, null, 2)); @@ -228,13 +354,20 @@ module.exports = new Event("interactionCreate", async (client, interaction) => { const upvoteCount = suggestion.upvotes.length; const downvoteCount = suggestion.downvotes.length; - const updatedEmbed = new Discord.EmbedBuilder(interaction.message.embeds[0].data) - .setFields([ - { name: 'Suggestion', value: suggestion.suggestion, inline: true }, - { name: 'From', value: `<@${suggestion.author}>` }, - { name: `${supportbot.Suggestions.UpvoteEmoji} ${supportbot.Suggestions.UpvoteTitle}`, value: `${upvoteCount}`, inline: true }, - { name: `${supportbot.Suggestions.DownvoteEmoji} ${supportbot.Suggestions.DownvoteTitle}`, value: `${downvoteCount}`, inline: true } - ]); + const updatedEmbed = new Discord.EmbedBuilder(interaction.message.embeds[0].data).setFields([ + { name: "Suggestion", value: suggestion.suggestion, inline: true }, + { name: "From", value: `<@${suggestion.author}>` }, + { + name: `${supportbot.Suggestions.UpvoteEmoji} ${supportbot.Suggestions.UpvoteTitle}`, + value: `${upvoteCount}`, + inline: true, + }, + { + name: `${supportbot.Suggestions.DownvoteEmoji} ${supportbot.Suggestions.DownvoteTitle}`, + value: `${downvoteCount}`, + inline: true, + }, + ]); await interaction.update({ embeds: [updatedEmbed] }); } @@ -242,11 +375,14 @@ module.exports = new Event("interactionCreate", async (client, interaction) => { async function handleInvites(interaction, enable) { if (supportbot.Ticket.Invites.StaffOnly) { + const { getRole, getChannel } = 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("Some roles seem to be missing!\nPlease check for errors when starting the bot."); + return interaction.reply( + "Some roles seem to be missing!\nPlease check for errors when starting the bot." + ); } const NoPerms = new Discord.EmbedBuilder() @@ -256,7 +392,10 @@ module.exports = new Event("interactionCreate", async (client, interaction) => { ) .setColor(supportbot.Embed.Colours.Warn); - if (!interaction.member.roles.cache.has(SupportStaff.id) && !interaction.member.roles.cache.has(Admin.id)) { + if ( + !interaction.member.roles.cache.has(SupportStaff.id) && + !interaction.member.roles.cache.has(Admin.id) + ) { return interaction.reply({ embeds: [NoPerms] }); } } @@ -264,9 +403,7 @@ module.exports = new Event("interactionCreate", async (client, interaction) => { await interaction.channel.setInvitable(enable); const message = enable ? msgconfig.Ticket.EnableInvites : msgconfig.Ticket.DisableInvites; - const embed = new Discord.EmbedBuilder() - .setDescription(message) - .setColor(supportbot.Embed.Colours.General); + const embed = new Discord.EmbedBuilder().setDescription(message).setColor(supportbot.Embed.Colours.General); await interaction.reply({ embeds: [embed], @@ -274,5 +411,55 @@ 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`; + let profileData; + if (fs.existsSync(profilePath)) { + profileData = JSON.parse(fs.readFileSync(profilePath, "utf8")); + } else { + profileData = { + bio: "", + timezone: "", + clockedIn: false, + }; + } + + try { + const newBio = interaction.fields.getTextInputValue("bio"); + const newTimezone = interaction.fields.getTextInputValue("timezone"); + + profileData.bio = newBio; + profileData.timezone = newTimezone; + + fs.writeFileSync(profilePath, JSON.stringify(profileData, null, 2)); + + const updatedProfileEmbed = new Discord.EmbedBuilder() + .setTitle(`${interaction.user.username}'s Profile`) + .setColor(supportbot.Embed.Colours.General) + .setThumbnail(interaction.user.displayAvatarURL()) + .addFields( + { name: "Bio", value: newBio || "No bio set.", inline: false }, + { name: "Timezone", value: newTimezone || "No timezone set.", inline: true } + ); + + if (profileData.clockedIn !== undefined) { + updatedProfileEmbed.addFields({ + name: "Clocked In Status", + value: profileData.clockedIn ? "✅ Clocked In" : "❌ Clocked Out", + inline: true, + }); + } + + await interaction.reply({ embeds: [updatedProfileEmbed], ephemeral: true }); + } catch (error) { + console.error("Failed to update profile data: ", error); + await interaction.reply({ + content: "There was an error while updating your profile. Please try again later.", + ephemeral: true, + }); + } + } }); diff --git a/Events/ready.js b/Events/ready.js index 8d228c6..0289a7b 100644 --- a/Events/ready.js +++ b/Events/ready.js @@ -59,7 +59,7 @@ module.exports = new Event("ready", async (client, interaction) => { }); } - console.log(`\u001b[32m`, `――――――――――――――――――――――――――――――――――――――――――――`); + console.log(`\u001b[33m`, `――――――――――――――――――――――――――――――――――――――――――――`); console.log(` `); console.log(`\u001b[31m`, `┏━━━┓╋╋╋╋╋╋╋╋╋╋╋╋╋┏┓┏━━┓╋╋╋┏┓`); console.log(`\u001b[31m`, `┃┏━┓┃╋╋╋╋╋╋╋╋╋╋╋╋┏┛┗┫┏┓┃╋╋┏┛┗┓`); @@ -70,60 +70,22 @@ module.exports = new Event("ready", async (client, interaction) => { console.log(`\u001b[31m`, `┗╋╋╋╋╋╋╋┃┃╋┃┃`); console.log(`\u001b[31m`, `╋╋╋╋╋╋╋┗┛╋┗┛`); console.log(` `); - console.log(`\u001b[32m`, `――――――――――――――――――――――――――――――――――――――――――――`); + console.log(`\u001b[33m`, `――――――――――――――――――――――――――――――――――――――――――――`); + console.log(` `); + console.log(`\u001b[33m`, `${supportbot.General.Name} | [${supportbot.SupportBot_Version}]`, `\u001b[32m`, `Connected to Discord`,); + console.log("\u001b[32m", "SupportBot created by Emerald Development "); console.log(` `); - console.log( - `\u001b[31m`, - `${supportbot.General.Name} | [${supportbot.SupportBot_Version}]`, - `\u001b[32m`, - `Connected to Discord`, - ); - - console.log(`\u001b[31m`, `SupportBot proudly created by Emerald Development`); - + console.log(`\u001b[33m`, `――――――――――――――――― [Links] ――――――――――――――――――`); + console.log("\u001b[32m", "Discord: https://dsc.gg/emerald-dev"); + console.log("\u001b[32m", "Website: https://emeraldsrv.com"); + console.log("\u001b[32m", "Community: https://community.emeraldsrv.com"); + console.log("\u001b[32m", "Documentation: https://docs.emeraldsrv.com"); console.log(` `); - - console.log( - `\u001b[31m`, - `Discord`, - `\u001b[32m`, - `https://dsc.gg/emerald-dev`, - ); - - console.log( - `\u001b[31m`, - `Website`, - `\u001b[32m`, - `https://emeraldsrv.com`, - ); - - console.log( - `\u001b[31m`, - `Marketplace`, - `\u001b[32m`, - `https://market.emeraldsrv.com`, - ); - - console.log( - `\u001b[31m`, - `Documentation`, - `\u001b[32m`, - `https://emeraldsrv.com/third-party`, - ); - + console.log(`\u001b[33m`, `――――――――――――――――― [Invite] ――――――――――――――――――`); + console.log("\u001b[32m", `Invite URL: https://discord.com/api/oauth2/authorize?client_id=${client.user.id}&permissions=8&scope=bot%20applications.commands`); console.log(` `); - - console.log( - `\u001b[31m`, - `Invite URL:`, - `\u001b[36m`, - `https://discord.com/api/oauth2/authorize?client_id=` + - client.user.id + - `&permissions=8&scope=bot%20applications.commands` - ); - - console.log(` `); - console.log(`\u001b[32m`, `――――――――――――――――――――――――――――――――――――――――――――`); + console.log(`\u001b[33m`, `――――――――――――――――― [Config Check] ――――――――――――――――――`); + console.log("\u001b[32m", "Config initialization..."); const roles = [ supportbot.Roles.StaffMember.Admin, @@ -168,35 +130,18 @@ module.exports = new Event("ready", async (client, interaction) => { // if (!find) missingCat.push(cat); // } - if (missingR.length > 0) { - console.log( - "\u001b[31m", - `[MISSING CONFIGURATION]`, - "\u001b[33m", - `The following Roles could not be found in your server.\n`, `\u001b[31m`, `[${missingR.join( - ", " - )}]`, `\n `, - ); + const missingRoles = await Promise.all(roles.map(role => getRole(role, client.guilds.cache.first()))); + const missingChannels = await Promise.all(channels.map(channel => getChannel(channel, client.guilds.cache.first()))); + + if (missingRoles.some(role => !role)) { + console.log("\u001b[31m", `Missing roles in your server configuration: ${missingRoles.filter(role => !role).join(', ')}`); } - if (missingC.length > 0) { - console.log( - "\u001b[31m", - `[MISSING CONFIGURATION]`, - "\u001b[33m", - `The following Channels could not be found in your server.\n`, `\u001b[31m`, `[${missingC.join( - ", " - )}]`, `\n ` - ); - } - if (missingCat.length > 0) { - console.log( - "\u001b[31m", - `[MISSING CONFIGURATION]`, - "\u001b[33m", - `The following Categories could not be found in your server.\n`, `\u001b[31m`, `[${missingCat.join( - ", " - )}]`, `\n `, - ); + if (missingChannels.some(channel => !channel)) { + console.log("\u001b[31m", `Missing channels in your server configuration: ${missingChannels.filter(channel => !channel).join(', ')}`); } + + console.log("\u001b[32m", "Configs initialized, No problems were detected."); + console.log(` `); + }); \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md index 0a0b305..0899fec 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 Emerald Services +Copyright (c) 2024 Emerald Services Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Structures/Client.js b/Structures/Client.js index 350d80d..cb27ba5 100644 --- a/Structures/Client.js +++ b/Structures/Client.js @@ -1,12 +1,8 @@ -// C:\Users\ollie\Documents\GitHub\SupportBot\Structures\Client.js - -// SupportBot | Emerald Services -// Client Structure - const fs = require("fs"); 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 supportbot = yaml.load(fs.readFileSync("./Configs/supportbot.yml", "utf8")); const cmdconfig = yaml.load(fs.readFileSync("./Configs/commands.yml", "utf8")); @@ -89,39 +85,48 @@ class Client extends Discord.Client { tempCommandFiles = tempCommandFiles.filter(item => item !== "Ping.js"); } - if (cmdconfig.Ping.Enabled === false) { - tempCommandFiles = tempCommandFiles.filter(item => item !== "Ping.js"); + if (cmdconfig.AddUser.Enabled === false) { + tempCommandFiles = tempCommandFiles.filter(item => item !== "addUser.js"); } - if (supportbot.Ticket.ClaimTickets.Enabled === false) { - tempCommandFiles = tempCommandFiles.filter(item => item !== "ticketstats.js"); - } + if (cmdconfig.RemoveUser.Enabled === false) { + tempCommandFiles = tempCommandFiles.filter(item => item !== "removeUser.js"); + } + + if (cmdconfig.ForceAddUser.Enabled === false) { + tempCommandFiles = tempCommandFiles.filter(item => item !== "forceaddUser.js"); + } + + if (cmdconfig.Profile.Enabled === false) { + tempCommandFiles = tempCommandFiles.filter(item => item !== "Profile.js"); + } const commandFiles = tempCommandFiles; const commands = commandFiles.map((file) => require(`../Commands/${file}`)); - // Commands - console.log(`\u001b[34;1m`, "▬▬▬▬▬▬▬ Commands ▬▬▬▬▬▬▬"); + // Load main commands + console.log(`\u001b[33m`, "――――――――――――――――――――――――――――――――――――――――――――"); commands.forEach((cmd) => { console.log(`\u001b[32m`, `[CMD]`, `\u001b[37;1m`, `${cmd.name}`, `\u001b[32;1m`, "Loaded"); this.commands.set(cmd.name, cmd); }); - console.log(`\u001b[34;1m`, "▬▬▬▬▬▬▬ Commands ▬▬▬▬▬▬▬"); + // Load addon commands and events + console.log(`\u001b[33m`, "▬▬▬▬▬▬▬ Commands ▬▬▬▬▬▬▬"); if (supportbot.General.Addons.Enabled) { const addonFiles = fs.readdirSync("./Addons").filter((file) => file.endsWith(".js")); const addons = addonFiles.map((file) => { - let addon = require(`../Addons/${file}`); + const addon = require(`../Addons/${file}`); addon.name = file.split(".")[0]; return addon; }); // Addons console.log(" "); - console.log(`\u001b[34;1m`, "▬▬▬▬▬▬▬ Addons ▬▬▬▬▬▬▬"); + console.log(`\u001b[33m`, "▬▬▬▬▬▬▬ Addons ▬▬▬▬▬▬▬"); addons.forEach((addon) => { console.log( @@ -132,18 +137,24 @@ class Client extends Discord.Client { `\u001b[32;1m`, "Loaded" ); - addon.commands?.forEach((command) => { - this.commands.set(command.name, command); - }); - addon.events?.forEach((event) => { - this.on(event.event, (...args) => event.run(this, ...args)); - }); + + if (addon instanceof Command) { + this.commands.set(addon.name, addon); + } + + if (addon.events && Array.isArray(addon.events)) { + addon.events.forEach((event) => { + if (event instanceof Event) { + this.on(event.event, (...args) => event.run(this, ...args)); + } + }); + } }); - console.log(`\u001b[34;1m`, "▬▬▬▬▬▬▬ Addons ▬▬▬▬▬▬▬"); + console.log(`\u001b[33m`, "▬▬▬▬▬▬▬ Addons ▬▬▬▬▬▬▬"); } - // Slash Commands + // Register Slash Commands this.once("ready", async () => { await this.guilds.cache.first()?.commands.set(this.commands); @@ -156,7 +167,7 @@ class Client extends Discord.Client { }); console.log(" "); - console.log(`\u001b[34;1m`, "▬▬▬▬▬▬▬ Events ▬▬▬▬▬▬▬"); + console.log(`\u001b[33m`, "▬▬▬▬▬▬▬ Events ▬▬▬▬▬▬▬"); fs.readdirSync("./Events").filter((file) => file.endsWith(".js")).forEach((file) => { const event = require(`../Events/${file}`); @@ -164,7 +175,7 @@ class Client extends Discord.Client { this.on(event.event, (...args) => event.run(this, ...args)); }); - console.log(`\u001b[34;1m`, "▬▬▬▬▬▬▬ Events ▬▬▬▬▬▬▬"); + console.log(`\u001b[33m`, "▬▬▬▬▬▬▬ Events ▬▬▬▬▬▬▬"); if (process.argv[2] !== "test") { await this.login(token); } else { diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..60c4cb1 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1593 @@ +{ + "name": "supportbot", + "version": "8.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "supportbot", + "version": "8.0.1", + "license": "MIT", + "dependencies": { + "@vitalets/google-translate-api": "^8.0.0", + "axios": "^1.7.2", + "chatgpt": "^5.2.5", + "discord.js": "^14.13.0", + "js-yaml": "^4.1.0", + "moment": "^2.29.3", + "openai": "^4.50.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "dependencies": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@discordjs/builders": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.8.2.tgz", + "integrity": "sha512-6wvG3QaCjtMu0xnle4SoOIeFB4y6fKMN6WZfy3BMKJdQQtPLik8KGzDwBVL/+wTtcE/ZlFjgEk74GublyEVZ7g==", + "dependencies": { + "@discordjs/formatters": "^0.4.0", + "@discordjs/util": "^1.1.0", + "@sapphire/shapeshift": "^3.9.7", + "discord-api-types": "0.37.83", + "fast-deep-equal": "^3.1.3", + "ts-mixer": "^6.0.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/collection": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz", + "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==", + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/@discordjs/formatters": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.4.0.tgz", + "integrity": "sha512-fJ06TLC1NiruF35470q3Nr1bi95BdvKFAF+T5bNfZJ4bNdqZ3VZ+Ttg6SThqTxm6qumSG3choxLBHMC69WXNXQ==", + "dependencies": { + "discord-api-types": "0.37.83" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/rest": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.3.0.tgz", + "integrity": "sha512-C1kAJK8aSYRv3ZwMG8cvrrW4GN0g5eMdP8AuN8ODH5DyOCbHgJspze1my3xHOAgwLJdKUbWNVyAeJ9cEdduqIg==", + "dependencies": { + "@discordjs/collection": "^2.1.0", + "@discordjs/util": "^1.1.0", + "@sapphire/async-queue": "^1.5.2", + "@sapphire/snowflake": "^3.5.3", + "@vladfrangu/async_event_emitter": "^2.2.4", + "discord-api-types": "0.37.83", + "magic-bytes.js": "^1.10.0", + "tslib": "^2.6.2", + "undici": "6.13.0" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/rest/node_modules/@discordjs/collection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.0.tgz", + "integrity": "sha512-mLcTACtXUuVgutoznkh6hS3UFqYirDYAg5Dc1m8xn6OvPjetnUlf/xjtqnnc47OwWdaoCQnHmHh9KofhD6uRqw==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/util": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.1.0.tgz", + "integrity": "sha512-IndcI5hzlNZ7GS96RV3Xw1R2kaDuXEp7tRIy/KlhidpN/BQ1qh1NZt3377dMLTa44xDUNKT7hnXkA/oUAzD/lg==", + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/ws": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.1.1.tgz", + "integrity": "sha512-PZ+vLpxGCRtmr2RMkqh8Zp+BenUaJqlS6xhgWKEZcgC/vfHLEzpHtKkB0sl3nZWpwtcKk6YWy+pU3okL2I97FA==", + "dependencies": { + "@discordjs/collection": "^2.1.0", + "@discordjs/rest": "^2.3.0", + "@discordjs/util": "^1.1.0", + "@sapphire/async-queue": "^1.5.2", + "@types/ws": "^8.5.10", + "@vladfrangu/async_event_emitter": "^2.2.4", + "discord-api-types": "0.37.83", + "tslib": "^2.6.2", + "ws": "^8.16.0" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/ws/node_modules/@discordjs/collection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.0.tgz", + "integrity": "sha512-mLcTACtXUuVgutoznkh6hS3UFqYirDYAg5Dc1m8xn6OvPjetnUlf/xjtqnnc47OwWdaoCQnHmHh9KofhD6uRqw==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@sapphire/async-queue": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.3.tgz", + "integrity": "sha512-x7zadcfJGxFka1Q3f8gCts1F0xMwCKbZweM85xECGI0hBTeIZJGGCrHgLggihBoprlQ/hBmDR5LKfIPqnmHM3w==", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@sapphire/shapeshift": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.9.7.tgz", + "integrity": "sha512-4It2mxPSr4OGn4HSQWGmhFMsNFGfFVhWeRPCRwbH972Ek2pzfGRZtb0pJ4Ze6oIzcyh2jw7nUDa6qGlWofgd9g==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v16" + } + }, + "node_modules/@sapphire/snowflake": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz", + "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "dependencies": { + "defer-to-connect": "^1.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@types/node": { + "version": "20.14.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.12.tgz", + "integrity": "sha512-r7wNXakLeSsGT0H1AU863vS2wa5wBOK4bWMjZz2wj+8nBx+m5PeIn0k8AloSLpRuiwdRQZwarZqHE4FNArPuJQ==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==" + }, + "node_modules/@types/ws": { + "version": "8.5.11", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.11.tgz", + "integrity": "sha512-4+q7P5h3SpJxaBft0Dzpbr6lmMaqh0Jr2tbhJZ/luAwvD7ohSCniYkwz/pLxuT2h0EOa6QADgJj1Ko+TzRfZ+w==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vitalets/google-translate-api": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@vitalets/google-translate-api/-/google-translate-api-8.0.0.tgz", + "integrity": "sha512-CHOi8DRgDAoLlFQy9pm1KHUauIk5ZXys3+Eh0aSYxzJSuxAE7Z2UL8+5MgNdl1oX7t/O7u+SwGhkhu6+U1bNcg==", + "dependencies": { + "configstore": "^5.0.1", + "got": "^9.6.0" + } + }, + "node_modules/@vladfrangu/async_event_emitter": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.4.tgz", + "integrity": "sha512-ZL62PFXEIeGUI8btfJ5S8Flc286eU1ZUSjwyFQtIGXfRUDPZKO+CDJMYb1R71LjGWRZ4n202O+a6FGjsgTw58g==", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/agentkeepalive": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", + "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/atomically": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/atomically/-/atomically-2.0.3.tgz", + "integrity": "sha512-kU6FmrwZ3Lx7/7y3hPS5QnbJfaohcIul5fGqf7ok+4KklIEk9tJ0C2IQPdacSbVUWv6zVHXEBWoWd6NrVMT7Cw==", + "dependencies": { + "stubborn-fs": "^1.2.5", + "when-exit": "^2.1.1" + } + }, + "node_modules/axios": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cacheable-request/node_modules/json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==" + }, + "node_modules/cacheable-request/node_modules/keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "dependencies": { + "json-buffer": "3.0.0" + } + }, + "node_modules/cacheable-request/node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chatgpt": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/chatgpt/-/chatgpt-5.2.5.tgz", + "integrity": "sha512-DNhBzPb2zTDjJADY44XfngMvsvrvHRq1md2VPXLmnKeP1UCeA1B6pV3s9ZRwlcgjVT0RyM77fRj1xj5V11Vctg==", + "dependencies": { + "cac": "^6.7.14", + "conf": "^11.0.1", + "eventsource-parser": "^1.0.0", + "js-tiktoken": "^1.0.5", + "keyv": "^4.5.2", + "p-timeout": "^6.1.1", + "quick-lru": "^6.1.1", + "read-pkg-up": "^9.1.0", + "uuid": "^9.0.0" + }, + "bin": { + "chatgpt": "bin/cli.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/conf": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/conf/-/conf-11.0.2.tgz", + "integrity": "sha512-jjyhlQ0ew/iwmtwsS2RaB6s8DBifcE2GYBEaw2SJDUY/slJJbNfY4GlDVzOs/ff8cM/Wua5CikqXgbFl5eu85A==", + "dependencies": { + "ajv": "^8.12.0", + "ajv-formats": "^2.1.1", + "atomically": "^2.0.0", + "debounce-fn": "^5.1.2", + "dot-prop": "^7.2.0", + "env-paths": "^3.0.0", + "json-schema-typed": "^8.0.1", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "dependencies": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/configstore/node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/debounce-fn": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-5.1.2.tgz", + "integrity": "sha512-Sr4SdOZ4vw6eQDvPYNxHogvrxmCIld/VenC5JbNrFwMiwd7lY/Z18ZFfo+EWNG4DD9nFlAujWAo/wGuOPHmy5A==", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/defer-to-connect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/discord-api-types": { + "version": "0.37.83", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.83.tgz", + "integrity": "sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA==" + }, + "node_modules/discord.js": { + "version": "14.15.3", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.15.3.tgz", + "integrity": "sha512-/UJDQO10VuU6wQPglA4kz2bw2ngeeSbogiIPx/TsnctfzV/tNf+q+i1HlgtX1OGpeOBpJH9erZQNO5oRM2uAtQ==", + "dependencies": { + "@discordjs/builders": "^1.8.2", + "@discordjs/collection": "1.5.3", + "@discordjs/formatters": "^0.4.0", + "@discordjs/rest": "^2.3.0", + "@discordjs/util": "^1.1.0", + "@discordjs/ws": "^1.1.1", + "@sapphire/snowflake": "3.5.3", + "discord-api-types": "0.37.83", + "fast-deep-equal": "3.1.3", + "lodash.snakecase": "4.1.1", + "tslib": "2.6.2", + "undici": "6.13.0" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/dot-prop": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-7.2.0.tgz", + "integrity": "sha512-Ol/IPXUARn9CSbkrdV4VJo7uCy1I3VuSiWCaFSg+8BdUOzF9n3jefIpcgAydvUZbTdEBZs2vEiTiS9m61ssiDA==", + "dependencies": { + "type-fest": "^2.11.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/duplexer3": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz", + "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==" + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventsource-parser": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-1.1.2.tgz", + "integrity": "sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==", + "engines": { + "node": ">=14.18" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-uri": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", + "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==" + }, + "node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==" + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, + "node_modules/formdata-node/node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "dependencies": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/is-core-module": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz", + "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, + "node_modules/js-tiktoken": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.12.tgz", + "integrity": "sha512-L7wURW1fH9Qaext0VzaUDpFGVQgjkdE3Dgsy9/+yXyGEpBKnylTd0mU0bfbNkKDlXRb6TEsZkwuflu1B8uQbJQ==", + "dependencies": { + "base64-js": "^1.5.1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/json-schema-typed": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.1.tgz", + "integrity": "sha512-XQmWYj2Sm4kn4WeTYvmpKEbyPsL7nBsb647c7pMe6l02/yx2+Jfc4dT6UZkEXnIUb5LhD55r2HPsJ1milQ4rDg==" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==" + }, + "node_modules/lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/magic-bytes.js": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.10.0.tgz", + "integrity": "sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==" + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dependencies": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/normalize-url": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", + "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openai": { + "version": "4.53.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.53.0.tgz", + "integrity": "sha512-XoMaJsSLuedW5eoMEMmZbdNoXgML3ujcU5KfwRnC6rnbmZkHE2Q4J/SArwhqCxQRqJwHnQUj1LpiROmKPExZJA==", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7", + "web-streams-polyfill": "^3.2.1" + }, + "bin": { + "openai": "bin/cli" + } + }, + "node_modules/openai/node_modules/@types/node": { + "version": "18.19.42", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.42.tgz", + "integrity": "sha512-d2ZFc/3lnK2YCYhos8iaNIYu9Vfhr92nHiyJHRltXWjXUBjEE+A4I58Tdbnw4VhggSW+2j5y5gTrLs4biNnubg==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.2.tgz", + "integrity": "sha512-UbD77BuZ9Bc9aABo74gfXhNvzC9Tx7SxtHSh1fxvx3jTLLYvmVhiQZZrJzqqU0jKbN32kb5VOKiLEQI/3bIjgQ==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" + }, + "node_modules/prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/quick-lru": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-6.1.2.tgz", + "integrity": "sha512-AAFUA5O1d83pIHEhJwWCq/RQcRukCkn/NSm2QsTEMle5f2hP0ChI2+3Xb051PZCkLryI/Ir1MVKviT2FIloaTQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-7.1.0.tgz", + "integrity": "sha512-5iOehe+WF75IccPc30bWTbpdDQLOCc3Uu8bi3Dte3Eueij81yx1Mrufk8qBx/YAbR4uL1FdUr+7BKXDwEtisXg==", + "dependencies": { + "@types/normalize-package-data": "^2.4.1", + "normalize-package-data": "^3.0.2", + "parse-json": "^5.2.0", + "type-fest": "^2.0.0" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-9.1.0.tgz", + "integrity": "sha512-vaMRR1AC1nrd5CQM0PhlRsO5oc2AAigqr7cCrZ/MW/Rsaflz4RlgzkpL4qoU/z1F6wrbd85iFv1OQj/y5RdGvg==", + "dependencies": { + "find-up": "^6.3.0", + "read-pkg": "^7.1.0", + "type-fest": "^2.5.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==", + "dependencies": { + "lowercase-keys": "^1.0.0" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.18", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz", + "integrity": "sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==" + }, + "node_modules/stubborn-fs": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/stubborn-fs/-/stubborn-fs-1.2.5.tgz", + "integrity": "sha512-H2N9c26eXjzL/S/K+i/RHHcFanE74dptvvjM8iwzwbVcWY/zjBbgRqF3K0DY4+OD+uTTASTBvDoxPDaPN02D7g==" + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-readable-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/ts-mixer": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", + "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==" + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/undici": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.13.0.tgz", + "integrity": "sha512-Q2rtqmZWrbP8nePMq7mOJIN98M0fYvSgV89vwl/BQRT4mDOeY2GXZngfGpcBBhtky3woM7G24wZV3Q304Bv6cw==", + "engines": { + "node": ">=18.0" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==", + "dependencies": { + "prepend-http": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/when-exit": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/when-exit/-/when-exit-2.1.3.tgz", + "integrity": "sha512-uVieSTccFIr/SFQdFWN/fFaQYmV37OKtuaGphMAzi4DmmUlrvRBJW5WSLkHyjNQY/ePJMz3LoiX9R3yy1Su6Hw==" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yocto-queue": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", + "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +}