import { LoadingEmbed } from "../utils/defaults.js"; import Discord, { SlashCommandBuilder, CommandInteraction, ActionRowBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuOptionBuilder, SelectMenuOptionBuilder, StringSelectMenuBuilder } from "discord.js"; import EmojiEmbed from "../utils/generateEmojiEmbed.js"; import getEmojiByName from "../utils/getEmojiByName.js"; import createPageIndicator from "../utils/createPageIndicator.js"; import client from "../utils/client.js"; import confirmationMessage from "../utils/confirmationMessage.js"; import { Embed } from "../utils/defaults.js"; const command = new SlashCommandBuilder() .setName("privacy") .setDescription("Information and options for you and your server's settings"); const callback = async (interaction: CommandInteraction): Promise => { const pages = [ new Embed() .setEmbed( new EmojiEmbed() .setTitle("Nucleus Privacy") .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)." + "\n\n[[Privacy Policy]](https://clicksminuteper.github.io/policies/nucleus) | [[Terms of Service]](https://clicksminuteper.github.io/policies/nucleustos)" ) .setEmoji("NUCLEUS.LOGO") .setStatus("Danger") ) .setTitle("Welcome") .setDescription("General privacy information") .setPageId(0), new Embed() .setEmbed( new EmojiEmbed() .setTitle("Scanners") .setDescription( "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") ) .setTitle("Scanners") .setDescription("About Scanners") .setPageId(1), new Embed() .setEmbed( new EmojiEmbed() .setTitle("Link scanning and Transcripts") .setDescription( "Transcripts allow you to store all messages sent in a channel. Transcripts are stored in our database along with the rest of the server's 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") ) .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).id === interaction.guild!.ownerId ? [ 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({ embeds: LoadingEmbed, fetchReply: true, ephemeral: true }); let page = parseInt( client.preloadPage[interaction.channel!.id] ? client.preloadPage[interaction.channel!.id]?.argument! : "0" ); let selectPaneOpen = false; let nextFooter = null; let timedOut = false; while (!timedOut) { let selectPane: Discord.ActionRowBuilder[] = []; if (selectPaneOpen) { const options: SelectMenuOptionBuilder[] = []; pages.forEach((embed) => { options.push( new StringSelectMenuOptionBuilder({ label: embed.title, value: embed.pageId.toString(), description: embed.description || "" }) ); }); selectPane = [ new ActionRowBuilder().addComponents([ new Discord.StringSelectMenuBuilder() .addOptions(options) .setCustomId("page") .setMaxValues(1) .setPlaceholder("Choose a page...") ]) ]; } const components: ActionRowBuilder[] = selectPane.concat([ new ActionRowBuilder().addComponents( new ButtonBuilder() .setCustomId("left") .setEmoji(getEmojiByName("CONTROL.LEFT", "id")) .setStyle(ButtonStyle.Secondary) .setDisabled(page === 0), new ButtonBuilder() .setCustomId("select") .setEmoji(getEmojiByName("CONTROL.MENU", "id")) .setStyle(selectPaneOpen ? ButtonStyle.Primary : ButtonStyle.Secondary) .setDisabled(false), new ButtonBuilder() .setCustomId("right") .setEmoji(getEmojiByName("CONTROL.RIGHT", "id")) .setStyle(ButtonStyle.Secondary) .setDisabled(page === pages.length - 1) ) ]); const em = new Discord.EmbedBuilder(pages[page]!.embed); em.setDescription(em.data.description + "\n\n" + createPageIndicator(pages.length, page)); if (nextFooter) em.setFooter({ text: nextFooter }); await interaction.editReply({ embeds: [em], components: components.concat(pages[page]?.componentsToSet ?? []) }); let i; 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) { timedOut = true; continue; } nextFooter = null; await i.deferUpdate(); if (i.customId === "left") { if (page > 0) page--; selectPaneOpen = false; } else if (i.customId === "right") { if (page < pages.length - 1) page++; selectPaneOpen = false; } else if (i.customId === "select") { selectPaneOpen = !selectPaneOpen; } else if (i.customId === "page" && i.isStringSelectMenu()) { page = parseInt(i.values[0]!); selectPaneOpen = false; } else if (i.customId === "clear-all-data") { const confirmation = await new confirmationMessage(interaction) .setEmoji("CONTROL.BLOCKCROSS") .setTitle("Clear All Data") .setDescription( "Are you sure you want to delete all data on this server? This includes your settings and all punishment histories.\n\n" + "**This cannot be undone.**" ) .setColor("Danger") .send(true); if (confirmation.cancelled) { continue; } if (confirmation.success) { await client.database.guilds.delete(interaction.guild!.id); await client.database.history.delete(interaction.guild!.id); await client.database.notes.delete(interaction.guild!.id); await client.database.transcripts.deleteAll(interaction.guild!.id); nextFooter = "All data cleared"; continue; } else { nextFooter = "No changes were made"; continue; } } else { const em = new Discord.EmbedBuilder(pages[page]!.embed); em.setDescription(em.data.description + "\n\n" + createPageIndicator(pages.length, page)); em.setFooter({ text: "Message closed" }); await interaction.editReply({ embeds: [em], components: [] }); return; } } const em = new Discord.EmbedBuilder(pages[page]!.embed); em.setDescription(em.data.description + "\n\n" + createPageIndicator(pages.length, page)); em.setFooter({ text: "Message timed out" }); await interaction.editReply({ embeds: [em], components: [] }); }; export { command }; export { callback };