diff --git a/src/commands/utility/confess.ts b/src/commands/utility/confess.ts new file mode 100644 index 0000000..009cf7e --- /dev/null +++ b/src/commands/utility/confess.ts @@ -0,0 +1,94 @@ +import { Command } from '@sapphire/framework'; +import { + ActionRowBuilder, + ButtonBuilder, + ButtonStyle, + EmbedBuilder, + LabelBuilder, + MessageFlags, + ModalBuilder, + TextInputBuilder, + TextInputStyle, + TextChannel +} from 'discord.js'; +import { getSettings } from '#lib/settings.js'; +import { storeConfessionContext } from '#lib/confessions.js'; +import { emojis } from '#utils/emoji.js'; + +export class ConfessCommand extends Command { + public constructor(context: Command.LoaderContext, options: Command.Options) { + super(context, { ...options }); + } + + public override registerApplicationCommands(_registry: Command.Registry) { + // Registration is handled per-guild at runtime in the Ready listener + } + + public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { + if (!interaction.inGuild() || !interaction.guild) { + await interaction.reply({ content: `${emojis.rightArrow2} This command can only be used in a server.`, flags: MessageFlags.Ephemeral }); + return; + } + + const settings = await getSettings(interaction.guild.id, interaction.guild.name); + + if (!settings.confessionChannelId || !settings.confessionEnabled) { + await interaction.reply({ content: `${emojis.rightArrow2} Confessions are not enabled or configured for this server.`, flags: MessageFlags.Ephemeral }); + return; + } + + const confessionInput = new TextInputBuilder() + .setCustomId('confession-text') + .setStyle(TextInputStyle.Paragraph) + .setRequired(true) + .setMaxLength(1_000); + + const confessionLabel = new LabelBuilder().setLabel('Confession').setTextInputComponent(confessionInput); + + const modal = new ModalBuilder().setCustomId('create-confession-modal').setTitle('Create Confession').addLabelComponents(confessionLabel); + + await interaction.showModal(modal); + + let modalSubmit; + + try { + modalSubmit = await interaction.awaitModalSubmit({ + filter: (m) => m.customId === 'create-confession-modal' && m.user.id === interaction.user.id, + time: 60_000 + }); + } catch { + return; + } + + const confession = modalSubmit.fields.getTextInputValue('confession-text'); + + const confessionChannel = await interaction.guild.channels.fetch(settings.confessionChannelId).catch(() => null); + + if (!(confessionChannel instanceof TextChannel)) { + await modalSubmit.reply({ content: `${emojis.rightArrow2} The configured confession channel is unavailable.`, flags: MessageFlags.Ephemeral }); + return; + } + + const embed = new EmbedBuilder().setTitle('Confession').setDescription(confession).setTimestamp(); + + const message = await confessionChannel.send({ embeds: [embed] }); + + let thread; + + try { + thread = await message.startThread({ name: `confession-${message.id}` }); + } catch (error) { + await message.delete().catch(() => null); + throw error; + } + + const reportButton = new ButtonBuilder().setCustomId(`report-confession:${message.id}`).setLabel('Report').setStyle(ButtonStyle.Danger); + const row = new ActionRowBuilder().addComponents(reportButton); + + await message.edit({ components: [row] }); + + storeConfessionContext({ guildId: interaction.guild.id, channelId: confessionChannel.id, messageId: message.id, threadId: thread.id }); + + await modalSubmit.reply({ content: `${emojis.rightArrow2} Confession sent.`, flags: MessageFlags.Ephemeral }); + } +} diff --git a/src/commands/utility/settings.ts b/src/commands/utility/settings.ts index 3b80149..c1df301 100644 --- a/src/commands/utility/settings.ts +++ b/src/commands/utility/settings.ts @@ -145,6 +145,42 @@ function buildLoggingPanel(settings: ServerSettings, guild: Guild, status?: stri }; } +function buildConfessionPanel(settings: ServerSettings, guild: Guild, status?: string) { + const currentChannelName = settings.confessionChannelId + ? guild.channels.cache.get(settings.confessionChannelId)?.name + : null; + + const toggleMenu = new StringSelectMenuBuilder() + .setCustomId('confessionToggle') + .setPlaceholder(`${settings.confessionEnabled ? 'Enabled' : 'Disabled'}`) + .addOptions( + new StringSelectMenuOptionBuilder() + .setLabel('Enable') + .setDescription('Enable confessions for this server.') + .setValue('enable'), + new StringSelectMenuOptionBuilder() + .setLabel('Disable') + .setDescription('Disable confessions for this server.') + .setValue('disable') + ); + + const channelMenu = new ChannelSelectMenuBuilder() + .setCustomId('confessionChannel') + .setPlaceholder(currentChannelName ? `#${currentChannelName}` : 'Select a channel for confessions') + .setChannelTypes(ChannelType.GuildText); + + return { + content: status + ? `${emojis.rightArrow1} **Confessions** module: +${emojis.rightArrow2} ${status}` + : `${emojis.rightArrow1} **Confessions** module:`, + components: [ + new ActionRowBuilder().addComponents(toggleMenu), + new ActionRowBuilder().addComponents(channelMenu) + ] + }; +} + async function normalizeTicketSettings(guildId: string, guild: Guild, settings: ServerSettings) { if (!settings.ticketCategoryId) return settings; @@ -200,6 +236,11 @@ export class SettingsCommand extends Command { .setLabel('Logging') .setDescription('Configure server channel logging.') .setValue('logging') + , + new StringSelectMenuOptionBuilder() + .setLabel('Confessions') + .setDescription('Configure where confessions are posted and whether they are enabled.') + .setValue('confessions') ); const response = await interaction.reply({ @@ -248,6 +289,8 @@ export class SettingsCommand extends Command { await settingChoice.update(buildTicketPanel(settings, guild)); } else if (settingChoice.values[0] === 'logging') { await settingChoice.update(buildLoggingPanel(settings, guild)); + } else if (settingChoice.values[0] === 'confessions') { + await settingChoice.update(buildConfessionPanel(settings, guild)); } else { return; } @@ -300,6 +343,16 @@ export class SettingsCommand extends Command { const next = await updateSettings(guildId, guild.name, { loggingChannelId: channelId }); await i.update(buildLoggingPanel(next, guild, `Logging channel set to <#${channelId}>.`)); + } else if (i.customId === 'confessionToggle' && i.isStringSelectMenu()) { + const enable = i.values[0] === 'enable'; + const next = await updateSettings(guildId, guild.name, { confessionEnabled: enable }); + + await i.update(buildConfessionPanel(next, guild, `Confessions **${enable ? 'enabled' : 'disabled'}**.`)); + } else if (i.customId === 'confessionChannel' && i.isChannelSelectMenu()) { + const channelId = i.values[0]; + const next = await updateSettings(guildId, guild.name, { confessionChannelId: channelId }); + + await i.update(buildConfessionPanel(next, guild, `Confession channel set to <#${channelId}>.`)); } }); diff --git a/src/commands/utility/setupConfessions.ts b/src/commands/utility/setupConfessions.ts deleted file mode 100644 index c25eb17..0000000 --- a/src/commands/utility/setupConfessions.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { Command } from '@sapphire/framework'; -import { emojis } from '#utils/emoji.js'; -import { - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, - ChannelType, - MessageFlags, - PermissionFlagsBits -} from 'discord.js'; -import { updateSettings } from '#lib/settings.js'; - -export class SetupConfessionsCommand extends Command { - public constructor(context: Command.LoaderContext, options: Command.Options) { - super(context, { ...options }); - } - - public override registerApplicationCommands(registry: Command.Registry) { - registry.registerChatInputCommand((builder) => - builder - .setName('setup-confessions') - .setDescription('Post the confession panel in a channel.') - .setDefaultMemberPermissions(PermissionFlagsBits.Administrator) - .addChannelOption((option) => - option - .setName('panel-channel') - .setDescription('The channel where the confession panel should be posted') - .addChannelTypes(ChannelType.GuildText) - .setRequired(true) - ) - .addChannelOption((option) => - option - .setName('confession-channel') - .setDescription('The channel where confessions should be sent') - .addChannelTypes(ChannelType.GuildText) - .setRequired(true) - ) - ); - } - - public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { - if (!interaction.guild) { - await interaction.reply({ - content: `${emojis.rightArrow2} This command can only be used in a server.`, - flags: MessageFlags.Ephemeral - }); - return; - } - - const panelChannel = interaction.options.getChannel('panel-channel', true, [ChannelType.GuildText]); - const confessionChannel = interaction.options.getChannel('confession-channel', true, [ChannelType.GuildText]); - - const button = new ButtonBuilder() - .setCustomId('create-confession') - .setLabel('Create Confession') - .setStyle(ButtonStyle.Success); - - const row = new ActionRowBuilder().addComponents(button); - - await panelChannel.send({ - content: `**Create a confession by clicking the button below!**`, - components: [row] - }); - - await updateSettings(interaction.guild.id, interaction.guild.name, { - confessionChannelId: confessionChannel.id - }); - - await interaction.reply({ - content: `${emojis.rightArrow2} Confession panel sent in ${panelChannel}. Confessions will be posted in ${confessionChannel}.`, - flags: MessageFlags.Ephemeral - }); - } -} \ No newline at end of file diff --git a/src/lib/settings.ts b/src/lib/settings.ts index 9323532..69c8e61 100644 --- a/src/lib/settings.ts +++ b/src/lib/settings.ts @@ -7,6 +7,7 @@ export type ServerSettings = { ticketCategoryId: string | null; staffRole: string | null; confessionChannelId: string | null; + confessionEnabled: boolean; loggingEnabled?: boolean; loggingChannelId?: string | null; }; @@ -17,6 +18,7 @@ export const DefaultSettings: ServerSettings = { ticketCategoryId: null, staffRole: null, confessionChannelId: null, + confessionEnabled: false, loggingEnabled: false, loggingChannelId: null, }; diff --git a/src/listeners/ready.ts b/src/listeners/ready.ts index c810cdc..7c05ef5 100644 --- a/src/listeners/ready.ts +++ b/src/listeners/ready.ts @@ -19,7 +19,7 @@ export class ReadyListener extends Listener { console.log(`Ready! Logged in as ${client.user.tag}`); client.user.setActivity({ - name: `🤐 /setup-confessions | Shard ${client.shard?.ids?.[0] ?? 0}`, + name: `🤐 Confessions! | Shard ${client.shard?.ids?.[0] ?? 0}`, type: ActivityType.Custom });