updated /nucleus suggest to be nice, fixed help, added needs saving to footer for automod settings.
diff --git a/src/commands/help.ts b/src/commands/help.ts
index 96971e6..041fb47 100644
--- a/src/commands/help.ts
+++ b/src/commands/help.ts
@@ -22,7 +22,7 @@
import { getCommandByName, getCommandMentionByName } from "../utils/getCommandDataByName.js";
import getEmojiByName from "../utils/getEmojiByName.js";
-const command = new SlashCommandBuilder().setName("help").setDescription("Shows help for commands");
+const command = new SlashCommandBuilder().setName("help").setDescription("Shows help for commands").setDMPermission(true);
const styles: Record<string, { emoji: string }> = {
help: { emoji: "NUCLEUS.LOGO" },
@@ -127,7 +127,7 @@
);
}
for (const option of options) {
- optionString += `> \`${option.name}\` (${ApplicationCommandOptionType[option.type]}) - ${
+ optionString += ` - \`${option.name}\` (${ApplicationCommandOptionType[option.type].replace("Integer", "Number").replace("String", "Text")}) - ${
option.description
}\n`;
}
@@ -136,14 +136,12 @@
"commands/" + currentPath.filter((value) => value !== "" && value !== "none").join("/")
]![0];
let allowedToRun = true;
- if (APICommand?.check) {
+ if (interaction.guild && APICommand?.check) {
allowedToRun = await APICommand.check(interaction as Interaction, true);
}
embed.setDescription(
- `${getEmojiByName(styles[currentPath[0]]!.emoji)} **${capitalize(currentData.name)}**\n> ${
- currentData.mention
- }\n\n` +
- `> ${currentData.description}\n\n` +
+ `${getEmojiByName(styles[currentPath[0]]!.emoji)} **${capitalize(currentData.name)}** | ${currentData.mention}\n\n` +
+ `${currentData.description}\n\n` +
(APICommand
? `${getEmojiByName(allowedToRun ? "CONTROL.TICK" : "CONTROL.CROSS")} You ${
allowedToRun ? "" : "don't "
diff --git a/src/commands/mod/nick.ts b/src/commands/mod/nick.ts
index a8d4ab8..ed14efb 100644
--- a/src/commands/mod/nick.ts
+++ b/src/commands/mod/nick.ts
@@ -81,7 +81,7 @@
"Change nickname",
"ICONS.EDIT",
"modal",
- newNickname ?? "",
+ {default: newNickname ?? ""},
new ModalBuilder().setTitle("Editing nickname").addComponents(
new ActionRowBuilder<TextInputBuilder>().addComponents(
new TextInputBuilder()
@@ -103,7 +103,7 @@
notify = confirmation.components["notify"]!.active;
createAppealTicket = confirmation.components["appeal"]!.active;
}
- if (confirmation.modals) newNickname = confirmation.modals![0]!.value;
+ if (confirmation.modals) newNickname = confirmation.modals![0]!.values["default"];
} while (!timedOut && !success);
if (timedOut || !success) return;
let dmSent = false;
@@ -222,7 +222,7 @@
});
};
-const check = async (interaction: CommandInteraction | ButtonInteraction, partial: boolean, target?: GuildMember) => {
+const check = (interaction: CommandInteraction | ButtonInteraction, partial: boolean, target?: GuildMember) => {
const member = interaction.member as GuildMember;
// Check if the user has manage_nicknames permission
if (!member.permissions.has("ManageNicknames")) return "You do not have the *Manage Nicknames* permission";
diff --git a/src/commands/nucleus/stats.ts b/src/commands/nucleus/stats.ts
index b2658bc..c2bf9a1 100644
--- a/src/commands/nucleus/stats.ts
+++ b/src/commands/nucleus/stats.ts
@@ -21,6 +21,54 @@
const command = (builder: SlashCommandSubcommandBuilder) =>
builder.setName("stats").setDescription("Gets the bot's stats");
+
+const confirm = async (interaction: CommandInteraction) => {
+ const requiredTexts = [
+ "just do it",
+ "yes, do as i say!",
+ "clicksminuteper/nucleus",
+ "i've said it once i'll say it again",
+ "no, i've changed my mind",
+ "this incident will be reported",
+ "coded told me to",
+ "mini told me to",
+ "pinea told me to",
+ "what's a java script",
+ "it's a feature not a bug",
+ "that never happened during testing"
+ ]
+ const chosen = requiredTexts[Math.floor(Math.random() * (requiredTexts.length - 1))]!;
+
+ const modal = new ModalBuilder()
+ .addComponents(
+ new ActionRowBuilder<TextInputBuilder>().addComponents(
+ new TextInputBuilder()
+ .setStyle(TextInputStyle.Short)
+ .setLabel(`Type "${chosen}" below`)
+ .setCustomId("confirm")
+ .setPlaceholder("Guild ID")
+ .setMinLength(chosen.length)
+ .setMaxLength(chosen.length)
+ )
+ )
+ .setTitle("Admin Panel")
+ .setCustomId("adminPanel");
+ await interaction.showModal(modal);
+ let out: ModalSubmitInteraction;
+ try {
+ out = await interaction.awaitModalSubmit({
+ filter: (i) => i.customId === "adminPanel" && i.user.id === interaction.user.id,
+ time: 300000
+ });
+ } catch {
+ return;
+ }
+ await out.deferUpdate();
+ const typed = out.fields.getTextInputValue("confirm");
+ return typed.toLowerCase() === chosen.toLowerCase()
+}
+
+
const callback = async (interaction: CommandInteraction): Promise<void> => {
const description = `**Servers:** ${client.guilds.cache.size}\n` + `**Ping:** \`${client.ws.ping * 2}ms\``;
const m = await interaction.reply({
@@ -45,11 +93,7 @@
],
components: [
new ActionRowBuilder<ButtonBuilder>().addComponents(
- new ButtonBuilder().setCustomId("admin").setLabel("Admin Panel").setStyle(ButtonStyle.Primary),
- new ButtonBuilder()
- .setCustomId("mod:nickname:599498449733550102")
- .setLabel("Testing")
- .setStyle(ButtonStyle.Primary)
+ new ButtonBuilder().setCustomId("admin").setLabel("Admin Panel").setStyle(ButtonStyle.Primary)
)
]
});
@@ -77,28 +121,30 @@
return;
// console.log(interaction)
if (!("awaitMessageComponent" in channel)) return;
- try {
- i1 = await channel!.awaitMessageComponent<ComponentType.Button>({
- filter: (i) => i.customId === "admin" && i.user.id === interaction.user.id && i.message.id === m.id,
- time: 300000
- });
- } catch (e) {
- console.log(e);
- return;
- }
- await i1.showModal(modal);
- let out: ModalSubmitInteraction;
- try {
- out = await i1.awaitModalSubmit({
- filter: (i) => i.customId === "adminPanel" && i.user.id === interaction.user.id,
- time: 300000
- });
- } catch {
- return;
- }
- await out.deferUpdate();
- const GuildID = out.fields.getTextInputValue("guildID");
- if (!client.guilds.cache.has(GuildID)) {
+ let GuildID = interaction.guildId;
+ if (!GuildID) {
+ try {
+ i1 = await channel!.awaitMessageComponent<ComponentType.Button>({
+ filter: (i) => i.customId === "admin" && i.user.id === interaction.user.id && i.message.id === m.id,
+ time: 300000
+ });
+ } catch (e) {
+ console.log(e);
+ return;
+ }
+ await i1.showModal(modal);
+ let out: ModalSubmitInteraction;
+ try {
+ out = await i1.awaitModalSubmit({
+ filter: (i) => i.customId === "adminPanel" && i.user.id === interaction.user.id,
+ time: 300000
+ });
+ } catch {
+ return;
+ }
+ await out.deferUpdate();
+ GuildID = out.fields.getTextInputValue("guildID");
+ } else if (!client.guilds.cache.has(GuildID)) {
await interaction.editReply({
embeds: [new EmojiEmbed().setTitle("Admin").setDescription("Not in server").setStatus("Danger")],
components: []
@@ -110,10 +156,10 @@
components: [
new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder().setCustomId("stats").setLabel("Stats").setStyle(ButtonStyle.Primary),
- new ButtonBuilder().setCustomId("leave").setLabel("Leave").setStyle(ButtonStyle.Danger),
new ButtonBuilder().setCustomId("data").setLabel("Guild data").setStyle(ButtonStyle.Secondary),
+ new ButtonBuilder().setCustomId("cache").setLabel("Reset cache").setStyle(ButtonStyle.Success),
+ new ButtonBuilder().setCustomId("leave").setLabel("Leave").setStyle(ButtonStyle.Danger),
new ButtonBuilder().setCustomId("purge").setLabel("Delete data").setStyle(ButtonStyle.Danger),
- new ButtonBuilder().setCustomId("cache").setLabel("Reset cache").setStyle(ButtonStyle.Success)
)
]
});
@@ -126,9 +172,9 @@
} catch {
return;
}
- await i.deferUpdate();
const guild = (await client.guilds.fetch(GuildID)) as Guild | null;
if (!guild) {
+ await i.deferUpdate();
await interaction.editReply({
embeds: [new EmojiEmbed().setTitle("Admin").setDescription("Not in server").setStatus("Danger")],
components: []
@@ -136,66 +182,91 @@
return;
}
if (i.customId === "stats") {
+ await i.deferUpdate();
await interaction.editReply({
embeds: [
new EmojiEmbed()
- .setTitle("Stats")
- .setDescription(
- `**Name:** ${guild.name}\n` +
- `**ID:** \`${guild.id}\`\n` +
- `**Owner:** ${client.users.cache.get(guild.ownerId)!.tag}\n` +
- `**Member Count:** ${guild.memberCount}\n` +
- `**Created:** <t:${guild.createdTimestamp}:F>\n` +
- `**Added Nucleus:** <t:${guild.members.me!.joinedTimestamp}:R>\n` +
- `**Nucleus' Perms:** https://discordapi.com/permissions.html#${guild.members.me!.permissions.valueOf()}\n`
+ .setTitle("Stats")
+ .setDescription(
+ `**Name:** ${guild.name}\n` +
+ `**ID:** \`${guild.id}\`\n` +
+ `**Owner:** ${client.users.cache.get(guild.ownerId)!.tag}\n` +
+ `**Member Count:** ${guild.memberCount}\n` +
+ `**Created:** <t:${guild.createdTimestamp}:F>\n` +
+ `**Added Nucleus:** <t:${guild.members.me!.joinedTimestamp}:R>\n` +
+ `**Nucleus' Perms:** https://discordapi.com/permissions.html#${guild.members.me!.permissions.valueOf()}\n`
)
.setStatus("Success")
.setEmoji("SETTINGS.STATS.GREEN")
- ]
- });
- } else if (i.customId === "leave") {
- await guild.leave();
- await interaction.editReply({
- embeds: [
- new EmojiEmbed()
+ ]
+ });
+ } else if (i.customId === "leave") {
+ if (!await confirm(interaction)) {
+ await interaction.editReply({
+ embeds: [
+ new EmojiEmbed()
+ .setTitle("No changes were made")
+ .setStatus("Danger")
+ ],
+ components: []
+ });
+ return;
+ }
+ await guild.leave();
+ await interaction.editReply({
+ embeds: [
+ new EmojiEmbed()
.setTitle("Left")
.setDescription(`Left ${guild.name}`)
.setStatus("Success")
.setEmoji("SETTINGS.STATS.GREEN")
- ],
- components: []
- });
- } else if (i.customId === "data") {
- // Get all the data and convert to a string
- const data = await client.database.guilds.read(guild.id);
- const stringified = JSON.stringify(data, null, 2);
- const buffer = Buffer.from(stringified);
- const attachment = new AttachmentBuilder(buffer).setName("data.json");
- await interaction.editReply({
- embeds: [
- new EmojiEmbed().setTitle("Data").setDescription(`Data for ${guild.name}`).setStatus("Success")
- ],
- components: [],
- files: [attachment]
- });
- } else if (i.customId === "purge") {
- await client.database.guilds.delete(GuildID);
- await client.database.history.delete(GuildID);
- await client.database.notes.delete(GuildID);
- await client.database.transcripts.deleteAll(GuildID);
- await interaction.editReply({
- embeds: [
- new EmojiEmbed()
+ ],
+ components: []
+ });
+ } else if (i.customId === "data") {
+ await i.deferUpdate();
+ // Get all the data and convert to a string
+ const data = await client.database.guilds.read(guild.id);
+ const stringified = JSON.stringify(data, null, 2);
+ const buffer = Buffer.from(stringified);
+ const attachment = new AttachmentBuilder(buffer).setName("data.json");
+ await interaction.editReply({
+ embeds: [
+ new EmojiEmbed().setTitle("Data").setDescription(`Data for ${guild.name}`).setStatus("Success")
+ ],
+ components: [],
+ files: [attachment]
+ });
+ } else if (i.customId === "purge") {
+ if (!await confirm(interaction)) {
+ await interaction.editReply({
+ embeds: [
+ new EmojiEmbed()
+ .setTitle("No changes were made")
+ .setStatus("Danger")
+ ],
+ components: []
+ });
+ return;
+ }
+ await client.database.guilds.delete(GuildID);
+ await client.database.history.delete(GuildID);
+ await client.database.notes.delete(GuildID);
+ await client.database.transcripts.deleteAll(GuildID);
+ await interaction.editReply({
+ embeds: [
+ new EmojiEmbed()
.setTitle("Purge")
.setDescription(`Deleted data for ${guild.name}`)
.setStatus("Success")
.setEmoji("SETTINGS.STATS.GREEN")
- ],
- components: []
- });
- } else if (i.customId === "cache") {
- await client.memory.forceUpdate(guild.id);
- await interaction.editReply({
+ ],
+ components: []
+ });
+ } else if (i.customId === "cache") {
+ await i.deferUpdate();
+ await client.memory.forceUpdate(guild.id);
+ await interaction.editReply({
embeds: [
new EmojiEmbed()
.setTitle("Cache")
diff --git a/src/commands/nucleus/suggest.ts b/src/commands/nucleus/suggest.ts
index 79a0673..44926c7 100644
--- a/src/commands/nucleus/suggest.ts
+++ b/src/commands/nucleus/suggest.ts
@@ -1,64 +1,115 @@
import { LoadingEmbed } from "../../utils/defaults.js";
-import { ButtonStyle, CommandInteraction } from "discord.js";
-import Discord from "discord.js";
+import Discord, { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction, ModalBuilder, TextInputBuilder, TextInputStyle } from "discord.js";
import type { SlashCommandSubcommandBuilder } from "discord.js";
import confirmationMessage from "../../utils/confirmationMessage.js";
import EmojiEmbed from "../../utils/generateEmojiEmbed.js";
import client from "../../utils/client.js";
-import getEmojiByName from "../../utils/getEmojiByName.js";
+import config from "../../config/main.js"
+import _ from "lodash";
const command = (builder: SlashCommandSubcommandBuilder) =>
builder
.setName("suggest")
.setDescription("Sends a suggestion to the developers")
- .addStringOption((option) =>
- option.setName("suggestion").setDescription("The suggestion to send").setRequired(true)
- );
const callback = async (interaction: CommandInteraction): Promise<void> => {
await interaction.guild?.members.fetch(interaction.member!.user.id);
- const { renderUser } = client.logger;
- const suggestion = interaction.options.get("suggestion")?.value as string;
await interaction.reply({ embeds: LoadingEmbed, ephemeral: true });
- const confirmation = await new confirmationMessage(interaction)
- .setEmoji("ICONS.OPP.ADD")
- .setTitle("Suggest")
- .setDescription(
- `**Suggestion:**\n> ${suggestion}\n` +
- "Your username and ID will also be sent with your suggestion.\n\nAre you sure you want to send this suggestion?"
- )
- .setColor("Danger")
- .setInverted(true)
- .setFailedMessage("Your suggestion was deleted", "Success", "ICONS.ADD")
- .send(true);
- if (confirmation.cancelled || !confirmation.success) return;
- await (client.channels.cache.get("955161206459600976") as Discord.TextChannel).send({
+ let closed = false;
+ let suggestionTitle: string | null = null
+ let suggestionDesc: string | null = null;
+ do {
+ const modal = new ModalBuilder()
+ .setTitle("Suggestion")
+ .setComponents(
+ new ActionRowBuilder<TextInputBuilder>()
+ .addComponents(
+ new TextInputBuilder()
+ .setLabel("Suggestion Title")
+ .setRequired(false)
+ .setStyle(TextInputStyle.Short)
+ .setCustomId("suggestionTitle")
+ .setPlaceholder("Summarize your suggestion in 1 sentence...")
+ .setMaxLength(256)
+ ),
+ new ActionRowBuilder<TextInputBuilder>()
+ .addComponents(
+ new TextInputBuilder()
+ .setLabel("Suggestion Description")
+ .setCustomId("suggestionDesc")
+ .setStyle(TextInputStyle.Paragraph)
+ .setRequired(true)
+ .setPlaceholder("Put the full details of your suggestion here...")
+ .setMinLength(50)
+ ),
+ )
+ const o: {suggestionDesc?: string, suggestionTitle?: string} = {};
+ if(suggestionTitle) {
+ o.suggestionTitle = suggestionTitle;
+ modal.components[0]!.components[0]!.setValue(suggestionTitle);
+ }
+ if(suggestionDesc) {
+ o.suggestionDesc = suggestionDesc
+ modal.components[1]!.components[0]!.setValue(suggestionDesc);
+ };
+ const confirmation = await new confirmationMessage(interaction)
+ .setEmoji("ICONS.ADD")
+ .setTitle("Suggest")
+ .setDescription(suggestionDesc ? (`Are you sure you want to send this suggestion?\n\n**Title ${suggestionTitle ? "" : "(*Placeholder*)"}:**\n> ${suggestionTitle ? suggestionTitle : `${suggestionDesc.substring(0, 70)}`}\n\n**Suggestion:**\n> ${suggestionDesc}`) : "Please enter your suggestion below.")
+ .addModal("Edit Suggestion", "ICONS.EDIT", "editSuggestion", _.cloneDeep(o), modal)
+ .setColor("Success")
+ .setInverted(true)
+ .setFailedMessage("Your suggestion was deleted", "Success", "ICONS.ADD")
+ .send(true);
+ if(confirmation.modals?.[0] && !_.isEqual(confirmation.modals[0].values, o)) {
+ suggestionTitle = confirmation.modals[0].values["suggestionTitle"] as string | null;
+ suggestionDesc = confirmation.modals[0].values["suggestionDesc"] as string | null;
+ continue;
+ }
+ if(confirmation.cancelled || confirmation.success === false) {
+ closed = true;
+ return;
+ }
+ if (confirmation.success) {
+ closed = true;
+ };
+ } while (!closed);
+ if(!suggestionDesc) return;
+ suggestionTitle = suggestionTitle ? suggestionTitle : `${suggestionDesc.substring(0, 70)}`;
+ const channel = client.channels.cache.get(config.suggestionChannel) as Discord.TextChannel;
+ const m = await channel.send({ embeds: LoadingEmbed});
+ const issue = await client.GitHub.rest.issues.create({
+ owner: "ClicksMinutePer",
+ repo: "Nucleus",
+ title: suggestionTitle,
+ body: `Linked Suggestion in Private Developer Channel: [Message](${m.url})\n\n**Suggestion:**\n> ${
+ suggestionDesc.replaceAll("@", "@<!-- -->").replaceAll("/issues", "/issues<!-- -->").replaceAll("/pull", "/pull<!-- -->")
+ }\n\n`,
+ labels: ["🤖 Auto", "📝 Suggestion"]
+ })
+ await m.edit({
embeds: [
new EmojiEmbed()
- .setTitle("Suggestion")
- .setDescription(
- `**From:** ${renderUser(
- interaction.member!.user as Discord.User
- )}\n**Suggestion:**\n> ${suggestion}\n\n` +
- `**Server:** ${interaction.guild!.name} (${interaction.guild!.id})\n`
- )
- .setStatus("Warning")
+ .setEmoji("ICONS.ADD")
+ .setTitle(`Suggestion from ${interaction.user.tag} (${interaction.user.id})`)
+ .setDescription(`**Suggestion:**\n> ${suggestionDesc}\n\n`)
+ .setStatus("Success")
+ .setFooter({text: `${issue.data.number}`})
],
components: [
- new Discord.ActionRowBuilder<Discord.ButtonBuilder>().addComponents(
- new Discord.ButtonBuilder()
- .setCustomId("suggestionAccept")
- .setLabel("Accept")
- .setStyle(ButtonStyle.Secondary)
- .setEmoji(getEmojiByName("ICONS.ADD", "id")),
- new Discord.ButtonBuilder()
- .setCustomId("suggestionDeny")
- .setLabel("Delete")
- .setStyle(ButtonStyle.Secondary)
- .setEmoji(getEmojiByName("ICONS.REMOVE", "id"))
+ new Discord.ActionRowBuilder<ButtonBuilder>().addComponents(
+ new ButtonBuilder().setCustomId("accept:Suggestion").setLabel("Accept").setStyle(ButtonStyle.Success),
+ new ButtonBuilder().setCustomId("deny:Suggestion").setLabel("Deny").setStyle(ButtonStyle.Danger),
+ new ButtonBuilder().setCustomId("close:Suggestion").setLabel("Close").setStyle(ButtonStyle.Secondary),
+ new ButtonBuilder().setCustomId("implemented:Suggestion").setLabel("Implemented").setStyle(ButtonStyle.Secondary),
+ new ButtonBuilder().setLabel(`Open Issue #${issue.data.number}`).setStyle(ButtonStyle.Link).setURL(`https://github.com/ClicksMinutePer/Nucleus/issues/${issue.data.number}`),
+ ),
+ new Discord.ActionRowBuilder<ButtonBuilder>().addComponents(
+ new ButtonBuilder().setCustomId("lock:Suggestion").setLabel("Lock").setStyle(ButtonStyle.Danger),
+ new ButtonBuilder().setCustomId("spam:Suggestion").setLabel("Mark as Spam").setStyle(ButtonStyle.Danger),
)
]
- });
+ })
await interaction.editReply({
embeds: [
new EmojiEmbed()
diff --git a/src/commands/privacy.ts b/src/commands/privacy.ts
index 3a671ed..7fefa36 100644
--- a/src/commands/privacy.ts
+++ b/src/commands/privacy.ts
@@ -29,11 +29,11 @@
.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" +
- "Any questions about Nucleus, how it works, and what data is stored can be asked in [our server](https://discord.gg/bPaNnxe)."
+ "Any questions about Nucleus, how it works, and what data is stored can be asked in [our server](https://discord.gg/bPaNnxe)." +
+ "\n\n[[Privacy Policy]](https://clicksminuteper.github.io/policies/nucleus) | [[Terms of Service]](https://clicksminuteper.github.io/policies/nucleustos)"
)
.setEmoji("NUCLEUS.LOGO")
.setStatus("Danger")
- .setFooter({ text: "https://clicksminuteper.github.io/policies/nucleus" })
)
.setTitle("Welcome")
.setDescription("General privacy information")
@@ -43,15 +43,16 @@
new EmojiEmbed()
.setTitle("Scanners")
.setDescription(
- "Nucleus uses [unscan](https://rapidapi.com/abcdan/api/unscan/) to scan links, images and files for malware and other threats.\n" +
- 'This service\'s [privacy policy](https://unscan.co/policies) is public, and they "do not store or sell your data."'
+ "Nucleus scans content sent by users for malware and NSFW content\n" +
+ 'Malware is detected using [ClamAV](https://clamav.net/), and the standard ClamAV database."\n' +
+ 'NSFW detection is provided by [NsfwJS](https://nsfwjs.com/), with a model provided by [GantMan](https://github.com/GantMan/nsfw_model/releases/tag/1.1.0)\n\n' +
+ 'All data is processed on our servers and is not processed by a 3rd party.'
)
.setEmoji("NUCLEUS.LOGO")
.setStatus("Danger")
- .setFooter({ text: "https://clicksminuteper.github.io/policies/nucleus" })
)
.setTitle("Scanners")
- .setDescription("About Unscan")
+ .setDescription("About Scanners")
.setPageId(1),
new Embed()
.setEmbed(
@@ -62,13 +63,12 @@
)
.setEmoji("NUCLEUS.LOGO")
.setStatus("Danger")
- .setFooter({ text: "https://clicksminuteper.github.io/policies/nucleus" })
)
.setTitle("Link scanning and Transcripts")
.setDescription("Information about how links and images are scanned, and transcripts are stored")
.setPageId(2)
].concat(
- (interaction.member as Discord.GuildMember).permissions.has("Administrator")
+ (interaction.member as Discord.GuildMember).id === interaction.guild!.ownerId
? [
new Embed()
.setEmbed(
@@ -77,7 +77,6 @@
.setDescription("Below are buttons for controlling this servers privacy settings")
.setEmoji("NUCLEUS.LOGO")
.setStatus("Danger")
- .setFooter({ text: "https://clicksminuteper.github.io/policies/nucleus" })
)
.setTitle("Options")
.setDescription("Options")
@@ -88,7 +87,6 @@
.setLabel("Clear all data")
.setCustomId("clear-all-data")
.setStyle(ButtonStyle.Danger)
- .setDisabled(!(interaction.user.id === interaction.guild!.ownerId))
])
])
]
diff --git a/src/commands/settings/automod.ts b/src/commands/settings/automod.ts
index b65eb4c..68454e4 100644
--- a/src/commands/settings/automod.ts
+++ b/src/commands/settings/automod.ts
@@ -28,6 +28,7 @@
import getEmojiByName from "../../utils/getEmojiByName.js";
import { modalInteractionCollector } from "../../utils/dualCollector.js";
import listToAndMore from "../../utils/listToAndMore.js";
+import _ from "lodash";
const command = (builder: SlashCommandSubcommandBuilder) =>
builder.setName("automod").setDescription("Setting for automatic moderation features");
@@ -157,6 +158,7 @@
const imageMenu = async (
interaction: StringSelectMenuInteraction,
m: Message,
+ unsavedChanges: boolean,
current: {
NSFW: boolean;
size: boolean;
@@ -186,7 +188,10 @@
.setTitle("Image Settings")
.setDescription(
`${emojiFromBoolean(current.NSFW)} **NSFW**\n` + `${emojiFromBoolean(current.size)} **Size**\n`
- );
+ )
+ .setFooter({
+ text: unsavedChanges ? "No changes made" : "Changes not saved"
+ });
await interaction.editReply({ embeds: [embed], components: [options] });
@@ -207,10 +212,12 @@
}
case "nsfw": {
current.NSFW = !current.NSFW;
+ unsavedChanges = true;
break;
}
case "size": {
current.size = !current.size;
+ unsavedChanges = true;
break;
}
}
@@ -221,6 +228,7 @@
const wordMenu = async (
interaction: StringSelectMenuInteraction,
m: Message,
+ unsavedChanges: boolean,
current: {
enabled: boolean;
words: { strict: string[]; loose: string[] };
@@ -296,7 +304,10 @@
)
)
.setStatus("Success")
- .setEmoji("GUILD.SETTINGS.GREEN");
+ .setEmoji("GUILD.SETTINGS.GREEN")
+ .setFooter({
+ text: unsavedChanges ? "No changes made" : "Changes not saved"
+ });
await interaction.editReply({ embeds: [embed], components: [selectMenu, buttons] });
@@ -320,6 +331,7 @@
}
case "enabled": {
current.enabled = !current.enabled;
+ unsavedChanges = true;
break;
}
}
@@ -391,6 +403,7 @@
.split(",")
.map((s) => s.trim())
.filter((s) => s.length > 0);
+ unsavedChanges = true;
break;
}
case "allowedUsers": {
@@ -402,6 +415,7 @@
"member",
"Word Filter"
);
+ unsavedChanges = true;
break;
}
case "allowedRoles": {
@@ -413,6 +427,7 @@
"role",
"Word Filter"
);
+ unsavedChanges = true;
break;
}
case "allowedChannels": {
@@ -424,6 +439,7 @@
"channel",
"Word Filter"
);
+ unsavedChanges = true;
break;
}
}
@@ -435,6 +451,7 @@
const inviteMenu = async (
interaction: StringSelectMenuInteraction,
m: Message,
+ unsavedChanges: boolean,
current: {
enabled: boolean;
allowed: { users: string[]; roles: string[]; channels: string[] };
@@ -503,7 +520,10 @@
)
)
.setStatus("Success")
- .setEmoji("GUILD.SETTINGS.GREEN");
+ .setEmoji("GUILD.SETTINGS.GREEN")
+ .setFooter({
+ text: unsavedChanges ? "No changes made" : "Changes not saved"
+ });
await interaction.editReply({ embeds: [embed], components: [menu, buttons] });
@@ -526,6 +546,7 @@
}
case "enabled": {
current.enabled = !current.enabled;
+ unsavedChanges = true;
break;
}
}
@@ -540,6 +561,7 @@
"member",
"Invite Settings"
);
+ unsavedChanges = true;
break;
}
case "roles": {
@@ -550,6 +572,7 @@
"role",
"Invite Settings"
);
+ unsavedChanges = true;
break;
}
case "channels": {
@@ -560,6 +583,7 @@
"channel",
"Invite Settings"
);
+ unsavedChanges = true;
break;
}
}
@@ -571,6 +595,7 @@
const mentionMenu = async (
interaction: StringSelectMenuInteraction,
m: Message,
+ unsavedChanges: boolean,
current: {
mass: number;
everyone: boolean;
@@ -690,7 +715,10 @@
}`
)
.setStatus("Success")
- .setEmoji("GUILD.SETTINGS.GREEN");
+ .setEmoji("GUILD.SETTINGS.GREEN")
+ .setFooter({
+ text: unsavedChanges ? "No changes made" : "Changes not saved"
+ });;
await interaction.editReply({ embeds: [embed], components: [menu, allowedMenu, buttons] });
@@ -714,10 +742,12 @@
}
case "everyone": {
current.everyone = !current.everyone;
+ unsavedChanges = true;
break;
}
case "roles": {
current.roles = !current.roles;
+ unsavedChanges = true;
break;
}
}
@@ -767,6 +797,7 @@
if (!out) break;
if (out.isButton()) break;
current.mass = parseInt(out.fields.getTextInputValue("mass"));
+ unsavedChanges = true;
break;
}
case "roles": {
@@ -778,6 +809,7 @@
"role",
"Mention Settings"
);
+ unsavedChanges = true;
break;
}
}
@@ -794,6 +826,7 @@
"member",
"Mention Settings"
);
+ unsavedChanges = true;
break;
}
case "roles": {
@@ -804,6 +837,7 @@
"role",
"Mention Settings"
);
+ unsavedChanges = true;
break;
}
case "channels": {
@@ -814,6 +848,7 @@
"channel",
"Mention Settings"
);
+ unsavedChanges = true;
break;
}
}
@@ -828,6 +863,7 @@
const cleanMenu = async (
interaction: StringSelectMenuInteraction,
m: Message,
+ unsavedChanges: boolean,
current?: {
channels?: string[];
allowed?: {
@@ -890,7 +926,10 @@
: "None"
}\n\n`
)
- .setStatus("Success");
+ .setStatus("Success")
+ .setFooter({
+ text: unsavedChanges ? "No changes made" : "Changes not saved"
+ });
await interaction.editReply({ embeds: [embed], components: [channelMenu, allowedMenu, buttons] });
@@ -958,6 +997,7 @@
}
}
}
+ unsavedChanges = true;
break;
}
case "allowed": {
@@ -970,6 +1010,7 @@
"member",
"Mention Settings"
);
+ unsavedChanges = true;
break;
}
case "roles": {
@@ -980,6 +1021,7 @@
"role",
"Mention Settings"
);
+ unsavedChanges = true;
break;
}
}
@@ -1001,15 +1043,16 @@
const callback = async (interaction: CommandInteraction): Promise<void> => {
if (!interaction.guild) return;
const m = await interaction.reply({ embeds: LoadingEmbed, fetchReply: true, ephemeral: true });
- const config = (await client.database.guilds.read(interaction.guild.id)).filters;
+ let config = (await client.database.guilds.read(interaction.guild.id)).filters;
let closed = false;
- const button = new ActionRowBuilder<ButtonBuilder>().addComponents(
- new ButtonBuilder().setCustomId("save").setLabel("Save").setStyle(ButtonStyle.Success)
- );
+ let current = _.cloneDeep(config);
do {
+ const button = new ActionRowBuilder<ButtonBuilder>().addComponents(
+ new ButtonBuilder().setCustomId("save").setLabel("Save").setStyle(ButtonStyle.Success).setDisabled(_.isEqual(config, current))
+ );
const selectMenu = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
new StringSelectMenuBuilder()
.setCustomId("filter")
@@ -1055,7 +1098,10 @@
`${emojiFromBoolean(config.clean.channels.length > 0)} **Clean**\n`
)
.setStatus("Success")
- .setEmoji("GUILD.SETTINGS.GREEN");
+ .setEmoji("GUILD.SETTINGS.GREEN")
+ .setFooter({
+ text: _.isEqual(config, current) ? "No changes made" : "Changes not saved"
+ });
await interaction.editReply({ embeds: [embed], components: [selectMenu, button] });
@@ -1069,41 +1115,37 @@
closed = true;
continue;
}
- if (i.isButton()) {
- await i.deferUpdate();
- await client.database.guilds.write(interaction.guild.id, { filters: config });
+ await i.deferUpdate();
+ if(i.isButton()) {
+ await client.database.guilds.write(interaction.guild.id, { filters: current });
await client.memory.forceUpdate(interaction.guild.id);
+ config = current;
+ current = _.cloneDeep(config);
} else {
switch (i.values[0]) {
case "invites": {
- await i.deferUpdate();
- config.invite = await inviteMenu(i, m, config.invite);
+ config.invite = await inviteMenu(i, m, _.isEqual(config, current), config.invite);
break;
}
case "mentions": {
- await i.deferUpdate();
- config.pings = await mentionMenu(i, m, config.pings);
+ config.pings = await mentionMenu(i, m, _.isEqual(config, current), config.pings);
break;
}
case "words": {
- await i.deferUpdate();
- config.wordFilter = await wordMenu(i, m, config.wordFilter);
+ config.wordFilter = await wordMenu(i, m, _.isEqual(config, current), config.wordFilter);
break;
}
case "malware": {
- await i.deferUpdate();
config.malware = !config.malware;
break;
}
case "images": {
- await i.deferUpdate();
- const next = await imageMenu(i, m, config.images);
+ const next = await imageMenu(i, m, _.isEqual(config, current), config.images);
config.images = next;
break;
}
case "clean": {
- await i.deferUpdate();
- const next = await cleanMenu(i, m, config.clean);
+ const next = await cleanMenu(i, m, _.isEqual(config, current), config.clean);
config.clean = next;
break;
}