const { SlashCommandBuilder, ActionRowBuilder, StringSelectMenuBuilder, ChannelType, AutoModerationActionType } = require('discord.js'); const SlashBuild = require('../../utils/slash'); const embed = require('../../utils/embed'); const utils = require('../../utils/utils'); module.exports = new SlashBuild( new SlashCommandBuilder() .setName('automod') .setDescription('Setup, edit, or delete AutoMod rules') .addSubcommand(subcommand => subcommand.setName('setup') .setDescription('Setup AutoMod system')) .addSubcommand(subcommand => subcommand.setName('edit') .setDescription('Edit an existing AutoMod rule')) .addSubcommand(subcommand => subcommand.setName('delete') .setDescription('Delete an existing AutoMod rule')), async (interaction) => { const { guild, options } = interaction; const sub = options.getSubcommand(); switch (sub) { case 'setup': const setupEmbed = embed.infos({ content: 'Select the type of rule you want to setup.' }); const ruleSelectMenu = new StringSelectMenuBuilder() .setCustomId('select-rule') .setPlaceholder('Select a rule type') .addOptions([ { label: 'Flagged Words', value: 'flagged-words' }, { label: 'Spam Messages', value: 'spam-messages' }, { label: 'Mention Spam', value: 'mention-spam' }, { label: 'Keyword', value: 'keyword' } ]); const actionRow = new ActionRowBuilder().addComponents(ruleSelectMenu); await utils.editOrReply(interaction, { embeds: [setupEmbed], components: [actionRow] }); const filter = i => i.customId === 'select-rule' && i.user.id === interaction.user.id; const collector = interaction.channel.createMessageComponentCollector({ filter, time: 120000 }); collector.on('collect', async i => { const selectedRule = i.values[0]; await i.deferUpdate(); if (selectedRule === 'mention-spam' || selectedRule === 'keyword') { await promptForTimeout(interaction, selectedRule); } else { await promptForLogChannel(interaction, selectedRule, 0); } }); break; case 'edit': const editEmbed = embed.infos({ content: 'Please enter the ID of the rule you want to edit.' }); await utils.editOrReply(interaction, { embeds: [editEmbed], components: [] }); const filterEdit = response => response.author.id === interaction.user.id; const collectedEdit = await interaction.channel.awaitMessages({ filter: filterEdit, max: 1, time: 120000, errors: ['time'] }).catch(() => null); if (collectedEdit && collectedEdit.first()) { const ruleId = collectedEdit.first().content; await promptForEditOptions(interaction, ruleId); } else { await utils.editOrReply(interaction, { embeds: [embed.error({ content: 'No input received within the time limit.' })] }); } break; case 'delete': const deleteEmbed = embed.infos({ content: 'Please enter the ID of the rule you want to delete.' }); await utils.editOrReply(interaction, { embeds: [deleteEmbed], components: [] }); const filterDelete = response => response.author.id === interaction.user.id; const collectedDelete = await interaction.channel.awaitMessages({ filter: filterDelete, max: 1, time: 120000, errors: ['time'] }).catch(() => null); if (collectedDelete && collectedDelete.first()) { const ruleId = collectedDelete.first().content; await deleteRule(interaction, ruleId); } else { await utils.editOrReply(interaction, { embeds: [embed.error({ content: 'No input received within the time limit.' })] }); } break; } }, { permissions: { user: ['ManageGuild'], client: ['ManageGuild'] } } ); async function promptForTimeout(interaction, ruleType) { const timeoutEmbed = embed.infos({ content: 'Please select the timeout duration.' }); const timeoutSelectMenu = new StringSelectMenuBuilder() .setCustomId('select-timeout') .setPlaceholder('Select a timeout duration') .addOptions([ { label: 'No timeout', value: '0' }, { label: '60 seconds', value: '60' }, { label: '5 minutes', value: '300' }, { label: '10 minutes', value: '600' }, { label: '1 hour', value: '3600' }, { label: '1 day', value: '86400' }, { label: '1 week', value: '604800' } ]); const actionRow = new ActionRowBuilder().addComponents(timeoutSelectMenu); await utils.editOrReply(interaction, { embeds: [timeoutEmbed], components: [actionRow] }); const filter = i => i.customId === 'select-timeout' && i.user.id === interaction.user.id; const collector = interaction.channel.createMessageComponentCollector({ filter, time: 120000 }); collector.on('collect', async i => { const timeout = parseInt(i.values[0], 10); await i.deferUpdate(); await promptForLogChannel(interaction, ruleType, timeout); }); } async function promptForLogChannel(interaction, ruleType, timeout) { const logChannelEmbed = embed.infos({ content: 'Please select a log channel for AutoMod actions.' }); const channelSelectMenu = new StringSelectMenuBuilder() .setCustomId('select-log-channel') .setPlaceholder('Select a log channel') .addOptions( interaction.guild.channels.cache .filter(channel => channel.type === ChannelType.GuildText) .map(channel => ({ label: channel.name, value: channel.id })) ); const actionRow = new ActionRowBuilder().addComponents(channelSelectMenu); await utils.editOrReply(interaction, { embeds: [logChannelEmbed], components: [actionRow] }); const filter = i => i.customId === 'select-log-channel' && i.user.id === interaction.user.id; const collector = interaction.channel.createMessageComponentCollector({ filter, time: 120000 }); collector.on('collect', async i => { const selectedChannel = i.values[0]; await i.deferUpdate(); switch (ruleType) { case 'flagged-words': await setupFlaggedWordsRule(interaction, selectedChannel); break; case 'spam-messages': await setupSpamMessagesRule(interaction, selectedChannel); break; case 'mention-spam': await promptForMentionSpamOptions(interaction, timeout, selectedChannel); break; case 'keyword': await promptForKeywordOptions(interaction, timeout, selectedChannel); break; } }); } async function setupFlaggedWordsRule(interaction, logChannel) { console.log('Creating flagged words rule.'); try { const rule = await interaction.guild.autoModerationRules.create({ name: `Block profanity, sexual content, and slurs by Atoms' AutoMod.`, creatorId: interaction.user.id, enabled: true, eventType: 1, triggerType: 4, triggerMetadata: { presets: [1, 2, 3] }, actions: [ { type: AutoModerationActionType.BlockMessage, metadata: { customMessage: `This message was prevented by Atoms' AutoMod.` } }, { type: AutoModerationActionType.SendAlertMessage, metadata: { channel: logChannel } } ] }); if (rule) { await utils.editOrReply(interaction, { embeds: [embed.success({ content: 'AutoMod rule successfully created.' })], components: [] }); } } catch (err) { if (err.code === 50035) { await utils.editOrReply(interaction, { embeds: [embed.error({ content: 'The same AutoMod rule already exists. If you wish to edit it, use the command `/automod edit`.' })] }); } else { console.log(err); await utils.editOrReply(interaction, { embeds: [embed.error({ content: 'An unknown error occurred while creating the AutoMod rule.' })] }); } } } async function setupSpamMessagesRule(interaction, logChannel) { console.log('Creating spam messages rule.'); try { const rule = await interaction.guild.autoModerationRules.create({ name: `Prevent spam messages by Atoms' AutoMod.`, creatorId: interaction.user.id, enabled: true, eventType: 1, triggerType: 3, actions: [ { type: AutoModerationActionType.BlockMessage }, { type: AutoModerationActionType.SendAlertMessage, metadata: { channel: logChannel } } ] }); if (rule) { await utils.editOrReply(interaction, { embeds: [embed.success({ content: 'AutoMod rule successfully created.' })], components: [] }); } } catch (err) { if (err.code === 50035) { await utils.editOrReply(interaction, { embeds: [embed.error({ content: 'The same AutoMod rule already exists. If you wish to edit it, use the command `/automod edit`.' })] }); } else { console.log(err); await utils.editOrReply(interaction, { embeds: [embed.error({ content: 'An unknown error occurred while creating the AutoMod rule.' })] }); } } } async function promptForMentionSpamOptions(interaction, timeout, logChannel) { const mentionLimitEmbed = embed.infos({ content: 'Please enter the maximum number of mentions allowed per message.' }); await utils.editOrReply(interaction, { embeds: [mentionLimitEmbed], components: [] }); const filter = response => response.author.id === interaction.user.id; const collected = await interaction.channel.awaitMessages({ filter, max: 1, time: 120000, errors: ['time'] }).catch(() => null); if (collected && collected.first()) { const mentionLimit = parseInt(collected.first().content, 10); if (isNaN(mentionLimit)) { await utils.editOrReply(interaction, { embeds: [embed.error({ content: 'Invalid input. Please enter a valid number for the mention limit.' })] }); return; } await setupMentionSpamRule(interaction, timeout, mentionLimit, logChannel); } else { await utils.editOrReply(interaction, { embeds: [embed.error({ content: 'No input received within the time limit.' })] }); } } async function setupMentionSpamRule(interaction, timeout, mentionLimit, logChannel) { console.log('Creating mention spam rule.'); try { const rule = await interaction.guild.autoModerationRules.create({ name: `Prevent mention spam by Atoms' AutoMod.`, creatorId: interaction.user.id, enabled: true, eventType: 1, triggerType: 5, triggerMetadata: { mentionTotalLimit: mentionLimit }, actions: [ { type: AutoModerationActionType.BlockMessage }, ...(timeout > 0 ? [{ type: AutoModerationActionType.Timeout, metadata: { durationSeconds: timeout } }] : []), { type: AutoModerationActionType.SendAlertMessage, metadata: { channel: logChannel } } ] }); if (rule) { await utils.editOrReply(interaction, { embeds: [embed.success({ content: 'AutoMod rule successfully created.' })], components: [] }); } } catch (err) { if (err.code === 50035) { await utils.editOrReply(interaction, { embeds: [embed.error({ content: 'The same AutoMod rule already exists. If you wish to edit it, use the command `/automod edit`.' })] }); } else { console.log(err); await utils.editOrReply(interaction, { embeds: [embed.error({ content: 'An unknown error occurred while creating the AutoMod rule.' })] }); } } } async function promptForKeywordOptions(interaction, timeout, logChannel) { const keywordEmbed = embed.infos({ content: 'Please enter the keywords to block, separated by commas.' }); await utils.editOrReply(interaction, { embeds: [keywordEmbed], components: [] }); const filter = response => response.author.id === interaction.user.id; const collected = await interaction.channel.awaitMessages({ filter, max: 1, time: 120000, errors: ['time'] }).catch(() => null); if (collected && collected.first()) { const keywords = collected.first().content.split(',').map(keyword => keyword.trim()).filter(keyword => keyword.length > 0); if (keywords.length === 0) { await utils.editOrReply(interaction, { embeds: [embed.error({ content: 'Invalid input. Please enter at least one keyword to block.' })] }); return; } await setupKeywordRule(interaction, timeout, keywords, logChannel); } else { await utils.editOrReply(interaction, { embeds: [embed.error({ content: 'No input received within the time limit.' })] }); } } async function setupKeywordRule(interaction, timeout, keywords, logChannel) { console.log('Creating keyword rule.'); try { const rule = await interaction.guild.autoModerationRules.create({ name: `Block specific keywords by Atoms' AutoMod.`, creatorId: interaction.user.id, enabled: true, eventType: 1, triggerType: 1, triggerMetadata: { keywordFilter: keywords }, actions: [ { type: AutoModerationActionType.BlockMessage }, ...(timeout > 0 ? [{ type: AutoModerationActionType.Timeout, metadata: { durationSeconds: timeout } }] : []), { type: AutoModerationActionType.SendAlertMessage, metadata: { channel: logChannel } } ] }); if (rule) { await utils.editOrReply(interaction, { embeds: [embed.success({ content: 'AutoMod rule successfully created.' })], components: [] }); } } catch (err) { if (err.code === 50035) { await utils.editOrReply(interaction, { embeds: [embed.error({ content: 'The same AutoMod rule already exists. If you wish to edit it, use the command `/automod edit`.' })] }); } else { console.log(err); await utils.editOrReply(interaction, { embeds: [embed.error({ content: 'An unknown error occurred while creating the AutoMod rule.' })] }); } } } async function promptForEditOptions(interaction, ruleId) { const rule = await interaction.guild.autoModerationRules.fetch(ruleId).catch(async err => { console.log(err); await utils.editOrReply(interaction, { embeds: [embed.error({ content: 'An error occurred while fetching the AutoMod rule.' })] }); }); if (!rule) return; let options = []; switch (rule.triggerType) { case 1: // Keyword Rule options = [ { label: 'Edit Timeout', value: 'edit-timeout' }, { label: 'Edit Log Channel', value: 'edit-log-channel' }, { label: 'Edit Keyword', value: 'edit-keyword' } ]; break; case 5: // Mention Spam Rule options = [ { label: 'Edit Timeout', value: 'edit-timeout' }, { label: 'Edit Log Channel', value: 'edit-log-channel' }, { label: 'Edit Mention Limit', value: 'edit-mention-limit' } ]; break; case 4: // Flagged Words Rule options = [ { label: 'Edit Log Channel', value: 'edit-log-channel' } ]; break; case 3: // Spam Messages Rule options = [ { label: 'Edit Log Channel', value: 'edit-log-channel' } ]; break; } async function promptForEditOptions(interaction, ruleId) { const rule = await interaction.guild.autoModerationRules.fetch(ruleId).catch(async err => { console.log(err); await utils.editOrReply(interaction, { embeds: [embed.error({ content: 'An error occurred while fetching the AutoMod rule.' })] }); }); if (!rule) return; let options = []; switch (rule.triggerType) { case 1: // Keyword Rule options = [ { label: 'Edit Timeout', value: 'edit-timeout' }, { label: 'Edit Log Channel', value: 'edit-log-channel' }, { label: 'Edit Keyword', value: 'edit-keyword' } ]; break; case 5: // Mention Spam Rule options = [ { label: 'Edit Timeout', value: 'edit-timeout' }, { label: 'Edit Log Channel', value: 'edit-log-channel' }, { label: 'Edit Mention Limit', value: 'edit-mention-limit' } ]; break; case 4: // Flagged Words Rule options = [ { label: 'Edit Log Channel', value: 'edit-log-channel' } ]; break; case 3: // Spam Messages Rule options = [ { label: 'Edit Log Channel', value: 'edit-log-channel' } ]; break; } const editOptionsEmbed = embed.infos({ content: 'Please select the parameter you want to edit.' }); const selectMenu = new StringSelectMenuBuilder() .setCustomId('select-edit-option') .setPlaceholder('Select an option') .addOptions(options); const actionRow = new ActionRowBuilder().addComponents(selectMenu); await utils.editOrReply(interaction, { embeds: [editOptionsEmbed], components: [actionRow] }); const filter = i => i.customId === 'select-edit-option' && i.user.id === interaction.user.id; const collector = interaction.channel.createMessageComponentCollector({ filter, time: 60000 }); collector.on('collect', async i => { const selectedOption = i.values[0]; await i.deferUpdate(); switch (selectedOption) { case 'edit-timeout': await promptForTimeoutEdit(interaction, rule); break; case 'edit-log-channel': await promptForLogChannelEdit(interaction, rule); break; case 'edit-keyword': await promptForKeywordEdit(interaction, rule); break; case 'edit-mention-limit': await promptForMentionLimitEdit(interaction, rule); break; } }); } async function promptForTimeoutEdit(interaction, rule) { const timeoutEmbed = embed.infos({ content: 'Please select the new timeout duration or keep the current one.' }); const timeoutSelectMenu = new StringSelectMenuBuilder() .setCustomId('select-new-timeout') .setPlaceholder('Select a new timeout duration') .addOptions([ { label: 'Keep current timeout', value: 'keep' }, { label: 'No timeout', value: '0' }, { label: '60 seconds', value: '60' }, { label: '5 minutes', value: '300' }, { label: '10 minutes', value: '600' }, { label: '1 hour', value: '3600' }, { label: '1 day', value: '86400' }, { label: '1 week', value: '604800' } ]); const actionRow = new ActionRowBuilder().addComponents(timeoutSelectMenu); await utils.editOrReply(interaction, { embeds: [timeoutEmbed], components: [actionRow] }); const filter = i => i.customId === 'select-new-timeout' && i.user.id === interaction.user.id; const collector = interaction.channel.createMessageComponentCollector({ filter, time: 60000 }); collector.on('collect', async i => { const newTimeout = i.values[0]; await i.deferUpdate(); if (newTimeout !== 'keep') { await rule.edit({ actions: rule.actions.map(action => action.type === AutoModerationActionType.Timeout ? { ...action, metadata: { durationSeconds: parseInt(newTimeout, 10) } } : action ) }); } await utils.editOrReply(interaction, { embeds: [embed.success({ content: 'Timeout successfully updated.' })] }); }); } async function promptForLogChannelEdit(interaction, rule) { const logChannelEmbed = embed.infos({ content: 'Please select a new log channel or keep the current one.' }); const channelSelectMenu = new StringSelectMenuBuilder() .setCustomId('select-new-log-channel') .setPlaceholder('Select a new log channel') .addOptions([ { label: 'Keep current log channel', value: 'keep' }, ...interaction.guild.channels.cache .filter(channel => channel.type === ChannelType.GuildText) .map(channel => ({ label: channel.name, value: channel.id })) ]); const actionRow = new ActionRowBuilder().addComponents(channelSelectMenu); await utils.editOrReply(interaction, { embeds: [logChannelEmbed], components: [actionRow] }); const filter = i => i.customId === 'select-new-log-channel' && i.user.id === interaction.user.id; const collector = interaction.channel.createMessageComponentCollector({ filter, time: 60000 }); collector.on('collect', async i => { const newLogChannel = i.values[0]; await i.deferUpdate(); if (newLogChannel !== 'keep') { await rule.edit({ actions: rule.actions.map(action => action.type === AutoModerationActionType.SendAlertMessage ? { ...action, metadata: { channel: newLogChannel } } : action ) }); } await utils.editOrReply(interaction, { embeds: [embed.success({ content: 'Log channel successfully updated.' })] }); }); } async function promptForKeywordEdit(interaction, rule) { const keywordEmbed = embed.infos({ content: 'Please enter the new keyword or keep the current one.' }); await utils.editOrReply(interaction, { embeds: [keywordEmbed], components: [] }); const filter = response => response.author.id === interaction.user.id; const collected = await interaction.channel.awaitMessages({ filter, max: 1, time: 60000, errors: ['time'] }).catch(() => null); if (collected && collected.first()) { const newKeyword = collected.first().content; if (newKeyword !== 'keep') { await rule.edit({ triggerMetadata: { keywordFilter: [newKeyword] } }); } await utils.editOrReply(interaction, { embeds: [embed.success({ content: 'Keyword successfully updated.' })] }); } else { await utils.editOrReply(interaction, { embeds: [embed.error({ content: 'No input received within the time limit.' })] }); } } async function promptForMentionLimitEdit(interaction, rule) { const mentionLimitEmbed = embed.infos({ content: 'Please enter the new mention limit (1-50) or keep the current one.' }); await utils.editOrReply(interaction, { embeds: [mentionLimitEmbed], components: [] }); const filter = response => response.author.id === interaction.user.id; const collected = await interaction.channel.awaitMessages({ filter, max: 1, time: 60000, errors: ['time'] }).catch(() => null); if (collected && collected.first()) { const newMentionLimit = collected.first().content; if (newMentionLimit !== 'keep') { const number = parseInt(newMentionLimit, 10); if (isNaN(number) || number < 1 || number > 50) { await utils.editOrReply(interaction, { embeds: [embed.error({ content: 'Invalid number of mentions provided (must be between 1 and 50).' })] }); } else { await rule.edit({ triggerMetadata: { mentionTotalLimit: number } }); } } await utils.editOrReply(interaction, { embeds: [embed.success({ content: 'Mention limit successfully updated.' })] }); } else { await utils.editOrReply(interaction, { embeds: [embed.error({ content: 'No input received within the time limit.' })] }); } } async function deleteRule(interaction, ruleId) { const rule = await interaction.guild.autoModerationRules.fetch(ruleId).catch(async err => { console.log(err); await utils.editOrReply(interaction, { embeds: [embed.error({ content: 'An error occurred while fetching the AutoMod rule.' })] }); }); if (rule) { await rule.delete().catch(async err => { console.log(err); await utils.editOrReply(interaction, { embeds: [embed.error({ content: 'An error occurred while deleting the AutoMod rule.' })] }); }); await utils.editOrReply(interaction, { embeds: [embed.success({ content: 'AutoMod rule successfully deleted.' })] }); } }