diff --git a/package-lock.json b/package-lock.json index ab6e74c..d7dd6ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,8 @@ "dependencies": { "@discordjs/builders": "^0.12.0", "discord.js": "^13.6.0", + "humanize": "^0.0.9", + "humanize-duration": "^3.27.1", "jshaiku": "file:../haiku", "typescript": "^4.5.5" } @@ -41,6 +43,7 @@ } }, "../haiku": { + "name": "jshaiku", "version": "1.0.0", "license": "AGPL-3.0", "dependencies": { @@ -225,6 +228,19 @@ "node": ">= 6" } }, + "node_modules/humanize": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/humanize/-/humanize-0.0.9.tgz", + "integrity": "sha1-GZT/rs3+nEQe0r2sdFK3u0yeQaQ=", + "engines": { + "node": "*" + } + }, + "node_modules/humanize-duration": { + "version": "3.27.1", + "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.27.1.tgz", + "integrity": "sha512-jCVkMl+EaM80rrMrAPl96SGG4NRac53UyI1o/yAzebDntEY6K6/Fj2HOjdPg8omTqIe5Y0wPBai2q5xXrIbarA==" + }, "node_modules/jshaiku": { "resolved": "../haiku", "link": true @@ -462,6 +478,16 @@ "mime-types": "^2.1.12" } }, + "humanize": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/humanize/-/humanize-0.0.9.tgz", + "integrity": "sha1-GZT/rs3+nEQe0r2sdFK3u0yeQaQ=" + }, + "humanize-duration": { + "version": "3.27.1", + "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.27.1.tgz", + "integrity": "sha512-jCVkMl+EaM80rrMrAPl96SGG4NRac53UyI1o/yAzebDntEY6K6/Fj2HOjdPg8omTqIe5Y0wPBai2q5xXrIbarA==" + }, "jshaiku": { "version": "file:../haiku", "requires": { diff --git a/package.json b/package.json index 8e5042f..2cba58a 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,8 @@ "dependencies": { "@discordjs/builders": "^0.12.0", "discord.js": "^13.6.0", + "humanize": "^0.0.9", + "humanize-duration": "^3.27.1", "jshaiku": "file:../haiku", "typescript": "^4.5.5" }, diff --git a/replacements.txt b/replacements.txt new file mode 100644 index 0000000..f4e132f --- /dev/null +++ b/replacements.txt @@ -0,0 +1,35 @@ +m!info - Info has moved to /help +m!stats - Stats has moved to /nucleus help +m!settings - Settings has changed, type /settings to see the full list +m!user - User is now /user info +m!avatar - Avatar is now /user avatar +m!roleall - Roleall has moved to /roll all +m!suggest - Suggest has been moved to /nucleus suggest +m!ping - Ping has moved to /nucleus ping +m!server - Server has been moved to /server info +m!tag - Tag has changed to /tag +m!role - Role is now /role +m!viewas - Viewas has moved to /viewas +m!verify - Verify has moved to /verify, please contact the server owners if the information message has not been updated +m!setverify - Setverify has moved to /settings verify role +m!mail - Mail has moved to /ticket create, please contact the server owners if the information message has not been updated +m!prefix - Nucleus' prefix has changed to / +m!setprefix - The prefix is now required to be / due to Discord's new rules on bots +m!warn - Warn has moved to /mod warn +m!clear - Clear has moved to /mod clear +m!kick - Kick has moved to /mod kick +m!softban - Softban has moved to /mod softban +m!ban - Ban has moved to /mod ban +m!unban - Unban has moved to /mod unban +m!purge - Purge has moved to /mod purge +m!punish - Punish options can now be viewed by typing /mod +m!setlog - Setlog has moved to /settings log channel +m!ignore - Ignore has moved to /settings log ignore +m!ignored - Ignored has moved to /settings log ignored +m!stafflog - stafflog has moved to /settings mod channel +m!auto - Auto has been moved to /settings automation +m!modmail - Modmail has been moved to /settings tickets +m!slowmode - Slowmode has moved to /mod slowmode +m!lock - Lock has moved to /mod lock action:add +m!unlock - Unlock has moved to /mod lock action:remove +m!reset - Reset can be found under /settings all \ No newline at end of file diff --git a/src/commands/mod/ban.ts b/src/commands/mod/ban.ts index 8946e17..1a27a2d 100644 --- a/src/commands/mod/ban.ts +++ b/src/commands/mod/ban.ts @@ -1,7 +1,6 @@ -import Discord, { CommandInteraction, GuildMember } from "discord.js"; +import { CommandInteraction, GuildMember } from "discord.js"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import { WrappedCheck } from "jshaiku"; -import getEmojiByName from "../../utils/getEmojiByName.js"; import confirmationMessage from "../../utils/confirmationMessage.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import keyValueList from "../../utils/generateKeyValueList.js"; @@ -20,7 +19,7 @@ const command = (builder: SlashCommandSubcommandBuilder) => const callback = async (interaction: CommandInteraction) => { // TODO:[Modals] Replace this with a modal if (await new confirmationMessage(interaction) - .setEmoji("PUNISH.BAN") + .setEmoji("PUNISH.BAN.RED") .setTitle("Ban") .setDescription(keyValueList({ "user": `<@!${(interaction.options.getMember("user") as GuildMember).id}> (${(interaction.options.getMember("user") as GuildMember).user.username})`, @@ -38,7 +37,7 @@ const callback = async (interaction: CommandInteraction) => { if (interaction.options.getString("notify") != "no") { await (interaction.options.getMember("user") as GuildMember).send({ embeds: [new EmojiEmbed() - .setEmoji("MEMBER.BAN") + .setEmoji("PUNISH.BAN.RED") .setTitle("Banned") .setDescription(`You have been banned in ${interaction.guild.name}` + (interaction.options.getString("reason") ? ` for:\n> ${interaction.options.getString("reason")}` : " with no reason provided.")) @@ -51,18 +50,18 @@ const callback = async (interaction: CommandInteraction) => { try { (interaction.options.getMember("user") as GuildMember).ban({ days: Number(interaction.options.getInteger("delete") ?? 0), - reason: interaction.options.getString("reason") + reason: interaction.options.getString("reason") ?? "No reason provided" }) let failed = (dmd == false && interaction.options.getString("notify") != "no") await interaction.editReply({embeds: [new EmojiEmbed() - .setEmoji(`PUNISH.${failed ? "SOFT" : ""}BAN`) // TODO: Add green ban icon for success + .setEmoji(`PUNISH.BAN.${failed ? "YELLOW" : "GREEN"}`) .setTitle(`Ban`) - .setDescription("The member was banned" + (failed ? ", but the user could not be messaged" : "")) + .setDescription("The member was banned" + (failed ? ", but could not be notified" : "")) .setStatus(failed ? "Warning" : "Success") ], components: []}) } catch { await interaction.editReply({embeds: [new EmojiEmbed() - .setEmoji("MEMBER.BAN") + .setEmoji("PUNISH.BAN.RED") .setTitle(`Ban`) .setDescription("Something went wrong and the user was not banned") .setStatus("Danger") @@ -70,7 +69,7 @@ const callback = async (interaction: CommandInteraction) => { } } else { await interaction.editReply({embeds: [new EmojiEmbed() - .setEmoji("MEMBER.UNBAN") + .setEmoji("PUNISH.BAN.GREEN") .setTitle(`Ban`) .setDescription("No changes were made") .setStatus("Success") diff --git a/src/commands/mod/kick.ts b/src/commands/mod/kick.ts index 895f035..32cd9fd 100644 --- a/src/commands/mod/kick.ts +++ b/src/commands/mod/kick.ts @@ -1,20 +1,92 @@ -import { CommandInteraction } from "discord.js"; +import { CommandInteraction, GuildMember } from "discord.js"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import { WrappedCheck } from "jshaiku"; +import confirmationMessage from "../../utils/confirmationMessage.js"; +import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; +import keyValueList from "../../utils/generateKeyValueList.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder .setName("kick") - .setDescription("Clears a users messages in a channel") + .setDescription("Kicks a user from the server") + .addUserOption(option => option.setName("user").setDescription("The user to kick").setRequired(true)) + .addStringOption(option => option.setName("reason").setDescription("The reason for the kick").setRequired(false)) + .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when they are kicked | Default yes").setRequired(false) + .addChoices([["Yes", "yes"], ["No", "no"]]) + ) -const callback = (interaction: CommandInteraction) => { - interaction.reply("Command incomplete [mod/kick]"); +const callback = async (interaction: CommandInteraction) => { + // TODO:[Modals] Replace this with a modal + if (await new confirmationMessage(interaction) + .setEmoji("PUNISH.KICK.RED") + .setTitle("Kick") + .setDescription(keyValueList({ + "user": `<@!${(interaction.options.getMember("user") as GuildMember).id}> (${(interaction.options.getMember("user") as GuildMember).user.username})`, + "reason": `\n> ${interaction.options.getString("reason") ? interaction.options.getString("reason") : "*No reason provided*"}` + }) + + `The user **will${interaction.options.getString("notify") === "no" ? ' not' : ''}** be notified\n\n` + + `Are you sure you want to kick <@!${(interaction.options.getMember("user") as GuildMember).id}>?`) + .setColor("Danger") +// pluralize("day", interaction.options.getInteger("delete")) +// const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" } + .send()) { + let dmd = false + try { + if (interaction.options.getString("notify") != "no") { + await (interaction.options.getMember("user") as GuildMember).send({ + embeds: [new EmojiEmbed() + .setEmoji("PUNISH.KICK.RED") + .setTitle("Kicked") + .setDescription(`You have been kicked in ${interaction.guild.name}` + + (interaction.options.getString("reason") ? ` for:\n> ${interaction.options.getString("reason")}` : " with no reason provided.")) + .setStatus("Danger") + ] + }) + dmd = true + } + } catch {} + try { + (interaction.options.getMember("user") as GuildMember).kick(interaction.options.getString("reason") ?? "No reason provided.") + let failed = (dmd == false && interaction.options.getString("notify") != "no") + await interaction.editReply({embeds: [new EmojiEmbed() + .setEmoji(`PUNISH.KICK.${failed ? "YELLOW" : "GREEN"}`) + .setTitle(`Kick`) + .setDescription("The member was kicked" + (failed ? ", but could not be notified" : "")) + .setStatus(failed ? "Warning" : "Success") + ], components: []}) + } catch { + await interaction.editReply({embeds: [new EmojiEmbed() + .setEmoji("PUNISH.KICK.RED") + .setTitle(`Kick`) + .setDescription("Something went wrong and the user was not kicked") + .setStatus("Danger") + ], components: []}) + } + } else { + await interaction.editReply({embeds: [new EmojiEmbed() + .setEmoji("PUNISH.KICK.GREEN") + .setTitle(`Kick`) + .setDescription("No changes were made") + .setStatus("Success") + ], components: []}) + } } const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => { - return true; + // Check if Nucleus can kick the member + if (! (interaction.guild.me.roles.highest.position > (interaction.member as GuildMember).roles.highest.position)) throw "I do not have a role higher than that member" + // Check if Nucleus has permission to kick + if (! interaction.guild.me.permissions.has("KICK_MEMBERS")) throw "I do not have the `kick_members` permission"; + // Do not allow kicking Nucleus + if ((interaction.member as GuildMember).id == interaction.guild.me.id) throw "I cannot kick myself" + // Allow the owner to kick anyone + if ((interaction.member as GuildMember).id == interaction.guild.ownerId) return true + // Check if the user has kick_members permission + if (! (interaction.member as GuildMember).permissions.has("KICK_MEMBERS")) throw "You do not have the `kick_members` permission"; + // Check if the user is below on the role list + if (! ((interaction.member as GuildMember).roles.highest.position > (interaction.options.getMember("user") as GuildMember).roles.highest.position)) throw "You do not have a role higher than that member" + // Allow kick + return true } -export { command }; -export { callback }; -export { check }; \ No newline at end of file +export { command, callback, check }; \ No newline at end of file diff --git a/src/commands/mod/lock.ts b/src/commands/mod/lock.ts index ec44493..dbc7b6c 100644 --- a/src/commands/mod/lock.ts +++ b/src/commands/mod/lock.ts @@ -15,6 +15,4 @@ const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => { return true; } -export { command }; -export { callback }; -export { check }; \ No newline at end of file +export { command, callback, check }; \ No newline at end of file diff --git a/src/commands/mod/mute.ts b/src/commands/mod/mute.ts new file mode 100644 index 0000000..ae59e7e --- /dev/null +++ b/src/commands/mod/mute.ts @@ -0,0 +1,191 @@ +import Discord, { CommandInteraction, GuildMember, MessageActionRow } from "discord.js"; +import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; +import { WrappedCheck } from "jshaiku"; +import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; +import getEmojiByName from "../../utils/getEmojiByName.js"; +import confirmationMessage from "../../utils/confirmationMessage.js"; +import keyValueList from "../../utils/generateKeyValueList.js"; +import humanizeDuration from "humanize-duration"; + +const command = (builder: SlashCommandSubcommandBuilder) => + builder + .setName("mute") + .setDescription("Mutes a member using Discord's \"Timeout\" feature") + .addUserOption(option => option.setName("user").setDescription("The user to mute").setRequired(true)) + .addIntegerOption(option => option.setName("days").setDescription("The number of days to mute the user for | Default 0").setMinValue(0).setMaxValue(27).setRequired(false)) + .addIntegerOption(option => option.setName("hours").setDescription("The number of hours to mute the user for | Default 0").setMinValue(0).setMaxValue(23).setRequired(false)) + .addIntegerOption(option => option.setName("minutes").setDescription("The number of minutes to mute the user for | Default 0").setMinValue(0).setMaxValue(59).setRequired(false)) + .addIntegerOption(option => option.setName("seconds").setDescription("The number of seconds to mute the user for | Default 0").setMinValue(0).setMaxValue(59).setRequired(false)) + .addStringOption(option => option.setName("reason").setDescription("The reason for the mute").setRequired(false)) + .addStringOption(option => option.setName("notify").setDescription("The user to notify they have been muted").setRequired(false)) + // TODO: notify the user when the mute is lifted + +const callback = async (interaction: CommandInteraction) => { + const user = interaction.options.getMember("user") as GuildMember + const reason = interaction.options.getString("reason") + const time = { + days: interaction.options.getInteger("days") || 0, + hours: interaction.options.getInteger("hours") || 0, + minutes: interaction.options.getInteger("minutes") || 0, + seconds: interaction.options.getInteger("seconds") || 0 + } + let muteTime = (time.days * 24 * 60 * 60) + (time.hours * 60 * 60) + (time.minutes * 60) + time.seconds + if (muteTime == 0) { + let m = await interaction.reply({embeds: [ + new EmojiEmbed() + .setEmoji("PUNISH.MUTE.GREEN") + .setTitle("Mute") + .setDescription("How long should the user be muted") + .setStatus("Success") + ], components: [ + new MessageActionRow().addComponents([ + new Discord.MessageButton() + .setCustomId("1m") + .setLabel("1 Minute") + .setStyle("SECONDARY"), + new Discord.MessageButton() + .setCustomId("10m") + .setLabel("10 Minutes") + .setStyle("SECONDARY"), + new Discord.MessageButton() + .setCustomId("30m") + .setLabel("30 Minutes") + .setStyle("SECONDARY"), + new Discord.MessageButton() + .setCustomId("1h") + .setLabel("1 Hour") + .setStyle("SECONDARY") + ]), + new MessageActionRow().addComponents([ + new Discord.MessageButton() + .setCustomId("6h") + .setLabel("6 Hours") + .setStyle("SECONDARY"), + new Discord.MessageButton() + .setCustomId("12h") + .setLabel("12 Hours") + .setStyle("SECONDARY"), + new Discord.MessageButton() + .setCustomId("1d") + .setLabel("1 Day") + .setStyle("SECONDARY"), + new Discord.MessageButton() + .setCustomId("1w") + .setLabel("1 Week") + .setStyle("SECONDARY") + ]), + new MessageActionRow().addComponents([ + new Discord.MessageButton() + .setCustomId("cancel") + .setLabel("Cancel") + .setStyle("DANGER") + .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) + ]) + ], ephemeral: true, fetchReply: true}) + let component; + try { + component = await (m as Discord.Message).awaitMessageComponent({filter: (m) => m.user.id === interaction.user.id, time: 2.5 * 60 * 1000}); + } catch { return } + component.deferUpdate(); + if (component.customId == "cancel") return interaction.editReply({embeds: [new EmojiEmbed() + .setEmoji("PUNISH.MUTE.RED") + .setTitle("Mute") + .setDescription("Mute cancelled") + .setStatus("Danger") + ]}) + switch (component.customId) { + case "1m": { muteTime = 60; break; } + case "10m": { muteTime = 60 * 10; break; } + case "30m": { muteTime = 60 * 30; break; } + case "1h": { muteTime = 60 * 60; break; } + case "6h": { muteTime = 60 * 60 * 6; break; } + case "12h": { muteTime = 60 * 60 * 12; break; } + case "1d": { muteTime = 60 * 60 * 24; break; } + case "1w": { muteTime = 60 * 60 * 24 * 7; break; } + } + } else { + await interaction.reply({embeds: [ + new EmojiEmbed() + .setEmoji("PUNISH.MUTE.GREEN") + .setTitle("Mute") + .setDescription("Loading...") + .setStatus("Success") + ], ephemeral: true, fetchReply: true}) + } + if (await new confirmationMessage(interaction) + .setEmoji("PUNISH.MUTE.RED") + .setTitle("Mute") + .setDescription(keyValueList({ + "user": `<@!${user.id}> (${user.user.username})`, + "time": `${humanizeDuration(muteTime * 1000, {round: true})}`, + "reason": `\n> ${interaction.options.getString("reason") ? interaction.options.getString("reason") : "*No reason provided*"}` + }) + + `The user **will${interaction.options.getString("notify") === "no" ? ' not' : ''}** be notified\n\n` + + `Are you sure you want to mute <@!${(interaction.options.getMember("user") as GuildMember).id}>?`) + .setColor("Danger") +// pluralize("day", interaction.options.getInteger("delete")) +// const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" } + .send(true)) { + let dmd = false + try { + if (interaction.options.getString("notify") != "no") { + await (interaction.options.getMember("user") as GuildMember).send({ + embeds: [new EmojiEmbed() + .setEmoji("PUNISH.MUTE.RED") + .setTitle("Muted") + .setDescription(`You have been muted in ${interaction.guild.name}` + + (interaction.options.getString("reason") ? ` for:\n> ${interaction.options.getString("reason")}` : " with no reason provided.\n\n" + + `You will be unmuted at: at ()`)) + .setStatus("Danger") + ] + }) + dmd = true + } + } catch {} + try { + (interaction.options.getMember("user") as GuildMember).timeout(muteTime * 1000, interaction.options.getString("reason") || "No reason provided") + let failed = (dmd == false && interaction.options.getString("notify") != "no") + await interaction.editReply({embeds: [new EmojiEmbed() + .setEmoji(`PUNISH.MUTE.${failed ? "YELLOW" : "GREEN"}`) + .setTitle(`Mute`) + .setDescription("The member was muted" + (failed ? ", but could not be notified" : "")) + .setStatus(failed ? "Warning" : "Success") + ], components: []}) + } catch { + await interaction.editReply({embeds: [new EmojiEmbed() + .setEmoji("PUNISH.MUTE.RED") + .setTitle(`Mute`) + .setDescription("Something went wrong and the user was not kicked") + .setStatus("Danger") + ], components: []}) + } + } else { + await interaction.editReply({embeds: [new EmojiEmbed() + .setEmoji("PUNISH.MUTE.GREEN") + .setTitle(`Mute`) + .setDescription("No changes were made") + .setStatus("Success") + ], components: []}) + } +} + +const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => { + // Check if Nucleus can mute the member + if (! (interaction.guild.me.roles.highest.position > (interaction.member as GuildMember).roles.highest.position)) throw "I do not have a role higher than that member" + // Check if Nucleus has permission to mute + if (! interaction.guild.me.permissions.has("MODERATE_MEMBERS")) throw "I do not have the `moderate_members` permission"; + // Do not allow the user to have admin or be the owner + if ((interaction.options.getMember("user") as GuildMember).permissions.has("ADMINISTRATOR") || (interaction.options.getMember("user") as GuildMember).id == interaction.guild.ownerId) throw "You cannot mute an admin or the owner" + // Do not allow muting Nucleus + if ((interaction.member as GuildMember).id == interaction.guild.me.id) throw "I cannot mute myself" + // Allow the owner to mute anyone + if ((interaction.member as GuildMember).id == interaction.guild.ownerId) return true + // Check if the user has moderate_members permission + if (! (interaction.member as GuildMember).permissions.has("MODERATE_MEMBERS")) throw "You do not have the `moderate_members` permission"; + // Check if the user is below on the role list + if (! ((interaction.member as GuildMember).roles.highest.position > (interaction.options.getMember("user") as GuildMember).roles.highest.position)) throw "You do not have a role higher than that member" + // Allow mute + return true +} + +export { command, callback, check }; \ No newline at end of file diff --git a/src/commands/mod/purge.ts b/src/commands/mod/purge.ts index 7c52b13..6d895f5 100644 --- a/src/commands/mod/purge.ts +++ b/src/commands/mod/purge.ts @@ -1,20 +1,194 @@ -import { CommandInteraction } from "discord.js"; +import Discord, { CommandInteraction, GuildChannel, GuildMember, TextChannel } from "discord.js"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import { WrappedCheck } from "jshaiku"; +import confirmationMessage from "../../utils/confirmationMessage.js"; +import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; +import keyValueList from "../../utils/generateKeyValueList.js"; +import getEmojiByName from "../../utils/getEmojiByName.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder .setName("purge") - .setDescription("Clears messages in a channel") + .setDescription("Bulk deletes messages in a channel") + .addIntegerOption(option => option + .setName("amount") + .setDescription("The amount of messages to delete") + .setRequired(false) + .setMinValue(1) + .setMaxValue(50)) + .addChannelOption(option => option.setName("channel").setDescription("The channel to purge messages from").setRequired(false)) + .addUserOption(option => option.setName("user").setDescription("The user to purge messages from").setRequired(false)) + .addStringOption(option => option.setName("reason").setDescription("The reason for the purge").setRequired(false)) -const callback = (interaction: CommandInteraction) => { - interaction.reply("Command incomplete [mod/purge]"); +const callback = async (interaction: CommandInteraction) => { + let channel = (interaction.options.getChannel("channel") as GuildChannel) ?? interaction.channel + let thischannel + if ((interaction.options.getChannel("channel") as GuildChannel) == null) { + thischannel = true + } else { + thischannel = (interaction.options.getChannel("channel") as GuildChannel).id == interaction.channel.id + } + if (!(["GUILD_TEXT", "GUILD_NEWS", "GUILD_NEWS_THREAD", "GUILD_PUBLIC_THREAD", "GUILD_PRIVATE_THREAD"].includes(channel.type.toString()))) { + return await interaction.reply({ + embeds: [ + new EmojiEmbed() + .setEmoji("CHANNEL.PURGE.RED") + .setTitle("Purge") + .setDescription("You cannot purge this channel") + .setStatus("Danger") + ], + components: [], + ephemeral: true, + }) + } + // TODO:[Modals] Replace this with a modal + if ( !interaction.options.getInteger("amount") ) { + await interaction.reply({ + embeds: [ + new EmojiEmbed() + .setEmoji("CHANNEL.PURGE.RED") + .setTitle("Purge") + .setDescription("Select how many messages to delete") + .setStatus("Danger") + ], + components: [], + ephemeral: true, + fetchReply: true + }) + let deleted = [] + while (true) { + let m = await interaction.editReply({ + embeds: [ + new EmojiEmbed() + .setEmoji("CHANNEL.PURGE.RED") + .setTitle("Purge") + .setDescription("Select how many messages to delete. You can continue clicking until all messages are cleared.") + .setStatus("Danger") + ], + components: [ + new Discord.MessageActionRow().addComponents([ + new Discord.MessageButton() + .setCustomId("1") + .setLabel("1") + .setStyle("SECONDARY"), + new Discord.MessageButton() + .setCustomId("3") + .setLabel("3") + .setStyle("SECONDARY"), + new Discord.MessageButton() + .setCustomId("5") + .setLabel("5") + .setStyle("SECONDARY") + ]), + new Discord.MessageActionRow().addComponents([ + new Discord.MessageButton() + .setCustomId("10") + .setLabel("10") + .setStyle("SECONDARY"), + new Discord.MessageButton() + .setCustomId("25") + .setLabel("25") + .setStyle("SECONDARY"), + new Discord.MessageButton() + .setCustomId("50") + .setLabel("50") + .setStyle("SECONDARY") + ]), + new Discord.MessageActionRow().addComponents([ + new Discord.MessageButton() + .setCustomId("done") + .setLabel("Done") + .setStyle("SUCCESS") + .setEmoji(getEmojiByName("CONTROL.TICK", "id")) + ]) + ] + }) + let component; + try { + component = await (m as Discord.Message).awaitMessageComponent({filter: (m) => m.user.id === interaction.user.id, time: 2.5 * 60 * 1000}); + } catch (e) { break; } + component.deferUpdate(); + if (component.customId === "done") break; + let amount; + try { amount = parseInt(component.customId); } catch { break; } + await (channel as TextChannel).bulkDelete(amount, true); // TODO: Add to deleted list | TODO: Support for users + } + if (deleted.length === 0) return await interaction.editReply({ + embeds: [ + new EmojiEmbed() + .setEmoji("CHANNEL.PURGE.RED") + .setTitle("Purge") + .setDescription("No messages were deleted") + .setStatus("Danger") + ], + components: [] + }) + return await interaction.editReply({ + embeds: [ + new EmojiEmbed() + .setEmoji("CHANNEL.PURGE.GREEN") + .setTitle("Purge") + .setDescription(`Deleted ${deleted.length} messages`) + .setStatus("Success") + ], + components: [] + }) + } else { + if (await new confirmationMessage(interaction) + .setEmoji("CHANNEL.PURGE.RED") + .setTitle("Purge") + .setDescription(keyValueList({ + "channel": `<#${channel.id}> (${(channel as GuildChannel).name})` + (thischannel ? " [This channel]" : ""), + "amount": interaction.options.getInteger("amount").toString(), + "reason": `\n> ${interaction.options.getString("reason") ? interaction.options.getString("reason") : "*No reason provided*"}` + })) + .setColor("Danger") + // pluralize("day", interaction.options.getInteger("amount")) + // const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" } + .send()) { + try { + let messages = await (channel as TextChannel).bulkDelete(interaction.options.getInteger("amount"), true) // TODO: Support for users + let out = "" + messages.reverse().forEach(message => { + out += `${message.author.username}#${message.author.discriminator} (${message.author.id})\n` + let lines = message.content.split("\n") + lines.forEach(line => {out += `> ${line}\n`}) // TODO: Humanize timestamp + out += `\n\n` + }) // TODO: Upload as file + await interaction.editReply({embeds: [new EmojiEmbed() + .setEmoji(`CHANNEL.PURGE.GREEN`) + .setTitle(`Purge`) + .setDescription("Messages cleared") + .setStatus("Success") + ], components: []}) + } catch { + await interaction.editReply({embeds: [new EmojiEmbed() + .setEmoji("CHANNEL.PURGE.RED") + .setTitle(`Purge`) + .setDescription("Something went wrong and no messages were deleted") + .setStatus("Danger") + ], components: []}) + } + } else { + await interaction.editReply({embeds: [new EmojiEmbed() + .setEmoji("CHANNEL.PURGE.GREEN") + .setTitle(`Purge`) + .setDescription("No changes were made") + .setStatus("Success") + ], components: []}) + } + } } const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => { - return true; + // Allow the owner to purge + if ((interaction.member as GuildMember).id == interaction.guild.ownerId) return true + // Check if the user has manage_messages permission + if (! (interaction.member as GuildMember).permissions.has("MANAGE_MESSAGES")) throw "You do not have the `manage_messages` permission"; + // Check if nucleus has the manage_messages permission + if (! interaction.guild.me.permissions.has("MANAGE_MESSAGES")) throw "I do not have the `manage_messages` permission"; + // Allow warn + return true } -export { command }; -export { callback }; -export { check }; \ No newline at end of file +export { command, callback, check }; \ No newline at end of file diff --git a/src/commands/mod/slowmode.ts b/src/commands/mod/slowmode.ts index 32624cc..52ab1bd 100644 --- a/src/commands/mod/slowmode.ts +++ b/src/commands/mod/slowmode.ts @@ -15,6 +15,4 @@ const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => { return true; } -export { command }; -export { callback }; -export { check }; \ No newline at end of file +export { command, callback, check }; \ No newline at end of file diff --git a/src/commands/mod/softban.ts b/src/commands/mod/softban.ts index 19db3c2..6da3a68 100644 --- a/src/commands/mod/softban.ts +++ b/src/commands/mod/softban.ts @@ -1,20 +1,97 @@ -import { CommandInteraction } from "discord.js"; +import { CommandInteraction, GuildMember } from "discord.js"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import { WrappedCheck } from "jshaiku"; +import confirmationMessage from "../../utils/confirmationMessage.js"; +import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; +import keyValueList from "../../utils/generateKeyValueList.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder .setName("softban") - .setDescription("Softbans a user") + .setDescription("Kicks a user and deletes their messages") + .addUserOption(option => option.setName("user").setDescription("The user to softban").setRequired(true)) + .addStringOption(option => option.setName("reason").setDescription("The reason for the softban").setRequired(false)) + .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when they are banbanned | Default yes").setRequired(false) + .addChoices([["Yes", "yes"], ["No", "no"]]) + ) + .addIntegerOption(option => option.setName("delete").setDescription("The days of messages to delete | Default 0").setMinValue(0).setMaxValue(7).setRequired(false)) -const callback = (interaction: CommandInteraction) => { - interaction.reply("Command incomplete [mod/softban]"); +const callback = async (interaction: CommandInteraction) => { + // TODO:[Modals] Replace this with a modal + if (await new confirmationMessage(interaction) + .setEmoji("PUNISH.BAN.RED") + .setTitle("Softban") + .setDescription(keyValueList({ + "user": `<@!${(interaction.options.getMember("user") as GuildMember).id}> (${(interaction.options.getMember("user") as GuildMember).user.username})`, + "reason": `\n> ${interaction.options.getString("reason") ? interaction.options.getString("reason") : "*No reason provided*"}` + }) + + `The user **will${interaction.options.getString("notify") === "no" ? ' not' : ''}** be notified\n` + + `${interaction.options.getInteger("delete") ? interaction.options.getInteger("delete") : 0} day${interaction.options.getInteger("delete") === 1 || interaction.options.getInteger("delete") === null ? "s" : ""} of messages will be deleted\n\n` // TODO:[s addition] + + `Are you sure you want to softban <@!${(interaction.options.getMember("user") as GuildMember).id}>?`) + .setColor("Danger") +// pluralize("day", interaction.options.getInteger("delete")) +// const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" } + .send()) { + let dmd = false + try { + if (interaction.options.getString("notify") != "no") { + await (interaction.options.getMember("user") as GuildMember).send({ + embeds: [new EmojiEmbed() + .setEmoji("PUNISH.BAN.RED") + .setTitle("Kick") + .setDescription(`You have been kicked from ${interaction.guild.name}` + + (interaction.options.getString("reason") ? ` for:\n> ${interaction.options.getString("reason")}` : " with no reason provided.")) + .setStatus("Danger") + ] + }) + dmd = true + } + } catch {} + try { + (interaction.options.getMember("user") as GuildMember).ban({ + days: Number(interaction.options.getInteger("delete") ?? 0), + reason: interaction.options.getString("reason") + }) // TODO: unban here + let failed = (dmd == false && interaction.options.getString("notify") != "no") + await interaction.editReply({embeds: [new EmojiEmbed() + .setEmoji(`PUNISH.BAN.${failed ? "YELLOW" : "GREEN"}`) + .setTitle(`Softban`) + .setDescription("The member was softbanned" + (failed ? ", but could not be notified" : "")) + .setStatus(failed ? "Warning" : "Success") + ], components: []}) + } catch { + await interaction.editReply({embeds: [new EmojiEmbed() + .setEmoji("PUNISH.BAN.RED") + .setTitle(`Softban`) + .setDescription("Something went wrong and the user was not softbanned") + .setStatus("Danger") + ], components: []}) + } + } else { + await interaction.editReply({embeds: [new EmojiEmbed() + .setEmoji("PUNISH.BAN.GREEN") + .setTitle(`Softban`) + .setDescription("No changes were made") + .setStatus("Success") + ], components: []}) + } } const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => { - return true; + // Check if Nucleus can ban the member + if (! (interaction.guild.me.roles.highest.position > (interaction.member as GuildMember).roles.highest.position)) throw "I do not have a role higher than that member" + // Check if Nucleus has permission to ban + if (! interaction.guild.me.permissions.has("BAN_MEMBERS")) throw "I do not have the `ban_members` permission"; + // Do not allow softbanning Nucleus + if ((interaction.member as GuildMember).id == interaction.guild.me.id) throw "I cannot softban myself" + // Allow the owner to ban anyone + if ((interaction.member as GuildMember).id == interaction.guild.ownerId) return true + // Check if the user has ban_members permission + if (! (interaction.member as GuildMember).permissions.has("BAN_MEMBERS")) throw "You do not have the `ban_members` permission"; + // Check if the user is below on the role list + if (! ((interaction.member as GuildMember).roles.highest.position > (interaction.options.getMember("user") as GuildMember).roles.highest.position)) throw "You do not have a role higher than that member" + // Allow softban + return true } -export { command }; -export { callback }; -export { check }; \ No newline at end of file +export { command, callback, check }; \ No newline at end of file diff --git a/src/commands/mod/unban.ts b/src/commands/mod/unban.ts index fe2133e..6477bb3 100644 --- a/src/commands/mod/unban.ts +++ b/src/commands/mod/unban.ts @@ -15,6 +15,4 @@ const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => { return true; } -export { command }; -export { callback }; -export { check }; \ No newline at end of file +export { command, callback, check }; \ No newline at end of file diff --git a/src/commands/mod/clear.ts b/src/commands/mod/unmute.ts similarity index 66% rename from src/commands/mod/clear.ts rename to src/commands/mod/unmute.ts index 78909a9..5194bce 100644 --- a/src/commands/mod/clear.ts +++ b/src/commands/mod/unmute.ts @@ -4,17 +4,15 @@ import { WrappedCheck } from "jshaiku"; const command = (builder: SlashCommandSubcommandBuilder) => builder - .setName("clear") - .setDescription("Clears a users messages in a channel") + .setName("unmute") + .setDescription("Unmutes a member") const callback = (interaction: CommandInteraction) => { - interaction.reply("Command incomplete [mod/clear]"); + interaction.reply("Command incomplete [mod/unmute]"); } const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => { return true; } -export { command }; -export { callback }; -export { check }; \ No newline at end of file +export { command, callback, check }; \ No newline at end of file diff --git a/src/commands/mod/viewas.ts b/src/commands/mod/viewas.ts index 12ab7a8..c7bd4b1 100644 --- a/src/commands/mod/viewas.ts +++ b/src/commands/mod/viewas.ts @@ -15,6 +15,4 @@ const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => { return true; } -export { command }; -export { callback }; -export { check }; \ No newline at end of file +export { command, callback, check }; \ No newline at end of file diff --git a/src/commands/mod/warn.ts b/src/commands/mod/warn.ts index f050ef2..cbf7701 100644 --- a/src/commands/mod/warn.ts +++ b/src/commands/mod/warn.ts @@ -1,20 +1,86 @@ -import { CommandInteraction } from "discord.js"; +import { CommandInteraction, GuildMember } from "discord.js"; import { SlashCommandSubcommandBuilder } from "@discordjs/builders"; import { WrappedCheck } from "jshaiku"; +import confirmationMessage from "../../utils/confirmationMessage.js"; +import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; +import keyValueList from "../../utils/generateKeyValueList.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder .setName("warn") .setDescription("Warns a user") + .addUserOption(option => option.setName("user").setDescription("The user to warn").setRequired(true)) + .addStringOption(option => option.setName("reason").setDescription("The reason for the warn").setRequired(false)) + .addStringOption(option => option.setName("notify").setDescription("If the user should get a message when they are warned | Default yes").setRequired(false) + .addChoices([["Yes", "yes"], ["No", "no"]]) + ) -const callback = (interaction: CommandInteraction) => { - interaction.reply("Command incomplete [mod/warn]"); +const callback = async (interaction: CommandInteraction) => { + // TODO:[Modals] Replace this with a modal + if (await new confirmationMessage(interaction) + .setEmoji("PUNISH.WARN.RED") + .setTitle("Warn") + .setDescription(keyValueList({ + "user": `<@!${(interaction.options.getMember("user") as GuildMember).id}> (${(interaction.options.getMember("user") as GuildMember).user.username})`, + "reason": `\n> ${interaction.options.getString("reason") ? interaction.options.getString("reason") : "*No reason provided*"}` + }) + + `The user **will${interaction.options.getString("notify") === "no" ? ' not' : ''}** be notified\n\n`) + .setColor("Danger") +// pluralize("day", interaction.options.getInteger("delete")) +// const pluralize = (word: string, count: number) => { return count === 1 ? word : word + "s" } + .send()) { + let dmd = false + try { + if (interaction.options.getString("notify") != "no") { + await (interaction.options.getMember("user") as GuildMember).send({ + embeds: [new EmojiEmbed() + .setEmoji("PUNISH.WARN.RED") + .setTitle("Warned") + .setDescription(`You have been warned in ${interaction.guild.name}` + + (interaction.options.getString("reason") ? ` for:\n> ${interaction.options.getString("reason")}` : " with no reason provided.")) + .setStatus("Danger") + ] + }) + dmd = true + } + } catch {} + try { + let failed = (dmd == false && interaction.options.getString("notify") != "no") // TODO: some way of dealing with not DMing users + await interaction.editReply({embeds: [new EmojiEmbed() + .setEmoji(`PUNISH.WARN.${failed ? "YELLOW" : "GREEN"}`) + .setTitle(`Warn`) + .setDescription(failed ? "The user cannot be messaged and was not warned" : "The user was warned") + .setStatus(failed ? "Warning" : "Success") + ], components: []}) + } catch { + await interaction.editReply({embeds: [new EmojiEmbed() + .setEmoji("PUNISH.WARN.RED") + .setTitle(`Warn`) + .setDescription("Something went wrong and the user was not warned") + .setStatus("Danger") + ], components: []}) + } + } else { + await interaction.editReply({embeds: [new EmojiEmbed() + .setEmoji("PUNISH.WARN.GREEN") + .setTitle(`Warn`) + .setDescription("No changes were made") + .setStatus("Success") + ], components: []}) + } } const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => { - return true; + // Do not allow warning bots + if ((interaction.member as GuildMember).user.bot) throw "I cannot warn bots" + // Allow the owner to warn anyone + if ((interaction.member as GuildMember).id == interaction.guild.ownerId) return true + // Check if the user has moderate_members permission + if (! (interaction.member as GuildMember).permissions.has("MODERATE_MEMBERS")) throw "You do not have the `moderate_members` permission"; + // Check if the user is below on the role list + if (! ((interaction.member as GuildMember).roles.highest.position > (interaction.options.getMember("user") as GuildMember).roles.highest.position)) throw "You do not have a role higher than that member" + // Allow warn + return true } -export { command }; -export { callback }; -export { check }; \ No newline at end of file +export { command, callback, check }; \ No newline at end of file diff --git a/src/commands/server.ts b/src/commands/server.ts new file mode 100644 index 0000000..27d0373 --- /dev/null +++ b/src/commands/server.ts @@ -0,0 +1,19 @@ +import { CommandInteraction } from "discord.js"; +import { SlashCommandBuilder } from "@discordjs/builders"; +import { WrappedCheck } from "jshaiku"; + +const command = new SlashCommandBuilder() + .setName("server") + .setDescription("Shows info about the server") + +const callback = (interaction: CommandInteraction) => { + interaction.reply("Command incomplete [server]"); +} + +const check = (interaction: CommandInteraction, defaultCheck: WrappedCheck) => { + return true; +} + +export { command }; +export { callback }; +export { check }; \ No newline at end of file diff --git a/src/config/emojis.json b/src/config/emojis.json index aecd6ef..fc3a145 100644 --- a/src/config/emojis.json +++ b/src/config/emojis.json @@ -1,204 +1,213 @@ { "NUCLEUS": { - "INFO":{ - "HELP":"751751467014029322", - "ABOUT":"751762088346517504", + "INFO": { + "HELP": "751751467014029322", + "ABOUT": "751762088346517504", "COMMANDS": "751762088229339136", - "SUPPORT":"751762087780286495" + "SUPPORT": "751762087780286495" }, "COMMANDS": { - "RAID":"777143043711172608", - "LOCK":"776848800995868682", - "IGNORE":"777520659270074389" + "RAID": "777143043711172608", + "LOCK": "776848800995868682", + "IGNORE": "777520659270074389" } }, - "ICONS": { - "LOADING": "a777130727925219358", "ADD": "826823514904330251", "REMOVE": "826823515268186152", "OPP": { - "ADD":"837355918831124500", - "REMOVE":"837355918420869162" + "ADD": "837355918831124500", + "REMOVE": "837355918420869162" }, "CHANNEL": { - "TEXT":"752971441351426089", - "VOICE":"752971441586307091", - "STORE":"853668786925469706", - "ANNOUNCEMENT":"853668786493063169", - "STAGE":"853668786842763294" + "TEXT": "752971441351426089", + "VOICE": "752971441586307091", + "STORE": "853668786925469706", + "ANNOUNCEMENT": "853668786493063169", + "STAGE": "853668786842763294" } }, - "CONTROL": { - "TICK":"729064531107774534", - "CROSS":"729064530310594601", - "LEFT":"877471438263697459", - "RIGHT":"877471438532132874", + "TICK": "947441964234702849", + "CROSS": "947441948543815720", + "LEFT": "947441951148486728", + "RIGHT": "947441957473488916", "PILL": { - "TICK":"753314339082993832", - "CROSS":"753314339389309100" + "TICK": "753314339082993832", + "CROSS": "753314339389309100" } }, - - "STATUS":{ - "ONLINE":"729064530084102166", - "IDLE":"729064531577536582", - "DND":"729064531057311886", - "STREAMING":"729764055082074153", - "OFFLINE":"729064531271221289" + "STATUS": { + "ONLINE": "729064530084102166", + "IDLE": "729064531577536582", + "DND": "729064531057311886", + "STREAMING": "729764055082074153", + "OFFLINE": "729064531271221289" }, - "CHANNEL": { "TEXT": { - "CREATE":"729066924943737033", - "DELETE":"729064529211686922" + "CREATE": "729066924943737033", + "DELETE": "729064529211686922" }, "VOICE": { - "CREATE":"729064530830950530", - "DELETE":"729064530981683200" + "CREATE": "729064530830950530", + "DELETE": "729064530981683200" }, "STORE": { - "CREATE":"729064530709315715", - "DELETE":"729064530768035922" + "CREATE": "729064530709315715", + "DELETE": "729064530768035922" }, "CATEGORY": { - "CREATE":"787987508465238026", - "EDIT":"787987508565770300", - "DELETE":"787987508507967488" + "CREATE": "787987508465238026", + "EDIT": "787987508565770300", + "DELETE": "787987508507967488" }, - "PURGE":"729064530797133875", - "TITLEUPDATE":"729763053620691044", - "TOPICUPDATE":"729763053477953536", + "PURGE": { + "RED": "729064530797133875", + "GREEN": "947443645391441940" + }, + "TITLEUPDATE": "729763053620691044", + "TOPICUPDATE": "729763053477953536", "SLOWMODE": { - "ON":"777138171301068831", - "OFF":"777138171447869480" + "ON": "777138171301068831", + "OFF": "777138171447869480" }, "NSFW": { - "ON":"729064531208175736", - "OFF":"729381430991388752" + "ON": "729064531208175736", + "OFF": "729381430991388752" } }, - "MEMBER": { - "JOIN":"729066519337762878", - "LEAVE":"729064531170558575", + "JOIN": "729066519337762878", + "LEAVE": "729064531170558575", "BOT": { - "JOIN":"729064528666689587", - "LEAVE":"729064528998039633" + "JOIN": "729064528666689587", + "LEAVE": "729064528998039633" }, - "KICK":"729263536785850458", - "BAN":"729263536643112991", - "UNBAN":"729263536840114216", - "NICKNAME":"729064531019694090" + "KICK": "729263536785850458", + "BAN": "729263536643112991", + "UNBAN": "729263536840114216", + "NICKNAME": "729064531019694090" }, - "INVITE": { - "CREATE":"729064529274601482", - "DELETE":"729064531103580230" + "CREATE": "729064529274601482", + "DELETE": "729064531103580230" }, - "WEBHOOK": { - "CREATE":"752070251906203679", - "UPDATE":"752073772823216191", - "DELETE":"752070251948146768" + "CREATE": "752070251906203679", + "UPDATE": "752073772823216191", + "DELETE": "752070251948146768" }, - "MESSAGE": { - "EDIT":"729065958584614925", - "DELETE":"729064530432360461", - "PIN":"729064530755190894", - "REACTION":{ - "ADD":"", - "REMOVE":"", - "CLEAR":"729064531468353606" + "EDIT": "729065958584614925", + "DELETE": "729064530432360461", + "PIN": "729064530755190894", + "REACTION": { + "ADD": "", + "REMOVE": "", + "CLEAR": "729064531468353606" }, "PING": { - "MASS":"729620608408879124", - "EVERYONE":"729064531073957909", - "ROLE":"729263536915742770" + "MASS": "729620608408879124", + "EVERYONE": "729064531073957909", + "ROLE": "729263536915742770" } }, - "PUNISH": { - "WARN":"729764054897524768", - "KICK":"729764053794422896", - "BAN":"729764053861400637", - "MUTE":"729764053865463840", - "SOFTBAN":"729764053941223476", - "VOICEMUTE":"729764054855450697", - "CLEARHISTORY":"729764062270980096" + "WARN": { + "RED": "947433493384806430", + "GREEN": "947433504076091424", + "YELLOW": "729764054897524768" + }, + "KICK": { + "RED": "729764053794422896", + "GREEN": "947428786692042764", + "YELLOW": "947429333289562132" + }, + "BAN": { + "RED": "729764053861400637", + "GREEN": "947421674364629022", + "YELLOW": "729764053941223476" + }, + "UNBAN": "729263536840114216", + "MUTE": { + "RED": "947555098974883910", + "GREEN": "947555107980066866", + "YELLOW": "729764053865463840" + }, + "SOFTBAN": "729764053941223476", + "VOICEMUTE": "729764054855450697", + "CLEARHISTORY": "729764062270980096" }, - "BADGES": { - "NUCLEUSDEVELOPER":"775783766147858534", - "CLICKSDEVELOPER":"776140126156881950", + "NUCLEUSDEVELOPER": "775783766147858534", + "CLICKSDEVELOPER": "776140126156881950", "HYPESQUAD": { - "BRILLIANCE":"775783766152577095", - "BRAVERY":"775783765930016789", - "BALANCE":"775783766303440937", - "EVENTS":"775783766194126908" + "BRILLIANCE": "775783766152577095", + "BRAVERY": "775783765930016789", + "BALANCE": "775783766303440937", + "EVENTS": "775783766194126908" }, - "EARLYSUPPORTER":"775783766055452693", - "BUGHUNTER":[null, "775783766252847154", "775783766130950234"], - "BOOSTER":"775783766131605545", - "PARTNER":"775783766178005033", - "STAFF":"775783766383788082", - "VERIFIEDBOTDEVELOPER":"775783766425600060", - "NITRO":"776149266775146546", - "BOT":"776375959108190239" + "EARLYSUPPORTER": "775783766055452693", + "BUGHUNTER": [ + null, + "775783766252847154", + "775783766130950234" + ], + "BOOSTER": "775783766131605545", + "PARTNER": "775783766178005033", + "STAFF": "775783766383788082", + "VERIFIEDBOTDEVELOPER": "775783766425600060", + "NITRO": "776149266775146546", + "BOT": "776375959108190239" }, - "VOICE": { - "CONNECT":"784785219391193138", - "CHANGE":"784785219353968670", - "LEAVE":"784785219432480808", - "MUTE":"784785219613360149", - "UNMUTE":"784785219441524766", - "DEAFEN":"784785219424747550", - "UNDEAFEN":"784785219324346378", + "CONNECT": "784785219391193138", + "CHANGE": "784785219353968670", + "LEAVE": "784785219432480808", + "MUTE": "784785219613360149", + "UNMUTE": "784785219441524766", + "DEAFEN": "784785219424747550", + "UNDEAFEN": "784785219324346378", "STREAM": { - "START":"853519659775819787", - "STOP":"853519660116213780" + "START": "853519659775819787", + "STOP": "853519660116213780" }, "VIDEO": { - "START":"853519659945295873", - "STOP":"853519660116738078" + "START": "853519659945295873", + "STOP": "853519660116738078" } }, - "GUILD": { - "EMOJIS":"729066518549233795", - "GRAPHS":"752214059159650396", - "SETTINGS":"752570111063228507", - "ICONCHANGE":"729763053612302356", - "MODERATIONUPDATE":"729763053352124529", + "EMOJIS": "729066518549233795", + "GRAPHS": "752214059159650396", + "SETTINGS": "752570111063228507", + "ICONCHANGE": "729763053612302356", + "MODERATIONUPDATE": "729763053352124529", "MODMAIL": { - "OPEN":"853245836331188264", - "CLOSE":"853580122506133505", - "ARCHIVE":"853580122636025856" + "OPEN": "853245836331188264", + "CLOSE": "853580122506133505", + "ARCHIVE": "853580122636025856" }, "ROLES": { - "CREATE":"729064530763579413", - "DELETE":"729064530885476392", - "EDIT":"776109664793919489", + "CREATE": "729064530763579413", + "DELETE": "729064530885476392", + "EDIT": "776109664793919489", "MEMBERS": "752570111281594509", "MESSAGES": "752570111373606942", "VOICE": "752570111088525354" } }, - "MOD": { "IMAGES": { - "SWEARING":"730438422627614810", - "INVISIBLE":"730438422690398238", - "TOOBIG":"730438422921084998", - "TOOSMALL":"730438422921216150" + "SWEARING": "730438422627614810", + "INVISIBLE": "730438422690398238", + "TOOBIG": "730438422921084998", + "TOOSMALL": "730438422921216150" }, - "SWEARING":"730438422816096377", - "SPAM":"730438422853845042" + "SWEARING": "730438422816096377", + "SPAM": "730438422853845042" }, - "NUMBERS": [ { "NORMAL": "753259024404840529", @@ -251,18 +260,16 @@ "RED": "753312609557545089" } ], - "BOTS": { - "GPS":"878919163937185803", - "NUCLEUS":"878919163597439016", - "CLICKSFORMS":"878919163337388073", - "CASTAWAY":"878919164255944726", - "CMPING":"878919164125929502", - "HOOKY":"878919164121731082" + "GPS": "878919163937185803", + "NUCLEUS": "878919163597439016", + "CLICKSFORMS": "878919163337388073", + "CASTAWAY": "878919164255944726", + "CMPING": "878919164125929502", + "HOOKY": "878919164121731082" }, - "CFSERVICE": { - "VERIFIED":"881984571242053642", - "UNVERIFIED":"881984571258847232" + "VERIFIED": "881984571242053642", + "UNVERIFIED": "881984571258847232" } } \ No newline at end of file diff --git a/src/utils/confirmationMessage.ts b/src/utils/confirmationMessage.ts index 2b769bb..5464e31 100644 --- a/src/utils/confirmationMessage.ts +++ b/src/utils/confirmationMessage.ts @@ -1,5 +1,6 @@ import Discord, { CommandInteraction, MessageActionRow, Message } from "discord.js"; import EmojiEmbed from "./generateEmojiEmbed.js" +import getEmojiByName from "./getEmojiByName.js"; class confirmationMessage { interaction: CommandInteraction; @@ -22,8 +23,8 @@ class confirmationMessage { setDescription(description: string) { this.description = description; return this } setColor(color: string) { this.color = color; return this } - async send() { - let m = await this.interaction.reply({ + async send(editOnly?: boolean) { + let object = { embeds: [ new EmojiEmbed() .setEmoji(this.emoji) @@ -36,16 +37,24 @@ class confirmationMessage { new Discord.MessageButton() .setCustomId("yes") .setLabel("Yes") - .setStyle("SUCCESS"), + .setStyle("SUCCESS") + .setEmoji(getEmojiByName("CONTROL.TICK", "id")), new Discord.MessageButton() .setCustomId("no") - .setLabel("Cancel") // TODO: + .setLabel("Cancel") .setStyle("DANGER") + .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) ]) ], ephemeral: true, fetchReply: true - }) + } + let m; + if ( editOnly ) { + m = await this.interaction.editReply(object); + } else { + m = await this.interaction.reply(object) + } let component; try { component = await (m as Message).awaitMessageComponent({filter: (m) => m.user.id === this.interaction.user.id, time: 2.5 * 60 * 1000}); diff --git a/src/utils/getEmojiByName.ts b/src/utils/getEmojiByName.ts index 64c7ede..0ec7cd6 100644 --- a/src/utils/getEmojiByName.ts +++ b/src/utils/getEmojiByName.ts @@ -1,11 +1,20 @@ import emojis from '../config/emojis.json' assert {type: 'json'}; -function getEmojiByName(name: string): string { +function getEmojiByName(name: string, format?: string): string { let split = name.split("."); let id = emojis split.forEach(part => { id = id[part]; }); + if ( format === "id" ) { + if (id === undefined) return "0"; + return id.toString(); + } + if (id === undefined) { + return `` + } else if (id.toString().startsWith("a")) { + return `` + } return `<:a:${id}>`; }