From 0a8278d2d8f18884cef961b6f0f731950e10706e Mon Sep 17 00:00:00 2001 From: Michael Wolfendale <4563722+wolfendale@users.noreply.github.com> Date: Sun, 28 Jul 2024 10:29:59 +0100 Subject: [PATCH 01/12] feat: add auto slow mode --- src/commands/slow-mode.ts | 579 ++++++++++++++++++++++++++++++++++++++ src/events/ready.ts | 6 +- src/models/slow-mode.ts | 101 +++++++ src/setup-guild.ts | 22 +- src/slow-mode.ts | 51 ++++ 5 files changed, 754 insertions(+), 5 deletions(-) create mode 100644 src/commands/slow-mode.ts create mode 100644 src/models/slow-mode.ts create mode 100644 src/slow-mode.ts diff --git a/src/commands/slow-mode.ts b/src/commands/slow-mode.ts new file mode 100644 index 0000000..34868ae --- /dev/null +++ b/src/commands/slow-mode.ts @@ -0,0 +1,579 @@ +import { SlowMode, SlowModeStage } from '@/models/slow-mode'; +import handleSlowMode from '@/slow-mode'; +import { SlashCommandBuilder } from '@discordjs/builders'; +import { sendEventLogMessage } from '@/util'; +import { ChannelType, EmbedBuilder, type ChatInputCommandInteraction } from 'discord.js'; + +async function slowModeHandler(interaction: ChatInputCommandInteraction): Promise { + switch (interaction.options.getSubcommandGroup()) { + case 'stage': { + switch (interaction.options.getSubcommand()) { + case 'set': + return setStageHandler(interaction); + case 'unset': + return unsetStageHandler(interaction); + } + break; + } + case 'enable': { + switch (interaction.options.getSubcommand()) { + case 'auto': + return enableAutoSlowModeHandler(interaction); + case 'static': + return enableStaticSlowModeHandler(interaction); + } + break; + } + case null: { + switch (interaction.options.getSubcommand()) { + case 'disable': + return disableSlowModeHandler(interaction); + case 'stats': + return slowModeStatsHandler(interaction); + } + } + } + + throw new Error('Unknown slow mode command'); +} + +async function setStageHandler(interaction: ChatInputCommandInteraction): Promise { + await interaction.deferReply({ ephemeral: true }); + + const channel = interaction.options.getChannel('channel') ?? interaction.channel; + if (!channel) { + throw new Error('No channel given'); + } + + if (channel.type !== ChannelType.GuildText) { + throw new Error('Slow mode only applies to text channels'); + } + + const slowMode = await SlowMode.findOne({ + where: { + channel_id: channel.id + }, + include: 'stages' + }); + + if (!slowMode) { + throw new Error(`No slow mode set for <#${channel.id}>`); + } + + const threshold = interaction.options.getNumber('threshold', true); + const limit = interaction.options.getNumber('limit', true); + + let oldLimit: number | null = null; + let stage = slowMode.stages?.find(stage => stage.threshold === threshold); + if (stage) { + oldLimit = stage.limit; + stage.limit = interaction.options.getNumber('limit', true); + await stage.save(); + } else { + stage = await SlowModeStage.create({ + threshold, + limit + }); + await slowMode.addStage(stage); + await slowMode.reload({ include: 'stages' }); + } + + const auditLogEmbed = new EmbedBuilder() + .setColor(0xC0C0C0) + .setTitle('Event Type: _Auto Slow-Mode Threshold Set_') + .setDescription('――――――――――――――――――――――――――――――――――') + .setFields( + { + name: 'User', + value: `<@${interaction.user.id}>` + }, + { + name: 'Channel', + value: `<#${channel.id}>` + }, + { + name: 'New Threshold', + value: `Limit of 1 message every ${limit} seconds above ${threshold} messages per minute` + } + ).setFooter({ + text: 'Pretendo Network', + iconURL: interaction.guild!.iconURL()! + }); + + if (oldLimit) { + auditLogEmbed.addFields([ + { + name: 'Old Threshold', + value: `Limit of 1 message every ${oldLimit} seconds above ${threshold} messages per minute` + } + ]); + } + + if (slowMode.stages) { + const stagesMessage = slowMode.stages.sort((a, b) => a.threshold - b.threshold).map(stage => { + return `1 message every ${stage.limit}s above ${stage.threshold} messages per minute`; + }).join('\n'); + + auditLogEmbed.addFields([ + { + name: 'Configured stages', + value: stagesMessage + } + ]); + } + + await sendEventLogMessage(interaction.guild!, channel.id, auditLogEmbed); + + await interaction.followUp({ content: `Set a limit of 1 message every ${limit} seconds above ${threshold} messages per minute on <#${channel.id}>` }); +} + +async function unsetStageHandler(interaction: ChatInputCommandInteraction): Promise { + await interaction.deferReply({ ephemeral: true }); + + const channel = interaction.options.getChannel('channel') ?? interaction.channel; + if (!channel) { + throw new Error('No channel given'); + } + + if (channel.type !== ChannelType.GuildText) { + throw new Error('Slow mode only applies to text channels'); + } + + const slowMode = await SlowMode.findOne({ + where: { + channel_id: channel.id + }, + include: 'stages' + }); + + if (!slowMode) { + throw new Error(`No slow mode set for <#${channel.id}>`); + } + + const threshold = interaction.options.getNumber('threshold', true); + + const stage = slowMode.stages?.find(stage => stage.threshold === threshold); + if (!stage) { + throw new Error(`No stage set at ${threshold} messages per minute`); + } + + const oldLimit = stage.limit; + await slowMode.removeStage(stage); + await slowMode.reload({ include: 'stages' }); + + const auditLogEmbed = new EmbedBuilder() + .setColor(0xC0C0C0) + .setTitle('Event Type: _Auto Slow-Mode Threshold Unset_') + .setDescription('――――――――――――――――――――――――――――――――――') + .setFields( + { + name: 'User', + value: `<@${interaction.user.id}>` + }, + { + name: 'Channel', + value: `<#${channel.id}>` + }, + { + name: 'Old Threshold', + value: `Limit of 1 message every ${oldLimit} seconds above ${threshold} messages per minute` + } + ).setFooter({ + text: 'Pretendo Network', + iconURL: interaction.guild!.iconURL()! + }); + + if (slowMode.stages) { + const stagesMessage = slowMode.stages.sort((a, b) => a.threshold - b.threshold).map(stage => { + return `1 message every ${stage.limit}s above ${stage.threshold} messages per minute`; + }).join('\n'); + + auditLogEmbed.addFields([ + { + name: 'Configured stages', + value: stagesMessage + } + ]); + } + + await sendEventLogMessage(interaction.guild!, channel.id, auditLogEmbed); + + await interaction.followUp({ content: `Unset the limit at ${threshold} messages per minute on <#${channel.id}>` }); +} + +async function enableAutoSlowModeHandler(interaction: ChatInputCommandInteraction): Promise { + await interaction.deferReply({ ephemeral: true }); + + const channel = interaction.options.getChannel('channel') ?? interaction.channel; + if (!channel) { + throw new Error('No channel given'); + } + + if (channel.type !== ChannelType.GuildText) { + throw new Error('Slow mode only applies to text channels'); + } + + let slowMode = await SlowMode.findOne({ + where: { + channel_id: channel.id + }, + include: 'stages' + }); + + if (slowMode) { + slowMode.enabled = true; + } else { + slowMode = await SlowMode.create({ + channel_id: channel.id, + window: 60000, + enabled: true + }); + } + + const window = interaction.options.getNumber('window'); + if (window) { + slowMode.window = window; + } + + await slowMode.save(); + + // * This returns a Promise but is specifically not awaited as it should spawn its own loop + handleSlowMode(interaction.guild!, slowMode); + + const auditLogEmbed = new EmbedBuilder() + .setColor(0xC0C0C0) + .setTitle('Event Type: _Auto Slow-Mode Enabled_') + .setDescription('――――――――――――――――――――――――――――――――――') + .setFields( + { + name: 'User', + value: `<@${interaction.user.id}>` + }, + { + name: 'Channel', + value: `<#${channel.id}>` + } + ).setFooter({ + text: 'Pretendo Network', + iconURL: interaction.guild!.iconURL()! + }); + + if (slowMode.stages) { + const stagesMessage = slowMode.stages.map(stage => { + return `1 message every ${stage.limit}s above ${stage.threshold} messages per minute`; + }).join('\n'); + + auditLogEmbed.addFields([ + { + name: 'Configured stages', + value: stagesMessage + } + ]); + } + + await sendEventLogMessage(interaction.guild!, channel.id, auditLogEmbed); + + await interaction.followUp({ content: `Auto slow mode enabled for <#${channel.id}>` }); +} + +async function enableStaticSlowModeHandler(interaction: ChatInputCommandInteraction): Promise { + await interaction.deferReply({ ephemeral: true }); + + const channel = interaction.options.getChannel('channel', false, [ChannelType.GuildText]) ?? interaction.channel; + if (!channel) { + throw new Error('No channel given'); + } + + if (channel.type !== ChannelType.GuildText) { + throw new Error('Slow mode only applies to text channels'); + } + + const slowMode = await SlowMode.findOne({ + where: { + channel_id: channel.id + }, + include: 'stages' + }); + + if (slowMode) { + slowMode.enabled = false; + await slowMode.save(); + } + + const limit = interaction.options.getNumber('limit', true); + channel.setRateLimitPerUser(limit); + + const auditLogEmbed = new EmbedBuilder() + .setColor(0xC0C0C0) + .setTitle('Event Type: _Static Slow-Mode Enabled_') + .setDescription('――――――――――――――――――――――――――――――――――') + .setFields( + { + name: 'User', + value: `<@${interaction.user.id}>` + }, + { + name: 'Channel', + value: `<#${channel.id}>` + }, + { + name: 'Limit', + value: `1 message every ${limit} seconds` + } + ).setFooter({ + text: 'Pretendo Network', + iconURL: interaction.guild!.iconURL()! + }); + + await sendEventLogMessage(interaction.guild!, channel.id, auditLogEmbed); + + await interaction.followUp({ content: `Static slow mode enabled for <#${channel.id}>` }); +} + +async function disableSlowModeHandler(interaction: ChatInputCommandInteraction): Promise { + await interaction.deferReply({ ephemeral: true }); + + const channel = interaction.options.getChannel('channel', false, [ChannelType.GuildText]) ?? interaction.channel; + if (!channel) { + throw new Error('No channel given'); + } + + if (channel.type !== ChannelType.GuildText) { + throw new Error('Slow mode only applies to text channels'); + } + + const slowMode = await SlowMode.findOne({ + where: { + channel_id: channel.id + } + }); + + if ((!slowMode || !slowMode.enabled) && channel.rateLimitPerUser === 0) { + throw new Error(`Slow mode for <#${channel.id}> is already disabled`); + } + + if (slowMode && slowMode.enabled === true) { + slowMode.enabled = false; + await slowMode.save(); + } + + await channel.setRateLimitPerUser(0); + + const auditLogEmbed = new EmbedBuilder() + .setColor(0xC0C0C0) + .setTitle('Event Type: _Slow-Mode Disabled_') + .setDescription('――――――――――――――――――――――――――――――――――') + .setFields( + { + name: 'User', + value: `<@${interaction.user.id}>` + }, + { + name: 'Channel', + value: `<#${channel.id}>` + } + ).setFooter({ + text: 'Pretendo Network', + iconURL: interaction.guild!.iconURL()! + }); + + await sendEventLogMessage(interaction.guild!, channel.id, auditLogEmbed); + + await interaction.followUp({ content: `Slow mode disabled for <#${channel.id}>` }); +} + +async function slowModeStatsHandler(interaction: ChatInputCommandInteraction): Promise { + await interaction.deferReply({ ephemeral: true }); + + const channel = interaction.options.getChannel('channel', false, [ChannelType.GuildText]) ?? interaction.channel; + if (!channel) { + throw new Error('No channel given'); + } + + if (channel.type !== ChannelType.GuildText) { + throw new Error('Slow mode only applies to text channels'); + } + + const slowMode = await SlowMode.findOne({ + where: { + channel_id: channel.id + }, + include: 'stages' + }); + + if (channel.rateLimitPerUser === 0) { + if (!slowMode || !slowMode.enabled) { + throw new Error(`No slow mode set for <#${channel.id}>`); + } + } + + const embed = new EmbedBuilder() + .setTitle('Slow mode stats') + .setFields([ + { + name: 'Channel', + value: `<#${channel.id}>` + }, + ]) + .setFooter({ + text: 'Pretendo Network', + iconURL: interaction.guild!.iconURL()! + }); + + if (slowMode && slowMode.enabled) { + embed.addFields([ + { + name: 'Participating Users', + value: slowMode.users.toString() + }, + { + name: 'Current Rate', + value: slowMode.rate.toString() + }, + { + name: 'Current Limit', + value: `1 message every ${slowMode.limit} seconds` + }, + { + name: 'Window', + value: `${(slowMode.window / 1000).toString()} seconds` + } + ]); + + if (slowMode.stages) { + const stagesMessage = slowMode.stages.sort((a, b) => a.threshold - b.threshold).map(stage => { + return `1 message every ${stage.limit}s above ${stage.threshold} messages per minute`; + }).join('\n'); + + embed.addFields([ + { + name: 'Configured stages', + value: stagesMessage + } + ]); + } + } else { + embed.addFields([ + { + name: 'Current limit', + value: `1 message every ${channel.rateLimitPerUser} seconds` + } + ]); + } + + await interaction.followUp({ embeds: [embed] }); +} + +const command = new SlashCommandBuilder() + .setDefaultMemberPermissions('0') + .setName('slow-mode') + .setDescription('Configure slow mode for a channel') + .addSubcommandGroup(group => { + group.setName('enable') + .setDescription('Enable slow mode for a channel') + .addSubcommand(cmd => { + cmd.setName('auto') + .setDescription('Enable auto slow mode') + .addChannelOption(option => { + option.setName('channel') + .setDescription('The channel to enable auto slow mode for'); + return option; + }) + .addNumberOption(option => { + option.setName('window') + .setDescription('The time window to use for calculating message rate') + .setMinValue(10000); + return option; + }); + return cmd; + }) + .addSubcommand(cmd =>{ + cmd.setName('static') + .setDescription('Enable static slow mode') + .addNumberOption(option => { + option.setName('limit') + .setDescription('The static limit to set the channel slow mode to') + .setMinValue(1) + .setRequired(true); + return option; + }) + .addChannelOption(option => { + option.setName('channel') + .setDescription('The channel to enable static slow mode for'); + return option; + }); + return cmd; + }); + return group; + }) + .addSubcommand(cmd => { + cmd.setName('disable') + .setDescription('Disable slow mode for a channel') + .addChannelOption(option => { + option.setName('channel') + .setDescription('The channel to disable auto slow mode for'); + return option; + }); + return cmd; + }) + .addSubcommand(cmd => { + cmd.setName('stats') + .setDescription('Get the current auto slow mode stats for a channel') + .addChannelOption(option => { + option.setName('channel') + .setDescription('The channel to fetch the auto slow mode stats for'); + return option; + }); + return cmd; + }) + .addSubcommandGroup(group => { + group.setName('stage') + .setDescription('Managed the auto slow mode threshold settings') + .addSubcommand(cmd => { + cmd.setName('set') + .setDescription('Set the properties of a stage for the given channel') + .addNumberOption(option => { + option.setName('threshold') + .setDescription('the threshold in messages per section that must be reached to enable this stage') + .setRequired(true) + .setMinValue(0); + return option; + }) + .addNumberOption(option => { + option.setName('limit') + .setDescription('the limit to apply to the channel once the threshold has been reached') + .setRequired(true) + .setMinValue(0); + return option; + }) + .addChannelOption(option => { + option.setName('channel') + .setDescription('The channel to add a threshold for'); + return option; + }); + return cmd; + }) + .addSubcommand(cmd => { + cmd.setName('unset') + .setDescription('Unset a stage for the given channel') + .addNumberOption(option => { + option.setName('threshold') + .setDescription('The index of the threshold to remove') + .setRequired(true); + return option; + }) + .addChannelOption(option => { + option.setName('channel') + .setDescription('The channel to remove a threshold for'); + return option; + }); + return cmd; + }); + return group; + }); + +export default { + name: command.name, + handler: slowModeHandler, + deploy: command.toJSON() +}; \ No newline at end of file diff --git a/src/events/ready.ts b/src/events/ready.ts index 73da137..a3a2269 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -7,6 +7,7 @@ import settingsCommand from '@/commands/settings'; import warnCommand from '@/commands/warn'; import modpingCommand from '@/commands/modping'; import messageLogContextMenu from '@/context-menus/messages/message-log'; +import slowModeCommand from '@/commands/slow-mode'; import { checkMatchmakingThreads } from '@/matchmaking-threads'; import { loadModel } from '@/check-nsfw'; import type { Database } from 'sqlite3'; @@ -15,7 +16,7 @@ import config from '@/config.json'; export default async function readyHandler(client: Client): Promise { console.log('Registering global commands'); - loadBotHandlersCollection('commands', client); + loadBotHandlersCollection(client); console.log('Establishing DB connection'); await sequelize.sync(config.sequelize); @@ -41,12 +42,13 @@ export default async function readyHandler(client: Client): Promise { await checkMatchmakingThreads(); } -function loadBotHandlersCollection(name: string, client: Client): void { +function loadBotHandlersCollection(client: Client): void { client.commands.set(banCommand.name, banCommand); client.commands.set(kickCommand.name, kickCommand); client.commands.set(settingsCommand.name, settingsCommand); client.commands.set(warnCommand.name, warnCommand); client.commands.set(modpingCommand.name, modpingCommand); + client.commands.set(slowModeCommand.name, slowModeCommand); client.contextMenus.set(messageLogContextMenu.name, messageLogContextMenu); } diff --git a/src/models/slow-mode.ts b/src/models/slow-mode.ts new file mode 100644 index 0000000..af01c45 --- /dev/null +++ b/src/models/slow-mode.ts @@ -0,0 +1,101 @@ +import { DataTypes, Model } from 'sequelize'; +import { sequelize } from '@/sequelize-instance'; +import type { + Association, + CreationOptional, + ForeignKey, + HasManyAddAssociationMixin, + HasManyAddAssociationsMixin, + HasManyCountAssociationsMixin, + HasManyCreateAssociationMixin, + HasManyGetAssociationsMixin, + HasManyHasAssociationMixin, + HasManyHasAssociationsMixin, + HasManyRemoveAssociationMixin, + HasManyRemoveAssociationsMixin, + HasManySetAssociationsMixin, + InferAttributes, + InferCreationAttributes, + NonAttribute +} from 'sequelize'; + +export class SlowMode extends Model, InferCreationAttributes> { + declare id: CreationOptional; + declare channel_id: string; + declare window: CreationOptional; + declare enabled: CreationOptional; + declare users: CreationOptional; + declare rate: CreationOptional; + declare limit: CreationOptional; + + declare getStages: HasManyGetAssociationsMixin; // Note the null assertions! + declare addStage: HasManyAddAssociationMixin; + declare addStages: HasManyAddAssociationsMixin; + declare setStages: HasManySetAssociationsMixin; + declare removeStage: HasManyRemoveAssociationMixin; + declare removeStages: HasManyRemoveAssociationsMixin; + declare hasStage: HasManyHasAssociationMixin; + declare hasStages: HasManyHasAssociationsMixin; + declare countStages: HasManyCountAssociationsMixin; + declare createStage: HasManyCreateAssociationMixin; + declare stages?: NonAttribute; + + declare static associations: { + stages: Association; + } +} + +SlowMode.init({ + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true + }, + channel_id: { + type: DataTypes.STRING, + unique: true + }, + enabled: { + type: DataTypes.BOOLEAN, + defaultValue: true + }, + window: { + type: DataTypes.NUMBER, + defaultValue: 60000 + }, + users: { + type: DataTypes.NUMBER + }, + rate: { + type: DataTypes.NUMBER + }, + limit: { + type: DataTypes.NUMBER + } +}, { sequelize, tableName: 'slow-mode' }); + +export class SlowModeStage extends Model, InferCreationAttributes> { + declare id: CreationOptional; + declare threshold: number; + declare limit: number; + + declare ownerId: ForeignKey; + declare owner?: NonAttribute; +} + +SlowModeStage.init({ + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true + }, + threshold: { + type: DataTypes.NUMBER + }, + limit: { + type: DataTypes.NUMBER + } +}, { sequelize, tableName: 'slow-mode-threshold' }); + +SlowMode.hasMany(SlowModeStage, { as: 'stages' }); +SlowModeStage.belongsTo(SlowMode); \ No newline at end of file diff --git a/src/setup-guild.ts b/src/setup-guild.ts index ce5b27a..bc24cbc 100644 --- a/src/setup-guild.ts +++ b/src/setup-guild.ts @@ -1,23 +1,28 @@ import { PermissionFlagsBits } from 'discord.js'; import { REST } from '@discordjs/rest'; import { Routes } from 'discord-api-types/v10'; +import handleSlowMode from '@/slow-mode'; +import { SlowMode } from '@/models/slow-mode'; import { bot_token as botToken } from '@/config.json'; import type { Guild } from 'discord.js'; const rest = new REST({ version: '10' }).setToken(botToken); export async function setupGuild(guild: Guild): Promise { - // do nothing if the bot does not have the correct permissions + // * Do nothing if the bot does not have the correct permissions if (!guild.members.me!.permissions.has([PermissionFlagsBits.ManageRoles, PermissionFlagsBits.ManageChannels])) { console.log('Bot does not have permissions to set up in guild', guild.name); return; } - // Populate members cache + // * Populate members cache await guild.members.fetch(); - // Setup commands + // * Setup commands await deployCommands(guild); + + // * Setup slow mode + await setupSlowMode(guild); } async function deployCommands(guild: Guild): Promise { @@ -33,4 +38,15 @@ async function deployCommands(guild: Guild): Promise { await rest.put(Routes.applicationGuildCommands(guild.members.me!.id, guild.id), { body: [...commands, ...contextMenus], }); +} + +async function setupSlowMode(guild: Guild): Promise { + const slowModes = await SlowMode.findAll({ + where: { + enabled: true + }, + include: { all: true } + }); + + slowModes.forEach(slowMode => handleSlowMode(guild, slowMode)); } \ No newline at end of file diff --git a/src/slow-mode.ts b/src/slow-mode.ts new file mode 100644 index 0000000..8d4b22c --- /dev/null +++ b/src/slow-mode.ts @@ -0,0 +1,51 @@ +import { ChannelType, PermissionsBitField } from 'discord.js'; +import { sequelize } from '@/sequelize-instance'; +import type { SlowMode } from '@/models/slow-mode'; +import type { Guild, Message } from 'discord.js'; + +export default async function handleSlowMode(guild: Guild, slowMode: SlowMode): Promise { + const channel = await guild.channels.fetch(slowMode.channel_id); + if (channel === null || channel.type !== ChannelType.GuildText) { + return; + } + + const filter = async (message: Message): Promise => { + // * This prevents people who are immune to slow mode from counting towards the message rate + const permissions = channel.permissionsFor(message.member!); + return !permissions.has([PermissionsBitField.Flags.ManageMessages, PermissionsBitField.Flags.ManageChannels]); + }; + + if (slowMode.enabled) { + console.log(`Slow mode starting for ${channel.name}`); + } + + while (slowMode.enabled) { + const messages = await channel.awaitMessages({ time: slowMode.window, filter }); + const messageCount = messages.size; + + await sequelize.transaction(async transaction => { + await slowMode.reload({ include: 'stages', transaction }); + + // * Calculates the rate of messages per minute + const rate = (60000 / slowMode.window) * messageCount; + const users = new Set(messages.map(message => message.author.id)); + + const stage = (slowMode.stages ?? []) + .filter(stage => rate >= stage.threshold) + .sort((a, b) => b.threshold - a.threshold)[0]; + + if (stage && channel.rateLimitPerUser !== stage.limit) { + await channel.setRateLimitPerUser(stage.limit); + } + + slowMode.users = users.size; + slowMode.rate = rate; + + if (stage) { + slowMode.limit = stage.limit; + } + + await slowMode.save({ transaction }); + }); + } +} \ No newline at end of file From 6412bbc9a66ee5d33d7423089d4bbae2eab85f15 Mon Sep 17 00:00:00 2001 From: Michael Wolfendale <4563722+wolfendale@users.noreply.github.com> Date: Thu, 1 Aug 2024 19:01:34 +0100 Subject: [PATCH 02/12] fix: fix validation for slow-mode options --- src/commands/slow-mode.ts | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/commands/slow-mode.ts b/src/commands/slow-mode.ts index 34868ae..354b488 100644 --- a/src/commands/slow-mode.ts +++ b/src/commands/slow-mode.ts @@ -60,14 +60,14 @@ async function setStageHandler(interaction: ChatInputCommandInteraction): Promis throw new Error(`No slow mode set for <#${channel.id}>`); } - const threshold = interaction.options.getNumber('threshold', true); - const limit = interaction.options.getNumber('limit', true); + const threshold = interaction.options.getInteger('threshold', true); + const limit = interaction.options.getInteger('limit', true); let oldLimit: number | null = null; let stage = slowMode.stages?.find(stage => stage.threshold === threshold); if (stage) { oldLimit = stage.limit; - stage.limit = interaction.options.getNumber('limit', true); + stage.limit = interaction.options.getInteger('limit', true); await stage.save(); } else { stage = await SlowModeStage.create({ @@ -150,7 +150,7 @@ async function unsetStageHandler(interaction: ChatInputCommandInteraction): Prom throw new Error(`No slow mode set for <#${channel.id}>`); } - const threshold = interaction.options.getNumber('threshold', true); + const threshold = interaction.options.getInteger('threshold', true); const stage = slowMode.stages?.find(stage => stage.threshold === threshold); if (!stage) { @@ -230,7 +230,7 @@ async function enableAutoSlowModeHandler(interaction: ChatInputCommandInteractio }); } - const window = interaction.options.getNumber('window'); + const window = interaction.options.getInteger('window'); if (window) { slowMode.window = window; } @@ -300,7 +300,7 @@ async function enableStaticSlowModeHandler(interaction: ChatInputCommandInteract await slowMode.save(); } - const limit = interaction.options.getNumber('limit', true); + const limit = interaction.options.getInteger('limit', true); channel.setRateLimitPerUser(limit); const auditLogEmbed = new EmbedBuilder() @@ -479,10 +479,11 @@ const command = new SlashCommandBuilder() .setDescription('The channel to enable auto slow mode for'); return option; }) - .addNumberOption(option => { + .addIntegerOption(option => { option.setName('window') .setDescription('The time window to use for calculating message rate') - .setMinValue(10000); + .setMinValue(10000) + .setMaxValue(1000 * 60 * 60); return option; }); return cmd; @@ -490,10 +491,11 @@ const command = new SlashCommandBuilder() .addSubcommand(cmd =>{ cmd.setName('static') .setDescription('Enable static slow mode') - .addNumberOption(option => { + .addIntegerOption(option => { option.setName('limit') .setDescription('The static limit to set the channel slow mode to') .setMinValue(1) + .setMaxValue(21600) .setRequired(true); return option; }) @@ -532,18 +534,19 @@ const command = new SlashCommandBuilder() .addSubcommand(cmd => { cmd.setName('set') .setDescription('Set the properties of a stage for the given channel') - .addNumberOption(option => { + .addIntegerOption(option => { option.setName('threshold') .setDescription('the threshold in messages per section that must be reached to enable this stage') .setRequired(true) .setMinValue(0); return option; }) - .addNumberOption(option => { + .addIntegerOption(option => { option.setName('limit') .setDescription('the limit to apply to the channel once the threshold has been reached') .setRequired(true) - .setMinValue(0); + .setMinValue(1) + .setMaxValue(21600); return option; }) .addChannelOption(option => { @@ -556,9 +559,10 @@ const command = new SlashCommandBuilder() .addSubcommand(cmd => { cmd.setName('unset') .setDescription('Unset a stage for the given channel') - .addNumberOption(option => { + .addIntegerOption(option => { option.setName('threshold') .setDescription('The index of the threshold to remove') + .setMinValue(0) .setRequired(true); return option; }) From b32949e85b3060afb5b99216f98c909b4bf71b70 Mon Sep 17 00:00:00 2001 From: Michael Wolfendale <4563722+wolfendale@users.noreply.github.com> Date: Thu, 1 Aug 2024 19:11:59 +0100 Subject: [PATCH 03/12] fix: check for empty slow mode stages --- src/commands/slow-mode.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/commands/slow-mode.ts b/src/commands/slow-mode.ts index 354b488..d79f093 100644 --- a/src/commands/slow-mode.ts +++ b/src/commands/slow-mode.ts @@ -109,7 +109,7 @@ async function setStageHandler(interaction: ChatInputCommandInteraction): Promis ]); } - if (slowMode.stages) { + if (slowMode.stages && slowMode.stages.length > 0) { const stagesMessage = slowMode.stages.sort((a, b) => a.threshold - b.threshold).map(stage => { return `1 message every ${stage.limit}s above ${stage.threshold} messages per minute`; }).join('\n'); @@ -183,7 +183,7 @@ async function unsetStageHandler(interaction: ChatInputCommandInteraction): Prom iconURL: interaction.guild!.iconURL()! }); - if (slowMode.stages) { + if (slowMode.stages && slowMode.stages.length > 0) { const stagesMessage = slowMode.stages.sort((a, b) => a.threshold - b.threshold).map(stage => { return `1 message every ${stage.limit}s above ${stage.threshold} messages per minute`; }).join('\n'); @@ -258,7 +258,7 @@ async function enableAutoSlowModeHandler(interaction: ChatInputCommandInteractio iconURL: interaction.guild!.iconURL()! }); - if (slowMode.stages) { + if (slowMode.stages && slowMode.stages.length > 0) { const stagesMessage = slowMode.stages.map(stage => { return `1 message every ${stage.limit}s above ${stage.threshold} messages per minute`; }).join('\n'); @@ -440,7 +440,7 @@ async function slowModeStatsHandler(interaction: ChatInputCommandInteraction): P } ]); - if (slowMode.stages) { + if (slowMode.stages && slowMode.stages.length > 0) { const stagesMessage = slowMode.stages.sort((a, b) => a.threshold - b.threshold).map(stage => { return `1 message every ${stage.limit}s above ${stage.threshold} messages per minute`; }).join('\n'); From ddd02711fd8ea96e0a7749c98aabc7018a586066 Mon Sep 17 00:00:00 2001 From: Michael Wolfendale <4563722+wolfendale@users.noreply.github.com> Date: Thu, 1 Aug 2024 19:35:02 +0100 Subject: [PATCH 04/12] fix: correct types on SlowMode model --- src/commands/slow-mode.ts | 28 +++++++++++++++++++--------- src/models/slow-mode.ts | 14 ++++++-------- src/slow-mode.ts | 4 ---- 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/commands/slow-mode.ts b/src/commands/slow-mode.ts index d79f093..dd16f40 100644 --- a/src/commands/slow-mode.ts +++ b/src/commands/slow-mode.ts @@ -421,18 +421,28 @@ async function slowModeStatsHandler(interaction: ChatInputCommandInteraction): P }); if (slowMode && slowMode.enabled) { + if (slowMode.users) { + embed.addFields([ + { + name: 'Participating Users', + value: slowMode.users.toString() + } + ]); + } + + if (slowMode.rate) { + embed.addFields([ + { + name: 'Current Rate', + value: slowMode.rate.toString() + } + ]); + } + embed.addFields([ - { - name: 'Participating Users', - value: slowMode.users.toString() - }, - { - name: 'Current Rate', - value: slowMode.rate.toString() - }, { name: 'Current Limit', - value: `1 message every ${slowMode.limit} seconds` + value: `1 message every ${channel.rateLimitPerUser} seconds` }, { name: 'Window', diff --git a/src/models/slow-mode.ts b/src/models/slow-mode.ts index af01c45..967db07 100644 --- a/src/models/slow-mode.ts +++ b/src/models/slow-mode.ts @@ -24,9 +24,8 @@ export class SlowMode extends Model; declare enabled: CreationOptional; - declare users: CreationOptional; - declare rate: CreationOptional; - declare limit: CreationOptional; + declare users?: CreationOptional; + declare rate?: CreationOptional; declare getStages: HasManyGetAssociationsMixin; // Note the null assertions! declare addStage: HasManyAddAssociationMixin; @@ -64,13 +63,12 @@ SlowMode.init({ defaultValue: 60000 }, users: { - type: DataTypes.NUMBER + type: DataTypes.NUMBER, + allowNull: true }, rate: { - type: DataTypes.NUMBER - }, - limit: { - type: DataTypes.NUMBER + type: DataTypes.NUMBER, + allowNull: true } }, { sequelize, tableName: 'slow-mode' }); diff --git a/src/slow-mode.ts b/src/slow-mode.ts index 8d4b22c..5d85bc3 100644 --- a/src/slow-mode.ts +++ b/src/slow-mode.ts @@ -41,10 +41,6 @@ export default async function handleSlowMode(guild: Guild, slowMode: SlowMode): slowMode.users = users.size; slowMode.rate = rate; - if (stage) { - slowMode.limit = stage.limit; - } - await slowMode.save({ transaction }); }); } From c2a1b0d388899c810c10d7b26cade308aa5e96a4 Mon Sep 17 00:00:00 2001 From: Michael Wolfendale <4563722+wolfendale@users.noreply.github.com> Date: Thu, 1 Aug 2024 19:39:23 +0100 Subject: [PATCH 05/12] fix: make sure slow-mode stages are deleted from db when unset --- src/commands/slow-mode.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/slow-mode.ts b/src/commands/slow-mode.ts index dd16f40..a84ae7b 100644 --- a/src/commands/slow-mode.ts +++ b/src/commands/slow-mode.ts @@ -158,7 +158,7 @@ async function unsetStageHandler(interaction: ChatInputCommandInteraction): Prom } const oldLimit = stage.limit; - await slowMode.removeStage(stage); + await slowMode.destroy(); await slowMode.reload({ include: 'stages' }); const auditLogEmbed = new EmbedBuilder() From c262ecfa9b4d1fcdd36bad3cf824e822daeae6e3 Mon Sep 17 00:00:00 2001 From: Michael Wolfendale <4563722+wolfendale@users.noreply.github.com> Date: Thu, 1 Aug 2024 23:26:24 +0100 Subject: [PATCH 06/12] fix: use findAndCreate in slow mode commands --- src/commands/slow-mode.ts | 104 ++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 49 deletions(-) diff --git a/src/commands/slow-mode.ts b/src/commands/slow-mode.ts index a84ae7b..28d85d7 100644 --- a/src/commands/slow-mode.ts +++ b/src/commands/slow-mode.ts @@ -49,17 +49,17 @@ async function setStageHandler(interaction: ChatInputCommandInteraction): Promis throw new Error('Slow mode only applies to text channels'); } - const slowMode = await SlowMode.findOne({ + const [ slowMode, ] = await SlowMode.findOrCreate({ where: { channel_id: channel.id }, - include: 'stages' + include: 'stages', + defaults: { + channel_id: channel.id, + enabled: false + } }); - if (!slowMode) { - throw new Error(`No slow mode set for <#${channel.id}>`); - } - const threshold = interaction.options.getInteger('threshold', true); const limit = interaction.options.getInteger('limit', true); @@ -213,21 +213,21 @@ async function enableAutoSlowModeHandler(interaction: ChatInputCommandInteractio throw new Error('Slow mode only applies to text channels'); } - let slowMode = await SlowMode.findOne({ + const [slowMode, created] = await SlowMode.findOrCreate({ where: { channel_id: channel.id }, - include: 'stages' + include: 'stages', + defaults: { + channel_id: channel.id, + enabled: false + } }); - if (slowMode) { + const enabled = slowMode.enabled; + + if (!created && !slowMode.enabled) { slowMode.enabled = true; - } else { - slowMode = await SlowMode.create({ - channel_id: channel.id, - window: 60000, - enabled: true - }); } const window = interaction.options.getInteger('window'); @@ -235,10 +235,14 @@ async function enableAutoSlowModeHandler(interaction: ChatInputCommandInteractio slowMode.window = window; } - await slowMode.save(); + if (slowMode.changed()) { + await slowMode.save(); + } - // * This returns a Promise but is specifically not awaited as it should spawn its own loop - handleSlowMode(interaction.guild!, slowMode); + if (!enabled) { + // * This returns a Promise but is specifically not awaited as it should spawn its own loop + handleSlowMode(interaction.guild!, slowMode); + } const auditLogEmbed = new EmbedBuilder() .setColor(0xC0C0C0) @@ -420,23 +424,25 @@ async function slowModeStatsHandler(interaction: ChatInputCommandInteraction): P iconURL: interaction.guild!.iconURL()! }); - if (slowMode && slowMode.enabled) { - if (slowMode.users) { - embed.addFields([ - { - name: 'Participating Users', - value: slowMode.users.toString() - } - ]); - } - - if (slowMode.rate) { - embed.addFields([ - { - name: 'Current Rate', - value: slowMode.rate.toString() - } - ]); + if (slowMode) { + if (slowMode.enabled) { + if (slowMode.users) { + embed.addFields([ + { + name: 'Participating Users', + value: slowMode.users.toString() + } + ]); + } + + if (slowMode.rate) { + embed.addFields([ + { + name: 'Current Rate', + value: slowMode.rate.toString() + } + ]); + } } embed.addFields([ @@ -449,19 +455,6 @@ async function slowModeStatsHandler(interaction: ChatInputCommandInteraction): P value: `${(slowMode.window / 1000).toString()} seconds` } ]); - - if (slowMode.stages && slowMode.stages.length > 0) { - const stagesMessage = slowMode.stages.sort((a, b) => a.threshold - b.threshold).map(stage => { - return `1 message every ${stage.limit}s above ${stage.threshold} messages per minute`; - }).join('\n'); - - embed.addFields([ - { - name: 'Configured stages', - value: stagesMessage - } - ]); - } } else { embed.addFields([ { @@ -471,6 +464,19 @@ async function slowModeStatsHandler(interaction: ChatInputCommandInteraction): P ]); } + if (slowMode && slowMode.stages && slowMode.stages.length > 0) { + const stagesMessage = slowMode.stages.sort((a, b) => a.threshold - b.threshold).map(stage => { + return `1 message every ${stage.limit}s above ${stage.threshold} messages per minute`; + }).join('\n'); + + embed.addFields([ + { + name: 'Configured stages', + value: stagesMessage + } + ]); + } + await interaction.followUp({ embeds: [embed] }); } @@ -571,7 +577,7 @@ const command = new SlashCommandBuilder() .setDescription('Unset a stage for the given channel') .addIntegerOption(option => { option.setName('threshold') - .setDescription('The index of the threshold to remove') + .setDescription('The value of the threshold to remove') .setMinValue(0) .setRequired(true); return option; From f42e571b221c52be5ed8227a902ca666e830fe18 Mon Sep 17 00:00:00 2001 From: Michael Wolfendale <4563722+wolfendale@users.noreply.github.com> Date: Thu, 1 Aug 2024 23:40:00 +0100 Subject: [PATCH 07/12] chore: remove redundant code from SlowMode --- src/models/slow-mode.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/slow-mode.ts b/src/models/slow-mode.ts index 967db07..8d42ca4 100644 --- a/src/models/slow-mode.ts +++ b/src/models/slow-mode.ts @@ -19,7 +19,7 @@ import type { NonAttribute } from 'sequelize'; -export class SlowMode extends Model, InferCreationAttributes> { +export class SlowMode extends Model, InferCreationAttributes> { declare id: CreationOptional; declare channel_id: string; declare window: CreationOptional; From 8652de215531a5c206fee748381364fff9784266 Mon Sep 17 00:00:00 2001 From: Michael Wolfendale <4563722+wolfendale@users.noreply.github.com> Date: Thu, 1 Aug 2024 23:48:22 +0100 Subject: [PATCH 08/12] chore: move slow mode setup to ready event handler --- src/commands/slow-mode.ts | 2 +- src/events/ready.ts | 17 ++++++++++++++++- src/setup-guild.ts | 16 ---------------- src/slow-mode.ts | 6 +++--- 4 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/commands/slow-mode.ts b/src/commands/slow-mode.ts index 28d85d7..c46fdda 100644 --- a/src/commands/slow-mode.ts +++ b/src/commands/slow-mode.ts @@ -241,7 +241,7 @@ async function enableAutoSlowModeHandler(interaction: ChatInputCommandInteractio if (!enabled) { // * This returns a Promise but is specifically not awaited as it should spawn its own loop - handleSlowMode(interaction.guild!, slowMode); + handleSlowMode(interaction.client, slowMode); } const auditLogEmbed = new EmbedBuilder() diff --git a/src/events/ready.ts b/src/events/ready.ts index a3a2269..246054c 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -10,9 +10,11 @@ import messageLogContextMenu from '@/context-menus/messages/message-log'; import slowModeCommand from '@/commands/slow-mode'; import { checkMatchmakingThreads } from '@/matchmaking-threads'; import { loadModel } from '@/check-nsfw'; +import { SlowMode } from '@/models/slow-mode'; +import handleSlowMode from '@/slow-mode'; +import config from '@/config.json'; import type { Database } from 'sqlite3'; import type { Client } from 'discord.js'; -import config from '@/config.json'; export default async function readyHandler(client: Client): Promise { console.log('Registering global commands'); @@ -40,6 +42,8 @@ export default async function readyHandler(client: Client): Promise { console.log(`Logged in as ${client.user!.tag}!`); await checkMatchmakingThreads(); + + await setupSlowMode(client); } function loadBotHandlersCollection(client: Client): void { @@ -52,3 +56,14 @@ function loadBotHandlersCollection(client: Client): void { client.contextMenus.set(messageLogContextMenu.name, messageLogContextMenu); } + +async function setupSlowMode(client: Client): Promise { + const slowModes = await SlowMode.findAll({ + where: { + enabled: true + }, + include: { all: true } + }); + + slowModes.forEach(slowMode => handleSlowMode(client, slowMode)); +} \ No newline at end of file diff --git a/src/setup-guild.ts b/src/setup-guild.ts index bc24cbc..fb488c7 100644 --- a/src/setup-guild.ts +++ b/src/setup-guild.ts @@ -1,8 +1,6 @@ import { PermissionFlagsBits } from 'discord.js'; import { REST } from '@discordjs/rest'; import { Routes } from 'discord-api-types/v10'; -import handleSlowMode from '@/slow-mode'; -import { SlowMode } from '@/models/slow-mode'; import { bot_token as botToken } from '@/config.json'; import type { Guild } from 'discord.js'; @@ -20,9 +18,6 @@ export async function setupGuild(guild: Guild): Promise { // * Setup commands await deployCommands(guild); - - // * Setup slow mode - await setupSlowMode(guild); } async function deployCommands(guild: Guild): Promise { @@ -39,14 +34,3 @@ async function deployCommands(guild: Guild): Promise { body: [...commands, ...contextMenus], }); } - -async function setupSlowMode(guild: Guild): Promise { - const slowModes = await SlowMode.findAll({ - where: { - enabled: true - }, - include: { all: true } - }); - - slowModes.forEach(slowMode => handleSlowMode(guild, slowMode)); -} \ No newline at end of file diff --git a/src/slow-mode.ts b/src/slow-mode.ts index 5d85bc3..c96f015 100644 --- a/src/slow-mode.ts +++ b/src/slow-mode.ts @@ -1,10 +1,10 @@ import { ChannelType, PermissionsBitField } from 'discord.js'; import { sequelize } from '@/sequelize-instance'; import type { SlowMode } from '@/models/slow-mode'; -import type { Guild, Message } from 'discord.js'; +import type { Client, Message } from 'discord.js'; -export default async function handleSlowMode(guild: Guild, slowMode: SlowMode): Promise { - const channel = await guild.channels.fetch(slowMode.channel_id); +export default async function handleSlowMode(client: Client, slowMode: SlowMode): Promise { + const channel = await client.channels.fetch(slowMode.channel_id); if (channel === null || channel.type !== ChannelType.GuildText) { return; } From cb7826c57213c897e5a627a2da338f9742900b93 Mon Sep 17 00:00:00 2001 From: Michael Wolfendale <4563722+wolfendale@users.noreply.github.com> Date: Sat, 3 Aug 2024 10:10:20 +0100 Subject: [PATCH 09/12] fix: destroy stage instead of slow mode --- src/commands/slow-mode.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/slow-mode.ts b/src/commands/slow-mode.ts index c46fdda..9febca7 100644 --- a/src/commands/slow-mode.ts +++ b/src/commands/slow-mode.ts @@ -158,7 +158,7 @@ async function unsetStageHandler(interaction: ChatInputCommandInteraction): Prom } const oldLimit = stage.limit; - await slowMode.destroy(); + await stage.destroy(); await slowMode.reload({ include: 'stages' }); const auditLogEmbed = new EmbedBuilder() From 86116141f6d6db7c6873ebbb926d38a138e0ff85 Mon Sep 17 00:00:00 2001 From: Michael Wolfendale <4563722+wolfendale@users.noreply.github.com> Date: Sat, 3 Aug 2024 10:11:59 +0100 Subject: [PATCH 10/12] fix: make min value for limit 0 --- src/commands/slow-mode.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/slow-mode.ts b/src/commands/slow-mode.ts index 9febca7..14e5a39 100644 --- a/src/commands/slow-mode.ts +++ b/src/commands/slow-mode.ts @@ -561,7 +561,7 @@ const command = new SlashCommandBuilder() option.setName('limit') .setDescription('the limit to apply to the channel once the threshold has been reached') .setRequired(true) - .setMinValue(1) + .setMinValue(0) .setMaxValue(21600); return option; }) From 895280215538f74a15e299ba0f5c30b91824f0c8 Mon Sep 17 00:00:00 2001 From: Michael Wolfendale <4563722+wolfendale@users.noreply.github.com> Date: Sat, 3 Aug 2024 11:34:43 +0100 Subject: [PATCH 11/12] chore: fix slow mode stats --- src/commands/slow-mode.ts | 133 +++++++++++++++++++++++++++----------- src/slow-mode.ts | 2 + 2 files changed, 99 insertions(+), 36 deletions(-) diff --git a/src/commands/slow-mode.ts b/src/commands/slow-mode.ts index 14e5a39..0e441b9 100644 --- a/src/commands/slow-mode.ts +++ b/src/commands/slow-mode.ts @@ -2,7 +2,8 @@ import { SlowMode, SlowModeStage } from '@/models/slow-mode'; import handleSlowMode from '@/slow-mode'; import { SlashCommandBuilder } from '@discordjs/builders'; import { sendEventLogMessage } from '@/util'; -import { ChannelType, EmbedBuilder, type ChatInputCommandInteraction } from 'discord.js'; +import { ChannelType, EmbedBuilder } from 'discord.js'; +import type { GuildTextBasedChannel, ChatInputCommandInteraction } from 'discord.js'; async function slowModeHandler(interaction: ChatInputCommandInteraction): Promise { switch (interaction.options.getSubcommandGroup()) { @@ -406,11 +407,24 @@ async function slowModeStatsHandler(interaction: ChatInputCommandInteraction): P }); if (channel.rateLimitPerUser === 0) { - if (!slowMode || !slowMode.enabled) { + if (!slowMode) { throw new Error(`No slow mode set for <#${channel.id}>`); } } + let embed: EmbedBuilder; + if (slowMode && slowMode.enabled) { + embed = autoSlowModeStats(slowMode, channel); + } else if (channel.rateLimitPerUser > 0) { + embed = staticSlowModeStats(slowMode, channel); + } else { + embed = disabledSlowModeStats(slowMode, channel); + } + + await interaction.followUp({ embeds: [embed] }); +} + +function autoSlowModeStats(slowMode: SlowMode, channel: GuildTextBasedChannel): EmbedBuilder { const embed = new EmbedBuilder() .setTitle('Slow mode stats') .setFields([ @@ -418,52 +432,101 @@ async function slowModeStatsHandler(interaction: ChatInputCommandInteraction): P name: 'Channel', value: `<#${channel.id}>` }, + { + name: 'Slow mode type', + value: 'Auto' + }, + { + name: 'Window', + value: `${(slowMode.window / 1000).toString()} seconds` + } ]) .setFooter({ text: 'Pretendo Network', - iconURL: interaction.guild!.iconURL()! + iconURL: channel.guild.iconURL()! }); - - if (slowMode) { - if (slowMode.enabled) { - if (slowMode.users) { - embed.addFields([ - { - name: 'Participating Users', - value: slowMode.users.toString() - } - ]); + + if (slowMode.users !== undefined) { + embed.addFields([ + { + name: 'Participating Users', + value: slowMode.users.toString() } - - if (slowMode.rate) { - embed.addFields([ - { - name: 'Current Rate', - value: slowMode.rate.toString() - } - ]); + ]); + } + + if (slowMode.rate !== undefined) { + embed.addFields([ + { + name: 'Current Rate', + value: slowMode.rate.toString() } + ]); + } + + embed.addFields([ + { + name: 'Current limit', + value: `1 message every ${channel.rateLimitPerUser} seconds` } + ]); - embed.addFields([ + addConfiguredStagesToEmbed(embed, slowMode); + + return embed; +} + +function staticSlowModeStats(slowMode: SlowMode | null, channel: GuildTextBasedChannel): EmbedBuilder { + const embed = new EmbedBuilder() + .setTitle('Slow mode stats') + .setFields([ { - name: 'Current Limit', - value: `1 message every ${channel.rateLimitPerUser} seconds` + name: 'Channel', + value: `<#${channel.id}>` }, { - name: 'Window', - value: `${(slowMode.window / 1000).toString()} seconds` - } - ]); - } else { - embed.addFields([ + name: 'Slow mode type', + value: 'Static' + }, { name: 'Current limit', value: `1 message every ${channel.rateLimitPerUser} seconds` } - ]); - } + ]) + .setFooter({ + text: 'Pretendo Network', + iconURL: channel.guild.iconURL()! + }); + + addConfiguredStagesToEmbed(embed, slowMode); + + return embed; +} +function disabledSlowModeStats(slowMode: SlowMode | null, channel: GuildTextBasedChannel): EmbedBuilder { + const embed = new EmbedBuilder() + .setTitle('Slow mode stats') + .setFields([ + { + name: 'Channel', + value: `<#${channel.id}>` + }, + { + name: 'Slow mode type', + value: 'Disabled' + } + ]) + .setFooter({ + text: 'Pretendo Network', + iconURL: channel.guild.iconURL()! + }); + + addConfiguredStagesToEmbed(embed, slowMode); + + return embed; +} + +function addConfiguredStagesToEmbed(embed: EmbedBuilder, slowMode: SlowMode | null): void { if (slowMode && slowMode.stages && slowMode.stages.length > 0) { const stagesMessage = slowMode.stages.sort((a, b) => a.threshold - b.threshold).map(stage => { return `1 message every ${stage.limit}s above ${stage.threshold} messages per minute`; @@ -471,13 +534,11 @@ async function slowModeStatsHandler(interaction: ChatInputCommandInteraction): P embed.addFields([ { - name: 'Configured stages', + name: 'Configured auto slow mode stages', value: stagesMessage - } + }, ]); } - - await interaction.followUp({ embeds: [embed] }); } const command = new SlashCommandBuilder() diff --git a/src/slow-mode.ts b/src/slow-mode.ts index c96f015..99d9521 100644 --- a/src/slow-mode.ts +++ b/src/slow-mode.ts @@ -44,4 +44,6 @@ export default async function handleSlowMode(client: Client, slowMode: SlowMode) await slowMode.save({ transaction }); }); } + + console.log(`Slow mode stopped for ${channel.name}`); } \ No newline at end of file From 929ab5d644fe419ccbb3d95dbea746f12244024f Mon Sep 17 00:00:00 2001 From: Michael Wolfendale <4563722+wolfendale@users.noreply.github.com> Date: Sat, 3 Aug 2024 12:09:18 +0100 Subject: [PATCH 12/12] chore: fix style issues --- .eslintrc.json | 2 +- src/commands/slow-mode.ts | 44 +++++++++++++++++---------------------- src/models/slow-mode.ts | 2 +- 3 files changed, 21 insertions(+), 27 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 5e17423..0e3827f 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -14,7 +14,7 @@ ], "rules": { "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], + "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }], "@typescript-eslint/no-extra-semi": "error", "@typescript-eslint/keyword-spacing": "error", "require-atomic-updates": "warn", diff --git a/src/commands/slow-mode.ts b/src/commands/slow-mode.ts index 0e441b9..184a3a9 100644 --- a/src/commands/slow-mode.ts +++ b/src/commands/slow-mode.ts @@ -6,32 +6,26 @@ import { ChannelType, EmbedBuilder } from 'discord.js'; import type { GuildTextBasedChannel, ChatInputCommandInteraction } from 'discord.js'; async function slowModeHandler(interaction: ChatInputCommandInteraction): Promise { - switch (interaction.options.getSubcommandGroup()) { - case 'stage': { - switch (interaction.options.getSubcommand()) { - case 'set': - return setStageHandler(interaction); - case 'unset': - return unsetStageHandler(interaction); - } - break; + const subcommandGroup = interaction.options.getSubcommandGroup(); + const subcommand = interaction.options.getSubcommand(); + + if (subcommandGroup === 'stage') { + if (subcommand === 'set') { + return setStageHandler(interaction); + } else if (subcommand === 'unset') { + return unsetStageHandler(interaction); } - case 'enable': { - switch (interaction.options.getSubcommand()) { - case 'auto': - return enableAutoSlowModeHandler(interaction); - case 'static': - return enableStaticSlowModeHandler(interaction); - } - break; + } else if (subcommandGroup === 'enable') { + if (subcommand === 'auto') { + return enableAutoSlowModeHandler(interaction); + } else { + return enableStaticSlowModeHandler(interaction); } - case null: { - switch (interaction.options.getSubcommand()) { - case 'disable': - return disableSlowModeHandler(interaction); - case 'stats': - return slowModeStatsHandler(interaction); - } + } else { + if (subcommand === 'disable') { + return disableSlowModeHandler(interaction); + } else if (subcommand === 'stats') { + return slowModeStatsHandler(interaction); } } @@ -50,7 +44,7 @@ async function setStageHandler(interaction: ChatInputCommandInteraction): Promis throw new Error('Slow mode only applies to text channels'); } - const [ slowMode, ] = await SlowMode.findOrCreate({ + const [slowMode, _] = await SlowMode.findOrCreate({ where: { channel_id: channel.id }, diff --git a/src/models/slow-mode.ts b/src/models/slow-mode.ts index 8d42ca4..079e090 100644 --- a/src/models/slow-mode.ts +++ b/src/models/slow-mode.ts @@ -27,7 +27,7 @@ export class SlowMode extends Model, InferCreationAttr declare users?: CreationOptional; declare rate?: CreationOptional; - declare getStages: HasManyGetAssociationsMixin; // Note the null assertions! + declare getStages: HasManyGetAssociationsMixin; declare addStage: HasManyAddAssociationMixin; declare addStages: HasManyAddAssociationsMixin; declare setStages: HasManySetAssociationsMixin;