diff --git a/src/Unfinished/all.ts b/src/Unfinished/all.ts index a6379b7..eea33f5 100644 --- a/src/Unfinished/all.ts +++ b/src/Unfinished/all.ts @@ -16,7 +16,7 @@ import addPlural from "../utils/plurals.js"; import client from "../utils/client.js"; const command = (builder: SlashCommandSubcommandBuilder) => - builder // TODO: DON'T RELEASE THIS + builder .setName("all") .setDescription("Gives or removes a role from everyone"); diff --git a/src/actions/tickets/create.ts b/src/actions/tickets/create.ts index 3e5cacd..935f4ae 100644 --- a/src/actions/tickets/create.ts +++ b/src/actions/tickets/create.ts @@ -257,7 +257,7 @@ export default async function (interaction: CommandInteraction | ButtonInteracti type: Discord.ChannelType.PrivateThread, reason: "Creating ticket" }) as Discord.PrivateThreadChannel; - c.members.add(interaction.member!.user.id); // TODO: When a thread is used, and a support role is added, automatically set channel permissions + c.members.add(interaction.member!.user.id); try { await c.send({ content: diff --git a/src/commands/help.ts b/src/commands/help.ts index e34500f..b6115f9 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -84,7 +84,12 @@ const callback = async (interaction: CommandInteraction): Promise => { if(currentPath[0] === "" || currentPath[0] === "help") { embed.setDescription( `Welcome to Nucleus\n\n` + - `Select a command to get started${(interaction.member?.permissions as PermissionsBitField).has("ManageGuild") ? `, or run ${getCommandMentionByName("nucleus/guide")} for commands to set up your server` : ``}` // FIXME + `Select a command to get started${ + (interaction.member?.permissions as PermissionsBitField).has("ManageGuild") ? + `, or run ${getCommandMentionByName("nucleus/guide")} for commands to set up your server` : `` + }\n\n\n` + + `Nucleus is fully [open source](https://github.com/clicksminuteper/Nucleus), and all currently free features will remain free forever.\n\n` + + `You can invite Nucleus to your server using ${getCommandMentionByName("nucleus/invite")}` ) } else { const currentData = getCommandByName(currentPath.filter(value => value !== "" && value !== "none").join('/')); @@ -95,7 +100,7 @@ const callback = async (interaction: CommandInteraction): Promise => { nameLocalized?: string; descriptionLocalized?: string; })[] = []; - //options + //o ptions if(currentPath[1] !== "" && currentPath[1] !== "none" && currentPath[2] !== "" && currentPath[2] !== "none") { const Op = current.options.find(option => option.name === currentPath[1])! as ApplicationCommandSubGroup const Op2 = Op.options!.find(option => option.name === currentPath[2])! diff --git a/src/commands/mod/mute.ts b/src/commands/mod/mute.ts index b7c1405..407adf4 100644 --- a/src/commands/mod/mute.ts +++ b/src/commands/mod/mute.ts @@ -103,7 +103,7 @@ const callback = async (interaction: CommandInteraction): Promise => { let component; try { component = await m.awaitMessageComponent({ - filter: (i) => {return i.user.id === interaction.user.id && i.id === m.id}, + filter: (i) => {return i.user.id === interaction.user.id && i.channelId === interaction.channelId}, time: 300000 }); } catch { diff --git a/src/commands/mod/slowmode.ts b/src/commands/mod/slowmode.ts index 8d5b709..f282e82 100644 --- a/src/commands/mod/slowmode.ts +++ b/src/commands/mod/slowmode.ts @@ -47,7 +47,7 @@ const callback = async (interaction: CommandInteraction): Promise => { }) + "Are you sure you want to set the slowmode in this channel?" ) .setColor("Danger") - .setFailedMessage("No changes were made", "Danger", "CHANNEL.SLOWMODE.ON") + .setFailedMessage("No changes were made", "Success", "CHANNEL.SLOWMODE.ON") .send(); if (confirmation.cancelled || !confirmation.success) return; try { diff --git a/src/commands/privacy.ts b/src/commands/privacy.ts index cb6054d..8803e25 100644 --- a/src/commands/privacy.ts +++ b/src/commands/privacy.ts @@ -21,7 +21,6 @@ const callback = async (interaction: CommandInteraction): Promise => { .setDescription( "Nucleus is a bot that naturally needs to store data about servers.\n" + "We are entirely [open source](https://github.com/ClicksMinutePer/Nucleus), so you can check exactly what we store, and how it works.\n\n" + - "If you are a server administrator, you can view the options page in the dropdown under this message.\n\n" + // TODO "Any questions about Nucleus, how it works, and what data is stored can be asked in [our server](https://discord.gg/bPaNnxe)." ) .setEmoji("NUCLEUS.LOGO") @@ -51,7 +50,7 @@ const callback = async (interaction: CommandInteraction): Promise => { .setDescription( "**Facebook** - Facebook trackers include data such as your date of birth, and guess your age if not entered, your preferences, who you interact with and more.\n" + "**AMP** - AMP is a technology that allows websites to be served by Google. This means Google can store and track data, and are pushing this to as many pages as possible.\n\n" + - "Transcripts allow you to store all messages sent in a channel. This could be an issue in some cases, as they are hosted on [Pastebin](https://pastebin.com), so a leaked link could show all messages sent in the channel.\n" // TODO: Not on pastebin + "Transcripts allow you to store all messages sent in a channel. This is stored in our database along with the rest of the servers settings but is accessible by anyone with the link, so a leaked link could show all messages sent in the channel.\n" ) .setEmoji("NUCLEUS.LOGO") .setStatus("Danger") @@ -62,26 +61,26 @@ const callback = async (interaction: CommandInteraction): Promise => { ].concat( (interaction.member as Discord.GuildMember).permissions.has("Administrator") ? [ - new Embed() - .setEmbed( - new EmojiEmbed() - .setTitle("Options") - .setDescription("Below are buttons for controlling this servers privacy settings") - .setEmoji("NUCLEUS.LOGO") - .setStatus("Danger") - ) - .setTitle("Options") - .setDescription("Options") - .setPageId(3) - .setComponents([ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setLabel("Clear all data") - .setCustomId("clear-all-data") - .setStyle(ButtonStyle.Danger) - ]) - ]) - ] + new Embed() + .setEmbed( + new EmojiEmbed() + .setTitle("Options") + .setDescription("Below are buttons for controlling this servers privacy settings") + .setEmoji("NUCLEUS.LOGO") + .setStatus("Danger") + ) + .setTitle("Options") + .setDescription("Options") + .setPageId(3) + .setComponents([ + new ActionRowBuilder().addComponents([ + new ButtonBuilder() + .setLabel("Clear all data") + .setCustomId("clear-all-data") + .setStyle(ButtonStyle.Danger) + ]) + ]) + ] : [] ); const m = await interaction.reply({ diff --git a/src/commands/settings/automod.ts b/src/commands/settings/automod.ts index d3d24c9..c3cac04 100644 --- a/src/commands/settings/automod.ts +++ b/src/commands/settings/automod.ts @@ -29,6 +29,7 @@ import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import client from "../../utils/client.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; import { modalInteractionCollector } from "../../utils/dualCollector.js"; +import listToAndMore from "../../utils/listToAndMore.js"; const command = (builder: SlashCommandSubcommandBuilder) => @@ -37,14 +38,6 @@ const command = (builder: SlashCommandSubcommandBuilder) => const emojiFromBoolean = (bool: boolean, id?: string) => bool ? getEmojiByName("CONTROL.TICK", id) : getEmojiByName("CONTROL.CROSS", id); -const listToAndMore = (list: string[], max: number) => { - // PineappleFan, Coded, Mini (and 10 more) - if(list.length > max) { - return list.slice(0, max).join(", ") + ` (and ${list.length - max} more)`; - } - return list.join(", "); -} - const toSelectMenu = async (interaction: StringSelectMenuInteraction, m: Message, ids: string[], type: "member" | "role" | "channel", title: string): Promise => { const back = new ActionRowBuilder().addComponents(new ButtonBuilder().setCustomId("back").setLabel("Back").setStyle(ButtonStyle.Secondary).setEmoji(getEmojiByName("CONTROL.LEFT", "id") as APIMessageComponentEmoji)); diff --git a/src/commands/settings/logs/events.ts b/src/commands/settings/logs/events.ts index 7259fa4..f2ec13a 100644 --- a/src/commands/settings/logs/events.ts +++ b/src/commands/settings/logs/events.ts @@ -31,7 +31,7 @@ const logs: Record = { const command = (builder: SlashCommandSubcommandBuilder) => builder.setName("events").setDescription("Sets what events should be logged"); -const callback = async (interaction: CommandInteraction): Promise => { // TODO: Maybe merge this into /settings log general +const callback = async (interaction: CommandInteraction): Promise => { await interaction.reply({ embeds: LoadingEmbed, fetchReply: true, diff --git a/src/commands/settings/logs/channel.ts b/src/commands/settings/logs/general.ts similarity index 100% rename from src/commands/settings/logs/channel.ts rename to src/commands/settings/logs/general.ts diff --git a/src/commands/settings/logs/staff.ts b/src/commands/settings/logs/warnings.ts similarity index 100% rename from src/commands/settings/logs/staff.ts rename to src/commands/settings/logs/warnings.ts diff --git a/src/commands/settings/commands.ts b/src/commands/settings/moderation.ts similarity index 79% rename from src/commands/settings/commands.ts rename to src/commands/settings/moderation.ts index bbbb24a..ffd3063 100644 --- a/src/commands/settings/commands.ts +++ b/src/commands/settings/moderation.ts @@ -1,45 +1,23 @@ import { LoadingEmbed } from "../../utils/defaults.js"; -import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, Role, ButtonStyle, ButtonComponent, TextInputBuilder, Message } from "discord.js"; +import Discord, { CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, ButtonComponent, TextInputBuilder, Message, RoleSelectMenuBuilder } from "discord.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; import type { SlashCommandSubcommandBuilder } from "discord.js"; import client from "../../utils/client.js"; import { modalInteractionCollector } from "../../utils/dualCollector.js"; -import confirmationMessage from "../../utils/confirmationMessage.js"; -import keyValueList from "../../utils/generateKeyValueList.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder - .setName("commands") + .setName("moderation") .setDescription("Links and text shown to a user after a moderator action is performed") - .addRoleOption((o) => o.setName("role").setDescription("The role given when a member is muted")); -const callback = async (interaction: CommandInteraction): Promise => { +const callback = async (interaction: CommandInteraction): Promise => { await interaction.reply({ embeds: LoadingEmbed, ephemeral: true, fetchReply: true }); let m: Message; - let clicked = ""; - if (interaction.options.get("role")) { - const confirmation = await new confirmationMessage(interaction) - .setEmoji("GUILD.ROLES.DELETE") - .setTitle("Moderation Commands") - .setDescription( - keyValueList({ - role: `<@&${(interaction.options.get("role") as unknown as Role).id}>` - }) - ) - .setColor("Danger") - .send(true); - if (confirmation.cancelled) return - if (confirmation.success) { - await client.database.guilds.write(interaction.guild!.id, { - ["moderation.mute.role"]: (interaction.options.get("role") as unknown as Role).id - }); - } - } let timedOut = false; while (!timedOut) { const config = await client.database.guilds.read(interaction.guild!.id); @@ -52,8 +30,7 @@ const callback = async (interaction: CommandInteraction): Promise => { .setStatus("Success") .setDescription( "These links are shown below the message sent in a user's DM when they are punished.\n\n" + - "**Mute Role:** " + - (moderation.mute.role ? `<@&${moderation.mute.role}>` : "*None set*") + "**Mute Role:** " + (moderation.mute.role ? `<@&${moderation.mute.role}>` : "*None set*") ) ], components: [ @@ -92,18 +69,17 @@ const callback = async (interaction: CommandInteraction): Promise => { .setStyle(ButtonStyle.Secondary) ]), new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setLabel(clicked === "clearMuteRole" ? "Click again to confirm" : "Clear mute role") - .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) - .setCustomId("clearMuteRole") - .setStyle(ButtonStyle.Danger) - .setDisabled(!moderation.mute.role), new ButtonBuilder() .setCustomId("timeout") .setLabel("Mute timeout " + (moderation.mute.timeout ? "Enabled" : "Disabled")) .setStyle(moderation.mute.timeout ? ButtonStyle.Success : ButtonStyle.Danger) .setEmoji(getEmojiByName("CONTROL." + (moderation.mute.timeout ? "TICK" : "CROSS"), "id")) - ]) + ]), + new ActionRowBuilder().addComponents( + new RoleSelectMenuBuilder() + .setCustomId("muteRole") + .setPlaceholder("Select a new mute role") + ) ] }); let i; @@ -118,20 +94,13 @@ const callback = async (interaction: CommandInteraction): Promise => { } type modIDs = "mute" | "kick" | "ban" | "softban" | "warn" | "role"; let chosen = moderation[i.customId as modIDs]; - if ((i.component as ButtonComponent).customId === "clearMuteRole") { + if (i.isRoleSelectMenu()) { await i.deferUpdate(); - if (clicked === "clearMuteRole") { - await client.database.guilds.write(interaction.guild!.id, { - "moderation.mute.role": null - }); - } else { - clicked = "clearMuteRole"; - } + await client.database.guilds.write(interaction.guild!.id, { + "moderation.mute.role": i.values[0]! + }); continue; - } else { - clicked = ""; - } - if ((i.component as ButtonComponent).customId === "timeout") { + } else if ((i.component as ButtonComponent).customId === "timeout") { await i.deferUpdate(); await client.database.guilds.write(interaction.guild!.id, { "moderation.mute.timeout": !moderation.mute.timeout diff --git a/src/commands/settings/rolemenu.ts b/src/commands/settings/rolemenu.ts index 635a1fd..02752c0 100644 --- a/src/commands/settings/rolemenu.ts +++ b/src/commands/settings/rolemenu.ts @@ -107,7 +107,7 @@ const editNameDescription = async (i: ButtonInteraction, interaction: StringSele new TextInputBuilder() .setLabel("Name") .setCustomId("name") - .setPlaceholder("Name here...") // TODO: Make better placeholder + .setPlaceholder("The name of the role (e.g. Programmer)") .setStyle(TextInputStyle.Short) .setValue(name ?? "") .setRequired(true) @@ -117,7 +117,7 @@ const editNameDescription = async (i: ButtonInteraction, interaction: StringSele new TextInputBuilder() .setLabel("Description") .setCustomId("description") - .setPlaceholder("Description here...") // TODO: Make better placeholder + .setPlaceholder("A short description of the role (e.g. A role for people who code)") .setStyle(TextInputStyle.Short) .setValue(description ?? "") ) diff --git a/src/commands/settings/tickets.ts b/src/commands/settings/tickets.ts index 29e6ea4..3d718dc 100644 --- a/src/commands/settings/tickets.ts +++ b/src/commands/settings/tickets.ts @@ -1,66 +1,38 @@ import { LoadingEmbed } from "../../utils/defaults.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; -import confirmationMessage from "../../utils/confirmationMessage.js"; import Discord, { CommandInteraction, - GuildChannel, Message, ActionRowBuilder, ButtonBuilder, - MessageComponentInteraction, StringSelectMenuBuilder, - Role, ButtonStyle, TextInputBuilder, ButtonComponent, ModalSubmitInteraction, - APIMessageComponentEmoji + APIMessageComponentEmoji, + RoleSelectMenuBuilder, + ChannelSelectMenuBuilder, + RoleSelectMenuInteraction, + ButtonInteraction, + ChannelSelectMenuInteraction, + TextInputStyle, + ModalBuilder, + ChannelType } from "discord.js"; import { SlashCommandSubcommandBuilder, StringSelectMenuOptionBuilder } from "discord.js"; -import { ChannelType } from "discord-api-types/v9"; import client from "../../utils/client.js"; import { toHexInteger, toHexArray, tickets as ticketTypes } from "../../utils/calculate.js"; import { capitalize } from "../../utils/generateKeyValueList.js"; import { modalInteractionCollector } from "../../utils/dualCollector.js"; import type { GuildConfig } from "../../utils/database.js"; +import { LinkWarningFooter } from "../../utils/defaults.js"; const command = (builder: SlashCommandSubcommandBuilder) => builder .setName("tickets") - .setDescription("Shows settings for tickets | Use no arguments to manage custom types") - .addStringOption((option) => - option - .setName("enabled") - .setDescription("If users should be able to create tickets") - .setRequired(false) - .addChoices( - {name: "Yes", value: "yes"}, - {name: "No",value: "no"} - ) - ) - .addChannelOption((option) => - option - .setName("category") - .setDescription("The category where tickets are created") - .addChannelTypes(ChannelType.GuildCategory) - .setRequired(false) - ) - .addNumberOption((option) => - option - .setName("maxticketsperuser") - .setDescription("The maximum amount of tickets a user can create | Default: 5") - .setRequired(false) - .setMinValue(1) - ) - .addRoleOption((option) => - option - .setName("supportrole") - .setDescription( - "This role will have view access to all tickets and will be pinged when a ticket is created" - ) - .setRequired(false) - ); + .setDescription("Shows settings for tickets") const callback = async (interaction: CommandInteraction): Promise => { if (!interaction.guild) return; @@ -69,215 +41,80 @@ const callback = async (interaction: CommandInteraction): Promise => { ephemeral: true, fetchReply: true })) as Message; - const options = { - enabled: (interaction.options.get("enabled")?.value as string).startsWith("yes") as boolean | null, - category: interaction.options.get("category")?.channel as Discord.CategoryChannel | null, - maxtickets: interaction.options.get("maxticketsperuser")?.value as number | null, - supportping: interaction.options.get("supportrole")?.role as Role | null - }; - if (options.enabled !== null || options.category || options.maxtickets || options.supportping) { - if (options.category) { - let channel: GuildChannel | null; - try { - channel = await interaction.guild.channels.fetch(options.category.id) as GuildChannel; - } catch { - return await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji("CHANNEL.TEXT.DELETE") - .setTitle("Tickets > Category") - .setDescription("The channel you provided is not a valid category") - .setStatus("Danger") - ] - }); - } - channel = channel as Discord.CategoryChannel; - if (channel.guild.id !== interaction.guild.id) - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Tickets > Category") - .setDescription("You must choose a category in this server") - .setStatus("Danger") - .setEmoji("CHANNEL.TEXT.DELETE") - ] - }); - } - if (options.maxtickets) { - if (options.maxtickets < 1) - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Tickets > Max Tickets") - .setDescription("You must choose a number greater than 0") - .setStatus("Danger") - .setEmoji("CHANNEL.TEXT.DELETE") - ] - }); - } - let role: Role | null; - if (options.supportping) { - try { - role = await interaction.guild.roles.fetch(options.supportping.id); - } catch { - return await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setEmoji("GUILD.ROLE.DELETE") - .setTitle("Tickets > Support Ping") - .setDescription("The role you provided is not a valid role") - .setStatus("Danger") - ] - }); - } - if (!role) return; - role = role as Discord.Role; - if (role.guild.id !== interaction.guild.id) - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Tickets > Support Ping") - .setDescription("You must choose a role in this server") - .setStatus("Danger") - .setEmoji("GUILD.ROLE.DELETE") - ] - }); - } - - const confirmation = await new confirmationMessage(interaction) - .setEmoji("GUILD.TICKET.ARCHIVED") - .setTitle("Tickets") - .setDescription( - (options.category ? `**Category:** ${options.category.name}\n` : "") + - (options.maxtickets ? `**Max Tickets:** ${options.maxtickets}\n` : "") + - (options.supportping ? `**Support Ping:** ${options.supportping.name}\n` : "") + - (options.enabled !== null - ? `**Enabled:** ${ - options.enabled - ? `${getEmojiByName("CONTROL.TICK")} Yes` - : `${getEmojiByName("CONTROL.CROSS")} No` - }\n` - : "") + - "\nAre you sure you want to apply these settings?" - ) - .setColor("Warning") - .setFailedMessage("No changes were made", "Success", "GUILD.TICKET.OPEN") - .setInverted(true) - .send(true); - if (confirmation.cancelled) return; - if (confirmation.success) { - const toUpdate: Record = {}; - if (options.enabled !== null) toUpdate["tickets.enabled"] = options.enabled; - if (options.category) toUpdate["tickets.category"] = options.category.id; - if (options.maxtickets) toUpdate["tickets.maxTickets"] = options.maxtickets; - if (options.supportping) toUpdate["tickets.supportRole"] = options.supportping.id; - try { - await client.database.guilds.write(interaction.guild.id, toUpdate); - } catch (e) { - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Tickets") - .setDescription("Something went wrong and the staff notifications channel could not be set") - .setStatus("Danger") - .setEmoji("GUILD.TICKET.DELETE") - ], - components: [] - }); - } - } else { - return interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Tickets") - .setDescription("No changes were made") - .setStatus("Success") - .setEmoji("GUILD.TICKET.OPEN") - ], - components: [] - }); - } - } const data = await client.database.guilds.read(interaction.guild.id); data.tickets.customTypes = (data.tickets.customTypes ?? []).filter( (value: string, index: number, array: string[]) => array.indexOf(value) === index ); - let lastClicked = ""; - const embed: EmojiEmbed = new EmojiEmbed(); - const compiledData = { - enabled: data.tickets.enabled, - category: data.tickets.category, - maxTickets: data.tickets.maxTickets, - supportRole: data.tickets.supportRole, - useCustom: data.tickets.useCustom, - types: data.tickets.types, - customTypes: data.tickets.customTypes as string[] | null - }; + let ticketData = (await client.database.guilds.read(interaction.guild.id)).tickets + let changesMade = false; let timedOut = false; + let errorMessage = ""; while (!timedOut) { - embed + const embed: EmojiEmbed = new EmojiEmbed() .setTitle("Tickets") .setDescription( - `${compiledData.enabled ? "" : getEmojiByName("TICKETS.REPORT")} **Enabled:** ${ - compiledData.enabled ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No` + `${ticketData.enabled ? "" : getEmojiByName("TICKETS.REPORT")} **Enabled:** ${ + ticketData.enabled ? `${getEmojiByName("CONTROL.TICK")} Yes` : `${getEmojiByName("CONTROL.CROSS")} No` }\n` + - `${compiledData.category ? "" : getEmojiByName("TICKETS.REPORT")} **Category:** ${ - compiledData.category ? `<#${compiledData.category}>` : "*None set*" - }\n` + - `**Max Tickets:** ${compiledData.maxTickets ? compiledData.maxTickets : "*No limit*"}\n` + - `**Support Ping:** ${compiledData.supportRole ? `<@&${compiledData.supportRole}>` : "*None set*"}\n\n` + - (compiledData.useCustom && compiledData.customTypes === null ? `${getEmojiByName("TICKETS.REPORT")} ` : "") + - `${compiledData.useCustom ? "Custom" : "Default"} types in use` + + `${ticketData.category ? "" : getEmojiByName("TICKETS.REPORT")}` + + ((await interaction.guild.channels.fetch(ticketData.category!))!.type === ChannelType.GuildCategory ? + `**Category:** ` : `**Channel:** `) + // TODO: Notify if permissions are wrong + `${ticketData.category ? `<#${ticketData.category}>` : "*None set*"}\n` + + `**Max Tickets:** ${ticketData.maxTickets ? ticketData.maxTickets : "*No limit*"}\n` + + `**Support Ping:** ${ticketData.supportRole ? `<@&${ticketData.supportRole}>` : "*None set*"}\n\n` + + (ticketData.useCustom && ticketData.customTypes === null ? `${getEmojiByName("TICKETS.REPORT")} ` : "") + + `${ticketData.useCustom ? "Custom" : "Default"} types in use` + "\n\n" + `${getEmojiByName("TICKETS.REPORT")} *Indicates a setting stopping tickets from being used*` ) .setStatus("Success") .setEmoji("GUILD.TICKET.OPEN"); + if (errorMessage) embed.setFooter({text: errorMessage, iconURL: LinkWarningFooter.iconURL}); m = (await interaction.editReply({ embeds: [embed], components: [ - new ActionRowBuilder().addComponents([ + new ActionRowBuilder().addComponents( new ButtonBuilder() - .setLabel("Tickets " + (compiledData.enabled ? "enabled" : "disabled")) - .setEmoji(getEmojiByName("CONTROL." + (compiledData.enabled ? "TICK" : "CROSS"), "id")) - .setStyle(compiledData.enabled ? ButtonStyle.Success : ButtonStyle.Danger) + .setLabel("Tickets " + (ticketData.enabled ? "enabled" : "disabled")) + .setEmoji(getEmojiByName("CONTROL." + (ticketData.enabled ? "TICK" : "CROSS"), "id")) + .setStyle(ticketData.enabled ? ButtonStyle.Success : ButtonStyle.Danger) .setCustomId("enabled"), new ButtonBuilder() - .setLabel(lastClicked === "cat" ? "Click again to confirm" : "Clear category") - .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) - .setStyle(ButtonStyle.Danger) - .setCustomId("clearCategory") - .setDisabled(compiledData.category === null), - new ButtonBuilder() - .setLabel(lastClicked === "max" ? "Click again to confirm" : "Reset max tickets") - .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) - .setStyle(ButtonStyle.Danger) - .setCustomId("clearMaxTickets") - .setDisabled(compiledData.maxTickets === 5), - new ButtonBuilder() - .setLabel(lastClicked === "sup" ? "Click again to confirm" : "Clear support ping") - .setEmoji(getEmojiByName("CONTROL.CROSS", "id")) - .setStyle(ButtonStyle.Danger) - .setCustomId("clearSupportPing") - .setDisabled(compiledData.supportRole === null) - ]), - new ActionRowBuilder().addComponents([ + .setLabel("Set max tickets") + .setEmoji(getEmojiByName("CONTROL.TICKET", "id")) + .setStyle(ButtonStyle.Primary) + .setCustomId("setMaxTickets") + .setDisabled(!ticketData.enabled), new ButtonBuilder() .setLabel("Manage types") .setEmoji(getEmojiByName("TICKETS.OTHER", "id")) .setStyle(ButtonStyle.Secondary) - .setCustomId("manageTypes"), + .setCustomId("manageTypes") + .setDisabled(!ticketData.enabled), new ButtonBuilder() - .setLabel("Add create ticket button") - .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id")) - .setStyle(ButtonStyle.Primary) - .setCustomId("send") - ]) + .setLabel("Save") + .setEmoji(getEmojiByName("ICONS.SAVE", "id")) + .setStyle(ButtonStyle.Success) + .setCustomId("save") + .setDisabled(!changesMade) + ), + new ActionRowBuilder().addComponents( + new RoleSelectMenuBuilder() + .setCustomId("supportRole") + .setPlaceholder("Select a support role") + .setDisabled(!ticketData.enabled) + ), + new ActionRowBuilder().addComponents( + new ChannelSelectMenuBuilder() + .setCustomId("category") + .setPlaceholder("Select a category or channel") + .setDisabled(!ticketData.enabled) + ) ] - })) as Message; - let i: MessageComponentInteraction; + })); + let i: RoleSelectMenuInteraction | ButtonInteraction | ChannelSelectMenuInteraction; try { - i = await m.awaitMessageComponent({ + i = await m.awaitMessageComponent<2 | 6 | 8>({ time: 300000, filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id } }); @@ -285,176 +122,49 @@ const callback = async (interaction: CommandInteraction): Promise => { timedOut = true; continue; } - await i.deferUpdate(); - if ((i.component as ButtonComponent).customId === "clearCategory") { - if (lastClicked === "cat") { - lastClicked = ""; - await client.database.guilds.write(interaction.guild.id, null, ["tickets.category"]); - compiledData.category = null; - } else lastClicked = "cat"; - } else if ((i.component as ButtonComponent).customId === "clearMaxTickets") { - if (lastClicked === "max") { - lastClicked = ""; - await client.database.guilds.write(interaction.guild.id, null, ["tickets.maxTickets"]); - compiledData.maxTickets = 5; - } else lastClicked = "max"; - } else if ((i.component as ButtonComponent).customId === "clearSupportPing") { - if (lastClicked === "sup") { - lastClicked = ""; - await client.database.guilds.write(interaction.guild.id, null, ["tickets.supportRole"]); - compiledData.supportRole = null; - } else lastClicked = "sup"; - } else if ((i.component as ButtonComponent).customId === "send") { - const ticketMessages = [ - { - label: "Create ticket", - description: "Click the button below to create a ticket" - }, - { - label: "Issues, questions or feedback?", - description: "Click below to open a ticket and get help from our staff team" - }, - { - label: "Contact Us", - description: "Click the button below to speak to us privately" - } - ]; - let innerTimedOut = false; - let templateSelected = false; - while (!innerTimedOut && !templateSelected) { - const enabled = compiledData.enabled && compiledData.category !== null; - await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Ticket Button") - .setDescription("Select a message template to send in this channel") - .setFooter({ - text: enabled - ? "" - : "Tickets are not set up correctly so the button may not work for users. Check the main menu to find which options must be set." - }) - .setStatus(enabled ? "Success" : "Warning") - .setEmoji("GUILD.ROLES.CREATE") - ], - components: [ - new ActionRowBuilder().addComponents([ - new StringSelectMenuBuilder() - .setOptions( - ticketMessages.map( - ( - t: { - label: string; - description: string; - value?: string; - }, - index - ) => { - t.value = index.toString(); - return t as { - value: string; - label: string; - description: string; - }; - } - ) - ) - .setCustomId("template") - .setMaxValues(1) - .setMinValues(1) - .setPlaceholder("Select a message template") - ]), - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setCustomId("back") - .setLabel("Back") - .setEmoji(getEmojiByName("CONTROL.LEFT", "id")) - .setStyle(ButtonStyle.Danger), - new ButtonBuilder().setCustomId("blank").setLabel("Empty").setStyle(ButtonStyle.Secondary), - new ButtonBuilder() - .setCustomId("custom") - .setLabel("Custom") - .setEmoji(getEmojiByName("TICKETS.OTHER", "id")) - .setStyle(ButtonStyle.Primary) - ]) - ] - }); - let i: MessageComponentInteraction; - try { - i = await m.awaitMessageComponent({ - time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id } - }); - } catch (e) { - innerTimedOut = true; - continue; - } - if (i.isStringSelectMenu() && i.customId === "template") { + changesMade = true; + if (i.isRoleSelectMenu()) { + await i.deferUpdate(); + ticketData.supportRole = i.values[0] ?? null; + } else if (i.isChannelSelectMenu()) { + await i.deferUpdate(); + ticketData.category = i.values[0] ?? null; + } else { + switch(i.customId) { + case "save": { await i.deferUpdate(); - await interaction.channel!.send({ - embeds: [ - new EmojiEmbed() - .setTitle(ticketMessages[parseInt(i.values[0]!)]!.label) - .setDescription( - ticketMessages[parseInt(i.values[0]!)]!.description - ) - .setStatus("Success") - .setEmoji("GUILD.TICKET.OPEN") - ], - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setLabel("Create Ticket") - .setEmoji(getEmojiByName("CONTROL.TICK", "id")) - .setStyle(ButtonStyle.Success) - .setCustomId("createticket") - ]) - ] - }); - templateSelected = true; - continue; - } else if ((i.component as ButtonComponent).customId === "blank") { + await client.database.guilds.write(interaction.guild.id, { tickets: ticketData }); + changesMade = false; + break; + } + case "enabled": { await i.deferUpdate(); - await interaction.channel!.send({ - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setLabel("Create Ticket") - .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id")) - .setStyle(ButtonStyle.Success) - .setCustomId("createticket") - ]) - ] - }); - templateSelected = true; - continue; - } else if ((i.component as ButtonComponent).customId === "custom") { + ticketData.enabled = !ticketData.enabled; + break; + } + case "setMaxTickets": { await i.showModal( - new Discord.ModalBuilder() - .setCustomId("modal") - .setTitle("Enter embed details") + new ModalBuilder() + .setCustomId("maxTickets") + .setTitle("Set max tickets") .addComponents( - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setCustomId("title") - .setLabel("Title") - .setMaxLength(256) - .setRequired(true) - .setStyle(Discord.TextInputStyle.Short) - ), - new ActionRowBuilder().addComponents( + new ActionRowBuilder().setComponents( new TextInputBuilder() - .setCustomId("description") - .setLabel("Description") - .setMaxLength(4000) - .setRequired(true) - .setStyle(Discord.TextInputStyle.Paragraph) + .setLabel("Max tickets - Leave blank for no limit") + .setCustomId("maxTickets") + .setPlaceholder("Enter a number") + .setRequired(false) + .setValue(ticketData.maxTickets.toString() ?? "") + .setMinLength(1) + .setMaxLength(3) + .setStyle(TextInputStyle.Short) ) ) - ); - await interaction.editReply({ + ) + await i.editReply({ embeds: [ new EmojiEmbed() - .setTitle("Ticket Button") + .setTitle("Tickets") .setDescription("Modal opened. If you can't see it, click back and try again.") .setStatus("Success") .setEmoji("GUILD.TICKET.OPEN") @@ -473,52 +183,34 @@ const callback = async (interaction: CommandInteraction): Promise => { try { out = await modalInteractionCollector( m, - (m) => m.channel!.id === interaction.channel!.id, - (m) => m.customId === "modify" + (m) => m.user.id === interaction.user.id, + (m) => m.customId === "back" ); } catch (e) { - innerTimedOut = true; continue; } + if (!out || out.isButton()) continue; out = out as ModalSubmitInteraction; - const title = out.fields.getTextInputValue("title"); - const description = out.fields.getTextInputValue("description"); - await interaction.channel!.send({ - embeds: [ - new EmojiEmbed() - .setTitle(title) - .setDescription(description) - .setStatus("Success") - .setEmoji("GUILD.TICKET.OPEN") - ], - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setLabel("Create Ticket") - .setEmoji(getEmojiByName("TICKETS.SUGGESTION", "id")) - .setStyle(ButtonStyle.Success) - .setCustomId("createticket") - ]) - ] - }); - templateSelected = true; + let toAdd = out.fields.getTextInputValue("maxTickets"); + if(isNaN(parseInt(toAdd))) { + errorMessage = "You entered an invalid number - No changes were made"; + break; + } + ticketData.maxTickets = toAdd === "" ? 0 : parseInt(toAdd); + break; + } + case "manageTypes": { + await i.deferUpdate(); + ticketData = await manageTypes(interaction, data.tickets, m); + break; } } - } else if ((i.component as ButtonComponent).customId === "enabled") { - await client.database.guilds.write(interaction.guild.id, { - "tickets.enabled": !compiledData.enabled - }); - compiledData.enabled = !compiledData.enabled; - } else if ((i.component as ButtonComponent).customId === "manageTypes") { - data.tickets = await manageTypes(interaction, data.tickets, m as Message); } } - await interaction.editReply({ - embeds: [ embed.setFooter({ text: "Message timed out" })], - components: [] - }); }; + + async function manageTypes(interaction: CommandInteraction, data: GuildConfig["tickets"], m: Message) { let timedOut = false; let backPressed = false; @@ -543,7 +235,7 @@ async function manageTypes(interaction: CommandInteraction, data: GuildConfig["t .setStatus("Success") .setEmoji("GUILD.TICKET.OPEN") ], - components: (customTypes + components: (customTypes && customTypes.length > 0 ? [ new ActionRowBuilder().addComponents([ new Discord.StringSelectMenuBuilder() @@ -644,9 +336,6 @@ async function manageTypes(interaction: CommandInteraction, data: GuildConfig["t if (i.isStringSelectMenu() && i.customId === "types") { await i.deferUpdate(); const types = toHexInteger(i.values, ticketTypes); - await client.database.guilds.write(interaction.guild!.id, { - "tickets.types": types - }); data.types = types; } else if (i.isStringSelectMenu() && i.customId === "removeTypes") { await i.deferUpdate(); @@ -655,9 +344,6 @@ async function manageTypes(interaction: CommandInteraction, data: GuildConfig["t if (customTypes) { customTypes = customTypes.filter((t) => !types.includes(t)); customTypes = customTypes.length > 0 ? customTypes : null; - await client.database.guilds.write(interaction.guild!.id, { - "tickets.customTypes": customTypes - }); data.customTypes = customTypes; } } else if ((i.component as ButtonComponent).customId === "addType") { @@ -678,7 +364,7 @@ async function manageTypes(interaction: CommandInteraction, data: GuildConfig["t ) ) ); - await interaction.editReply({ + await i.editReply({ embeds: [ new EmojiEmbed() .setTitle("Tickets > Types") @@ -700,8 +386,8 @@ async function manageTypes(interaction: CommandInteraction, data: GuildConfig["t try { out = await modalInteractionCollector( m, - (m) => m.channel!.id === interaction.channel!.id, - (m) => m.customId === "addType" + (m) => m.user.id === interaction.user.id, + (m) => m.customId === "back" ); } catch (e) { continue; @@ -714,7 +400,8 @@ async function manageTypes(interaction: CommandInteraction, data: GuildConfig["t } toAdd = toAdd.substring(0, 80); try { - await client.database.guilds.append(interaction.guild!.id, "tickets.customTypes", toAdd); + if(!data.customTypes) data.customTypes = []; + data.customTypes?.push(toAdd); } catch { continue; } diff --git a/src/commands/settings/tracks.ts b/src/commands/settings/tracks.ts index 4e27d68..a353324 100644 --- a/src/commands/settings/tracks.ts +++ b/src/commands/settings/tracks.ts @@ -35,7 +35,7 @@ const editName = async (i: ButtonInteraction, interaction: StringSelectMenuInter new TextInputBuilder() .setLabel("Name") .setCustomId("name") - .setPlaceholder("Name here...") // TODO: Make better placeholder + .setPlaceholder("The name of the track (e.g. Moderators)") .setStyle(TextInputStyle.Short) .setValue(name) .setRequired(true) diff --git a/src/commands/settings/verify.ts b/src/commands/settings/verify.ts index 3467aee..1d695f8 100644 --- a/src/commands/settings/verify.ts +++ b/src/commands/settings/verify.ts @@ -174,211 +174,11 @@ const callback = async (interaction: CommandInteraction): Promise => { await client.database.guilds.write(interaction.guild.id, null, ["verify.role", "verify.enabled"]); role = null; } - } else if ((i.component as ButtonComponent).customId === "send") { - const verifyMessages = [ - { - label: "Verify", - description: "Click the button below to get verified" - }, - { - label: "Get verified", - description: "To get access to the rest of the server, click the button below" - }, - { - label: "Ready to verify?", - description: "Click the button below to verify yourself" - } - ]; - let innerTimedOut = false; - let templateSelected = false; - while (!innerTimedOut && !templateSelected) { - await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Verify Button") - .setDescription("Select a message template to send in this channel") - .setFooter({ - text: role ? "" : "You do no have a verify role set so the button will not work." - }) - .setStatus(role ? "Success" : "Warning") - .setEmoji("GUILD.ROLES.CREATE") - ], - components: [ - new ActionRowBuilder().addComponents([ - new StringSelectMenuBuilder() - .setOptions( - verifyMessages.map( - ( - t: { - label: string; - description: string; - value?: string; - }, - index - ) => { - t.value = index.toString(); - return t as { - value: string; - label: string; - description: string; - }; - } - ) - ) - .setCustomId("template") - .setMaxValues(1) - .setMinValues(1) - .setPlaceholder("Select a message template") - ]), - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setCustomId("back") - .setLabel("Back") - .setEmoji(getEmojiByName("CONTROL.LEFT", "id")) - .setStyle(ButtonStyle.Danger), - new ButtonBuilder().setCustomId("blank").setLabel("Empty").setStyle(ButtonStyle.Secondary), - new ButtonBuilder() - .setCustomId("custom") - .setLabel("Custom") - .setEmoji(getEmojiByName("TICKETS.OTHER", "id")) - .setStyle(ButtonStyle.Primary) - ]) - ] - }); - let i: MessageComponentInteraction; - try { - i = await m.awaitMessageComponent({ - time: 300000, - filter: (i) => { return i.user.id === interaction.user.id && i.channel!.id === interaction.channel!.id && i.message.id === m.id } - }); - } catch (e) { - innerTimedOut = true; - continue; - } - if (i.isStringSelectMenu() && i.customId === "template") { - await i.deferUpdate(); - await interaction.channel!.send({ - embeds: [ - new EmojiEmbed() - .setTitle(verifyMessages[parseInt(i.values[0]!)]!.label) - .setDescription( - verifyMessages[parseInt(i.values[0]!)]!.description - ) - .setStatus("Success") - .setEmoji("CONTROL.BLOCKTICK") - ], - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setLabel("Verify") - .setEmoji(getEmojiByName("CONTROL.TICK", "id")) - .setStyle(ButtonStyle.Success) - .setCustomId("verifybutton") - ]) - ] - }); - templateSelected = true; - continue; - } else if ((i.component as ButtonComponent).customId === "blank") { - await i.deferUpdate(); - await interaction.channel!.send({ - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setLabel("Verify") - .setEmoji(getEmojiByName("CONTROL.TICK", "id")) - .setStyle(ButtonStyle.Success) - .setCustomId("verifybutton") - ]) - ] - }); - templateSelected = true; - continue; - } else if ((i.component as ButtonComponent).customId === "custom") { - await i.showModal( - new Discord.ModalBuilder() - .setCustomId("modal") - .setTitle("Enter embed details") - .addComponents( - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setCustomId("title") - .setLabel("Title") - .setMaxLength(256) - .setRequired(true) - .setStyle(Discord.TextInputStyle.Short) - ), - new ActionRowBuilder().addComponents( - new TextInputBuilder() - .setCustomId("description") - .setLabel("Description") - .setMaxLength(4000) - .setRequired(true) - .setStyle(Discord.TextInputStyle.Paragraph) - ) - ) - ); - await interaction.editReply({ - embeds: [ - new EmojiEmbed() - .setTitle("Verify Button") - .setDescription("Modal opened. If you can't see it, click back and try again.") - .setStatus("Success") - .setEmoji("GUILD.TICKET.OPEN") - ], - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setLabel("Back") - .setEmoji(getEmojiByName("CONTROL.LEFT", "id")) - .setStyle(ButtonStyle.Primary) - .setCustomId("back") - ]) - ] - }); - let out; - try { - out = await modalInteractionCollector( - m, - (m: Interaction) => - (m as MessageComponentInteraction | ModalSubmitInteraction).channelId === - interaction.channelId, - (m) => m.customId === "modify" - ); - } catch (e) { - innerTimedOut = true; - continue; - } - if (out !== null && out instanceof ModalSubmitInteraction) { - const title = out.fields.getTextInputValue("title"); - const description = out.fields.getTextInputValue("description"); - await interaction.channel!.send({ - embeds: [ - new EmojiEmbed() - .setTitle(title) - .setDescription(description) - .setStatus("Success") - .setEmoji("CONTROL.BLOCKTICK") - ], - components: [ - new ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setLabel("Verify") - .setEmoji(getEmojiByName("CONTROL.TICK", "id")) - .setStyle(ButtonStyle.Success) - .setCustomId("verifybutton") - ]) - ] - }); - templateSelected = true; - } - } - } } else { await i.deferUpdate(); break; } - } + } // TODO: On save, clear SEN await interaction.editReply({ embeds: [new EmbedBuilder(m.embeds[0]!.data).setFooter({ text: "Message closed" })], components: [] diff --git a/src/commands/user/about.ts b/src/commands/user/about.ts index c32bf8a..0eb8580 100644 --- a/src/commands/user/about.ts +++ b/src/commands/user/about.ts @@ -173,11 +173,8 @@ async function userAbout(guild: Discord.Guild, member: Discord.GuildMember, inte generateKeyValueList({ member: renderUser(member.user), id: `\`${member.id}\``, - roles: `${member.roles.cache.size - 1}` // FIXME - }) + - "\n" + - (s.length > 0 ? s : "*None*") + - "\n" + roles: `${member.roles.cache.size - 1}` + }) + "\n" + (s.length > 0 ? s : "*None*") + "\n" ) ) .setTitle("Roles") diff --git a/src/commands/user/role.ts b/src/commands/user/role.ts index 19bd3c7..5aa8782 100644 --- a/src/commands/user/role.ts +++ b/src/commands/user/role.ts @@ -4,14 +4,7 @@ import client from "../../utils/client.js"; import EmojiEmbed from "../../utils/generateEmojiEmbed.js"; import { LoadingEmbed } from "../../utils/defaults.js"; import getEmojiByName from "../../utils/getEmojiByName.js"; - -const listToAndMore = (list: string[], max: number) => { - // PineappleFan, Coded, Mini (and 10 more) - if(list.length > max) { - return list.slice(0, max).join(", ") + ` (and ${list.length - max} more)`; - } - return list.join(", "); -} +import listToAndMore from "../../utils/listToAndMore.js" const { renderUser } = client.logger; diff --git a/src/config/emojis.json b/src/config/emojis.json index 9ccb9fa..e4afdfb 100644 --- a/src/config/emojis.json +++ b/src/config/emojis.json @@ -98,9 +98,8 @@ "TITLEUPDATE": "729763053620691044", "TOPICUPDATE": "729763053477953536", "SLOWMODE": { - "ON": "777138171301068831", - "OFF": "777138171447869480", - "// TODO": "Make these green and red respectively" + "ON": "973616021304913950", + "OFF": "777138171447869480" }, "NSFW": { "ON": "729064531208175736", diff --git a/src/events/guildMemberUpdate.ts b/src/events/guildMemberUpdate.ts index 8889f57..d25c9c4 100644 --- a/src/events/guildMemberUpdate.ts +++ b/src/events/guildMemberUpdate.ts @@ -43,7 +43,7 @@ export async function callback(client: NucleusClient, before: GuildMember, after log(data); } else if ( (before.communicationDisabledUntilTimestamp ?? 0) < new Date().getTime() && - (after.communicationDisabledUntil ?? 0) > new Date().getTime() // TODO: test this + (after.communicationDisabledUntil ?? 0) > new Date().getTime() ) { await client.database.history.create( "mute", diff --git a/src/events/roleUpdate.ts b/src/events/roleUpdate.ts index a728b79..09e062f 100644 --- a/src/events/roleUpdate.ts +++ b/src/events/roleUpdate.ts @@ -32,6 +32,12 @@ export async function callback(client: NucleusClient, oldRole: Role, newRole: Ro changes["mentionable"] = entry([oldRole.mentionable, newRole.mentionable], `${mentionable[0]} -> ${mentionable[1]}`); if (oldRole.hexColor !== newRole.hexColor) changes["color"] = entry([oldRole.hexColor, newRole.hexColor], `\`${oldRole.hexColor}\` -> \`${newRole.hexColor}\``); + if (oldRole.permissions.bitfield !== newRole.permissions.bitfield) { + changes["permissions"] = entry( + [oldRole.permissions.bitfield.toString(), newRole.permissions.bitfield.toString()], + `[[Old]](https://discordapi.com/permissions.html#${oldRole.permissions.bitfield.toString()}) -> [[New]](https://discordapi.com/permissions.html#${newRole.permissions.bitfield.toString()})` + ); + } if (Object.keys(changes).length === 4) return; @@ -48,6 +54,6 @@ export async function callback(client: NucleusClient, oldRole: Role, newRole: Ro hidden: { guild: newRole.guild.id } - }; // TODO: show perms changed (webpage) + }; // TODO: make our own page for this log(data); } diff --git a/src/premium/attachmentLogs.ts b/src/premium/attachmentLogs.ts index 078587e..3c583f2 100644 --- a/src/premium/attachmentLogs.ts +++ b/src/premium/attachmentLogs.ts @@ -43,7 +43,7 @@ export default async function logAttachment(message: Message): Promise { "**General:** These are events like kicks and channel changes etc.\n" + `> These are standard logs and can be set with ${getCommandMentionByName("settings/logs/general")}\n` + "**Warnings:** Warnings like NSFW avatars and spam etc. that may require action by a server staff member.\n" + - `> These may require special action by a moderator. You can set the channel with ${getCommandMentionByName("settings/logs/warnings")}\n` + // TODO + `> These may require special action by a moderator. You can set the channel with ${getCommandMentionByName("settings/logs/warnings")}\n` + "**Attachments:** All images sent in the server - Used to keep a record of deleted images\n" + `> Sent to a separate log channel to avoid spam. This can be set with ${getCommandMentionByName("settings/logs/attachments")}\n` + `> ${getEmojiByName("NUCLEUS.PREMIUM")} Please note this feature is only available with ${getCommandMentionByName("nucleus/premium")}` diff --git a/src/reflex/verify.ts b/src/reflex/verify.ts index f7d0396..290e372 100644 --- a/src/reflex/verify.ts +++ b/src/reflex/verify.ts @@ -13,6 +13,8 @@ import fetch from "node-fetch"; import { TestString, NSFWCheck } from "./scanners.js"; import createPageIndicator from "../utils/createPageIndicator.js"; import client from "../utils/client.js"; +import singleNotify from "../utils/singleNotify.js"; +import { getCommandMentionByName } from "../utils/getCommandDataByName.js"; export interface VerifySchema { uID: string; @@ -206,7 +208,8 @@ export default async function (interaction: CommandInteraction | ButtonInteracti .setEmoji("CONTROL.BLOCKCROSS") ] }); - return; // TODO: SEN + singleNotify("verifyRoleDeleted", interaction.guild!.id, `The role given when a member is verified has been deleted. Use ${getCommandMentionByName("settings/verify")} to set a new one`, "Critical") + return; } verify[code] = { uID: interaction.member!.user.id, diff --git a/src/utils/database.ts b/src/utils/database.ts index 7deb3c1..6eb735e 100644 --- a/src/utils/database.ts +++ b/src/utils/database.ts @@ -168,7 +168,7 @@ export class ScanCache { } async write(hash: string, data: boolean, tags?: string[]) { - await this.scanCache.insertOne({ hash: hash, data: data, tags: tags ?? [], addedAt: new Date() }); // TODO: cleanup function maybe + await this.scanCache.insertOne({ hash: hash, data: data, tags: tags ?? [], addedAt: new Date() }); } async cleanup() { diff --git a/src/utils/listToAndMore.ts b/src/utils/listToAndMore.ts new file mode 100644 index 0000000..791ce40 --- /dev/null +++ b/src/utils/listToAndMore.ts @@ -0,0 +1,7 @@ +export default (list: string[], max: number) => { + // PineappleFan, Coded, Mini (and 10 more) + if(list.length > max) { + return list.slice(0, max).join(", ") + ` (and ${list.length - max} more)`; + } + return list.join(", "); +} \ No newline at end of file