diff --git a/src/commands/confess.ts b/src/commands/confess.ts index 0f903a5..4a30d0b 100644 --- a/src/commands/confess.ts +++ b/src/commands/confess.ts @@ -17,7 +17,11 @@ */ import { + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, CommandInteraction, + ComponentType, EmbedBuilder, SlashCommandBuilder, TextChannel @@ -27,6 +31,7 @@ import { dt } from "../main"; import { StoreMan } from "../storeman"; import getRandomColor from "../utils/getRandomColor"; import Logger from "../utils/Logger"; +import { submit } from "../modals"; const logger = new Logger("(/) confess"); @@ -41,6 +46,8 @@ export const data = new SlashCommandBuilder() ); export async function execute(interaction: CommandInteraction) { + // TODO: This all works as intended, but I'd like for it so be a reusable function + // instead because all of this is reused in src/main.ts:56 try { if (dt.isBanned(interaction.guild?.id!, interaction.user.id)) { return interaction.reply({ @@ -88,10 +95,27 @@ export async function execute(interaction: CommandInteraction) { } ); + const submitConfessionButton = new ButtonBuilder() + .setCustomId("submitConfession") + .setLabel("Submit a Confession") + .setStyle(ButtonStyle.Primary); + + const actionRow = new ActionRowBuilder() + .setComponents(submitConfessionButton); + const message = await ( BotClient.channels.cache.get(confessChannel!) as TextChannel ).send({ - embeds: [userConfessionEmbed] + embeds: [userConfessionEmbed], + components: [actionRow] + }); + + const collector = message.createMessageComponentCollector({ componentType: ComponentType.Button }); + + collector.on("collect", i => { + if (i.customId === "submitConfession") { + i.showModal(submit); + } }); await (BotClient.channels.cache.get(adminChannel!) as TextChannel).send({ @@ -106,6 +130,16 @@ export async function execute(interaction: CommandInteraction) { messageContent ); + const confessionsLength = dt.getGuildInfo(interaction.guild?.id!)?.confessions.length!; + + if (confessionsLength >= 2) { + await (BotClient.channels.cache.get(confessChannel!) as TextChannel).messages.fetch( + dt.getGuildInfo(interaction.guild?.id!)?.confessions[confessionsLength - 2].messageId! + ).then(message => { + message.edit({ components: [] }); + }); + } + return interaction.reply({ content: "Confession sent!", ephemeral: true diff --git a/src/main.ts b/src/main.ts index 3f25589..a40bc57 100644 --- a/src/main.ts +++ b/src/main.ts @@ -16,10 +16,13 @@ * along with this program. If not, see . */ +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CacheType, ComponentType, EmbedBuilder, Events, Interaction, ModalSubmitInteraction, TextChannel, } from "discord.js"; import { BotClient, BOT_TOKEN, deployCommands } from "./bot"; import { commands } from "./commands"; import { StoreMan } from "./storeman"; import Logger from "./utils/Logger"; +import getRandomColor from "./utils/getRandomColor"; +import { submit } from "./modals"; export const dt = new StoreMan(StoreMan.checkFile()); const logger = new Logger("Main"); @@ -28,16 +31,18 @@ BotClient.once("ready", client => { logger.log(`We're ready! Logged in as ${client.user.tag}`); }); +// Deploy the commands for a new guild BotClient.on("guildCreate", async guild => { await deployCommands({ guildId: guild.id }); }); +// Delete the data for a guild after it is removed BotClient.on("guildDelete", guild => { logger.log(`${guild.name} didn't want us anymore... :(`); dt.clearSettings(guild.id); }); -BotClient.on("interactionCreate", async interaction => { +BotClient.on(Events.InteractionCreate, async interaction => { if (!interaction.isCommand()) { return; } @@ -49,4 +54,113 @@ BotClient.on("interactionCreate", async interaction => { } }); +BotClient.on(Events.InteractionCreate, async interaction => { + if (!interaction.isModalSubmit()) { + return; + } + + if (interaction.customId === "submitConfession") { + const messageContent: string = interaction.fields.getTextInputValue("confessionInput"); + // const attachment: string = interaction.getTextInputValue("confessionAttachment"); + + try { + if (dt.isBanned(interaction.guild?.id!, interaction.user.id)) { + return interaction.reply({ + content: "You are banned from confessions in this server!", + ephemeral: true + }); + } + + if (!dt.getGuildInfo(interaction.guild?.id!)) { + return interaction.reply({ + content: + "The bot hasn't been set up yet! Ask the server admins to set it up.", + ephemeral: true + }); + } + + const confessChannel = dt.getGuildInfo(interaction.guild?.id!)?.settings + .confessChannel; + const adminChannel = dt.getGuildInfo(interaction.guild?.id!)?.settings + .modChannel; + + const color = getRandomColor(); + const messageId = StoreMan.genId(); + const userConfessionEmbed = new EmbedBuilder() + .setColor(color) + .setTitle(`Anonymous Confession \`${messageId}\``) + // @ts-ignore + .setDescription(messageContent); + + const adminConfessionEmbed = new EmbedBuilder() + .setColor(color) + .setTitle(`Anonymous Confession \`${messageId}\``) + // @ts-ignore + .setDescription(messageContent) + .addFields( + { + name: "Author", + value: interaction.user.displayName + }, + { + name: "Author ID", + value: interaction.user.id + } + ); + + const submitConfessionButton = new ButtonBuilder() + .setCustomId("submitConfession") + .setLabel("Submit a Confession") + .setStyle(ButtonStyle.Primary); + + const actionRow = new ActionRowBuilder() + .setComponents(submitConfessionButton); + + const message = await ( + BotClient.channels.cache.get(confessChannel!) as TextChannel + ).send({ + embeds: [userConfessionEmbed], + components: [actionRow] + }); + + const collector = message.createMessageComponentCollector({ componentType: ComponentType.Button }); + + collector.on("collect", i => { + if (i.customId === "submitConfession") { + i.showModal(submit); + } + }); + + await (BotClient.channels.cache.get(adminChannel!) as TextChannel).send({ + embeds: [adminConfessionEmbed] + }); + + dt.addConfession( + message, + messageId, + interaction.user.displayName, + interaction.user.id, + messageContent + ); + + const confessionsLength = dt.getGuildInfo(interaction.guild?.id!)?.confessions.length!; + + if (confessionsLength >= 2) { + await (BotClient.channels.cache.get(confessChannel!) as TextChannel).messages.fetch( + dt.getGuildInfo(interaction.guild?.id!)?.confessions[confessionsLength - 2].messageId! + ).then(message => { + message.edit({ components: [] }); + }); + } + + return interaction.reply({ + content: "Confession sent!", + ephemeral: true + }); + } catch (err) { + logger.error("An error occured:", err); + } + } +}) + BotClient.login(BOT_TOKEN); diff --git a/src/modals/index.ts b/src/modals/index.ts new file mode 100644 index 0000000..586df0a --- /dev/null +++ b/src/modals/index.ts @@ -0,0 +1,19 @@ +/* + * Confoss: Anonymous confessions for Discord, free as in freedom and price! + * Copyright (C) 2024 powermaker450 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +export * from "./submit"; diff --git a/src/modals/submit.ts b/src/modals/submit.ts new file mode 100644 index 0000000..4bb36cf --- /dev/null +++ b/src/modals/submit.ts @@ -0,0 +1,52 @@ +/* + * Confoss: Anonymous confessions for Discord, free as in freedom and price! + * Copyright (C) 2024 powermaker450 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { + ActionRowBuilder, + ModalActionRowComponentBuilder, + ModalBuilder, + TextInputBuilder, + TextInputStyle +} from "discord.js"; + +const submit = new ModalBuilder() + .setCustomId("submitConfession") + .setTitle("Submit Confession") + +const confessionInput = new TextInputBuilder() + .setCustomId("confessionInput") + .setLabel("Confession") + .setRequired(true) + .setMaxLength(2000) + .setStyle(TextInputStyle.Paragraph) + +// TODO: Add support for attachments +// +// const attachmentInput = new TextInputBuilder() +// .setCustomId("confessionAttachment") +// .setLabel("Attachment (optional)") +// .setRequired(false) +// .setStyle(TextInputStyle.Short) + +const actionRow = new ActionRowBuilder() + .addComponents(confessionInput); + // .addComponents(confessionInput, attachmentInput); + +submit.addComponents(actionRow); + +export { submit };