diff --git a/.bun-version b/.bun-version index 5166d134a..40e713d59 100644 --- a/.bun-version +++ b/.bun-version @@ -1 +1 @@ -1.1.26 +1.1.30 diff --git a/.node-version b/.node-version index c2aa6d768..728f7de5c 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -22.4.1 +22.9.0 diff --git a/.vscode/settings.json b/.vscode/settings.json index 179417d0e..2e9961247 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -123,5 +123,25 @@ "typescript.inlayHints.variableTypes.enabled": true, "typescript.inlayHints.propertyDeclarationTypes.enabled": true, "typescript.inlayHints.variableTypes.suppressWhenTypeMatchesName": true, - "todo-tree.tree.showBadges": true + "todo-tree.tree.showBadges": true, + "files.exclude": { + "**/tmp/**": true, + "**/node_modules/**": true, + "**/.git/objects/**": true + }, + "files.watcherExclude": { + "**/.git/objects/**": true, + "**/.git/subtree-cache/**": true, + "**/node_modules/**": true, + "**/tmp/**": true, + "**/dist/**": true + }, + "search.exclude": { + "**/node_modules/**": true, + "**/dist/**": true, + "**/tmp/**": true, + "**/.git/objects/**": true, + "**/.git/subtree-cache/**": true + }, + "search.followSymlinks": false } diff --git a/blaze/wrapper/blaze_wrapper.properties b/blaze/wrapper/blaze_wrapper.properties index 4ee3c7c87..246371e2b 100644 --- a/blaze/wrapper/blaze_wrapper.properties +++ b/blaze/wrapper/blaze_wrapper.properties @@ -1,4 +1,4 @@ -node.version=22.4.1 -bun.version=1.1.26 +node.version=22.9.0 +bun.version=1.1.30 blaze.srcpath=blazebuild -blaze.version=1.0.0-beta.1 +blaze.version=1.0.0-beta.2 diff --git a/blazebuild/.gitignore b/blazebuild/.gitignore index 9b1ee42e8..c2ca96e2d 100644 --- a/blazebuild/.gitignore +++ b/blazebuild/.gitignore @@ -24,6 +24,7 @@ pids _.pid _.seed *.pid.lock +/bun.lockb # Directory for instrumented libs generated by jscoverage/JSCover diff --git a/blazebuild/bun.lockb b/blazebuild/bun.lockb deleted file mode 100755 index 7c2c31434..000000000 Binary files a/blazebuild/bun.lockb and /dev/null differ diff --git a/build_src/src/main/typescript/tasks/CompileTask.ts b/build_src/src/main/typescript/tasks/CompileTask.ts index 572f84bc8..4662d43ae 100644 --- a/build_src/src/main/typescript/tasks/CompileTask.ts +++ b/build_src/src/main/typescript/tasks/CompileTask.ts @@ -8,7 +8,6 @@ import { files, type Awaitable } from "blazebuild"; -import { $ } from "bun"; import path from "path"; @Task({ @@ -24,10 +23,6 @@ class CompileTask extends AbstractTask { if (!buildOutputDirectory) { throw new Error("buildOutputDirectory is not defined in project properties"); } - - await $`mv ${buildOutputDirectory}/out/src ${buildOutputDirectory}/out.tmp`; - await $`rm -rf ${buildOutputDirectory}/out`; - await $`mv ${buildOutputDirectory}/out.tmp ${buildOutputDirectory}/out`; } @TaskDependencyGenerator diff --git a/build_src/src/main/typescript/tasks/CompileTypeScriptTask.ts b/build_src/src/main/typescript/tasks/CompileTypeScriptTask.ts index 0e4967250..6de861a57 100644 --- a/build_src/src/main/typescript/tasks/CompileTypeScriptTask.ts +++ b/build_src/src/main/typescript/tasks/CompileTypeScriptTask.ts @@ -21,6 +21,17 @@ class CompileTypeScriptTask extends AbstractTask { protected override async run(): Promise { IO.newline(); await $`bun x tsc`; + + const buildOutputDirectory = + this.blaze.projectManager.properties.structure?.buildOutputDirectory; + + if (!buildOutputDirectory) { + throw new Error("buildOutputDirectory is not defined in project properties"); + } + + await $`mv ${buildOutputDirectory}/out/src ${buildOutputDirectory}/out.tmp`; + await $`rm -rf ${buildOutputDirectory}/out`; + await $`mv ${buildOutputDirectory}/out.tmp ${buildOutputDirectory}/out`; } @TaskInputGenerator diff --git a/package.json b/package.json index 3ef2c2446..4b9c0d1b5 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "discord.js": "^14.16.3", "dot-object": "^2.1.5", "dotenv": "^16.4.5", - "drizzle-orm": "^0.32.1", + "drizzle-orm": "^0.34.1", "express": "^4.19.2", "express-rate-limit": "^7.3.1", "figlet": "^1.7.0", @@ -104,14 +104,14 @@ "devDependencies": { "@commitlint/cli": "^19.3.0", "@commitlint/config-conventional": "^19.2.2", - "@faker-js/faker": "^8.4.1", + "@faker-js/faker": "^9.0.3", "@onesoftnet/pm2-config": "^0.0.7", "@types/archiver": "^6.0.2", "@types/bcrypt": "^5.0.2", "@types/bun": "latest", "@types/cors": "^2.8.17", "@types/dot-object": "^2.1.6", - "@types/express": "^4.17.21", + "@types/express": "^5.0.0", "@types/figlet": "^1.5.8", "@types/glob": "^8.1.0", "@types/jsonwebtoken": "^9.0.6", @@ -123,7 +123,7 @@ "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^7.16.1", "@typescript-eslint/parser": "^7.16.1", - "@vitest/coverage-v8": "^1.6.0", + "@vitest/coverage-v8": "^2.1.2", "eslint": "^8.57.0", "husky": "^9.1.3", "prettier": "^3.3.3", @@ -132,7 +132,7 @@ "typedoc-plugin-rename-defaults": "^0.7.1", "typescript": "^5.5.3", "typescript-eslint": "^7.16.1", - "vitest": "^1.6.0", + "vitest": "^2.1.2", "zod-to-json-schema": "^3.23.1" } } diff --git a/src/main/typescript/services/GuildSetupService.ts b/src/main/typescript/services/GuildSetupService.ts index 9771db2a7..d4c490401 100644 --- a/src/main/typescript/services/GuildSetupService.ts +++ b/src/main/typescript/services/GuildSetupService.ts @@ -8,6 +8,7 @@ import { fetchChannel, fetchMember } from "@framework/utils/entities"; import { Colors } from "@main/constants/Colors"; import { AIAutoModSchema } from "@main/schemas/AIAutoModSchema"; import { LogEventType } from "@main/schemas/LoggingSchema"; +import { ModerationActionType } from "@main/schemas/ModerationActionSchema"; import type ConfigurationManager from "@main/services/ConfigurationManager"; import { emoji } from "@main/utils/emoji"; import { @@ -35,7 +36,9 @@ import { enum SetupOption { Prefix = "prefix", Logging = "logging", - AIBasedAutoMod = "ai_automod" + AIBasedAutoMod = "ai_automod", + SpamProtection = "spam_protection", + ModerationRules = "moderation_rules" } type SetupState = { @@ -50,7 +53,9 @@ class GuildSetupService extends Service implements HasEventListeners { private static readonly handlers: Record = { [SetupOption.Prefix]: "handlePrefixSetup", [SetupOption.Logging]: "handleLoggingSetup", - [SetupOption.AIBasedAutoMod]: "handleAIAutoModSetup" + [SetupOption.AIBasedAutoMod]: "handleAIAutoModSetup", + [SetupOption.SpamProtection]: "handleSpamProtectionSetup", + [SetupOption.ModerationRules]: "handleModerationRulesSetup" }; private readonly inactivityTimeout: number = 120_000; private readonly setupState: Map<`${string}::${string}::${string}`, SetupState> = new Map(); @@ -143,6 +148,18 @@ class GuildSetupService extends Service implements HasEventListeners { value: SetupOption.AIBasedAutoMod, emoji: "🛡️", description: "Configure AI-powered automatic moderation for this server." + }, + { + label: "Spam Protection", + value: SetupOption.SpamProtection, + emoji: "🛡️", + description: "Configure AI-powered automatic moderation for this server." + }, + { + label: "Moderation Rules", + value: SetupOption.ModerationRules, + emoji: "🛠️", + description: "Configure message moderation rules for this server." } ]) .setMinValues(1) @@ -444,6 +461,22 @@ class GuildSetupService extends Service implements HasEventListeners { interaction ); break; + case "spam_protection_modal": + await this.handleSpamProtectionThresholdsUpdate( + guildId, + interaction.user.id, + message.id, + interaction + ); + break; + case "rule_moderation_word_modal": + await this.handleModerationRuleAddWordModal( + guildId, + interaction.user.id, + message.id, + interaction + ); + break; } return; @@ -472,6 +505,29 @@ class GuildSetupService extends Service implements HasEventListeners { message.id, interaction ); + + return; + } + + if (id === "spam_protection" && subId === "actions_select") { + await this.handleSpamProtectionActionsUpdate( + guildId, + interaction.user.id, + message.id, + interaction + ); + + return; + } + + if (id === "rule_moderation" && subId === "actions_select") { + await this.handleModerationRuleActionsUpdate( + guildId, + interaction.user.id, + message.id, + interaction + ); + return; } } @@ -531,6 +587,54 @@ class GuildSetupService extends Service implements HasEventListeners { } break; + case "spam_protection": + switch (subId) { + case "actions": + await this.handleSpamProtectionActionsUpdateRequest( + guildId, + interaction.user.id, + message.id, + interaction + ); + break; + case "limits": + await this.handleSpamProtectionThresholdsUpdateRequest( + guildId, + interaction.user.id, + message.id, + interaction + ); + break; + default: + done = false; + } + break; + + case "rule_moderation": + switch (subId) { + case "words": + await this.handleModerationRuleWordAdd( + guildId, + interaction.user.id, + message.id, + interaction + ); + break; + + case "create": + await this.handleModerationRuleCreate( + guildId, + interaction.user.id, + message.id, + interaction + ); + break; + default: + done = false; + } + + break; + default: done = false; } @@ -1116,6 +1220,666 @@ class GuildSetupService extends Service implements HasEventListeners { ] }); } + + private spamProtectionButtons(guildId: string, enable = true) { + return new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId(`setup::${guildId}::spam_protection::actions`) + .setLabel("Actions") + .setStyle(ButtonStyle.Secondary) + .setDisabled(!enable), + new ButtonBuilder() + .setCustomId(`setup::${guildId}::spam_protection::limits`) + .setLabel("Thresholds") + .setStyle(ButtonStyle.Secondary) + .setDisabled(!enable) + ); + } + + public async handleModerationRulesSetup( + guildId: string, + id: string, + messageId: string, + interaction: StringSelectMenuInteraction + ) { + await this.defer(interaction); + this.resetState(guildId, id, messageId); + + await this.pushState(guildId, id, messageId, { + embeds: [ + this.embed( + ["Message Moderation Rules"], + "Configure moderation rules for this server.", + { + color: Colors.Primary + } + ) + ], + components: [ + new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId(`setup::${guildId}::rule_moderation::create`) + .setLabel("Create keyword rule") + .setStyle(ButtonStyle.Secondary) + ), + this.selectMenu(guildId, true), + this.buttonRow(guildId, id, messageId, { + back: true, + cancel: true, + finish: false + }) + ] + }); + } + + public async handleModerationRuleAddWordModal( + guildId: string, + id: string, + messageId: string, + interaction: ModalSubmitInteraction + ) { + await this.defer(interaction); + this.resetState(guildId, id, messageId); + + const keywords = + interaction.fields.getTextInputValue("keywords")?.split(/\s+/)?.filter(Boolean) || []; + + if (keywords.length === 0) { + await this.pushState(guildId, id, messageId, { + embeds: [ + this.embed( + ["Message Moderation Rules", "Create Rule", "Add Keywords"], + `${emoji(this.application, "error")} Please provide at least one keyword.`, + { + color: Colors.Danger + } + ) + ], + components: [ + ...this.moderationRuleCreateActionRow(guildId, true), + this.selectMenu(guildId, true), + this.buttonRow(guildId, id, messageId, { + back: true, + cancel: true + }) + ] + }); + + return; + } + + const state = this.setupState.get(`${guildId}::${id}::${messageId}`); + + if (state) { + state.finishable = true; + + this.configManager.config[guildId]!.rule_moderation ??= { + enabled: true, + rules: [], + global_disabled_channels: [] + }; + + const firstWordFilterIndex = this.configManager.config[ + guildId + ]!.rule_moderation.rules.findIndex(rule => rule.type === "word_filter"); + + if (firstWordFilterIndex !== -1) { + ( + this.configManager.config[guildId]!.rule_moderation.rules[ + firstWordFilterIndex + ] as { words: string[] } + ).words = keywords; + } else { + this.configManager.config[guildId]!.rule_moderation.rules.push({ + type: "word_filter", + words: keywords, + actions: [], + bail: false, + bypasses: [], + enabled: true, + is_bypasser: false, + name: "Word Filter", + mode: "normal", + normalize: true, + tokens: [], + exceptions: {}, + for: {} + }); + } + + await this.configManager.write({ guild: true, system: false }); + await this.configManager.load(); + + await this.pushState(guildId, id, messageId, { + embeds: [ + this.embed( + ["Message Moderation Rules", "Create Rule", "Add Keywords"], + `${emoji(this.application, "check")} Successfully added keywords for this rule.`, + { + color: Colors.Success + } + ) + ], + components: [ + ...this.moderationRuleCreateActionRow(guildId), + this.selectMenu(guildId, true), + this.buttonRow(guildId, id, messageId, { + back: true, + cancel: true, + finish: true + }) + ] + }); + } + } + + public async handleModerationRuleActionsUpdate( + guildId: string, + id: string, + messageId: string, + interaction: StringSelectMenuInteraction + ) { + await this.defer(interaction); + this.resetState(guildId, id, messageId); + + const actions = interaction.values as Array< + "delete_message" | "mute" | "kick" | "ban" | "clear" + >; + + if (!actions.length) { + await this.pushState(guildId, id, messageId, { + embeds: [ + this.embed( + ["Message Moderation Rules", "Create Rule", "Actions"], + `${emoji(this.application, "error")} Please select at least one action.`, + { + color: Colors.Danger + } + ) + ], + components: [ + ...this.moderationRuleCreateActionRow(guildId, true), + this.selectMenu(guildId, true), + this.buttonRow(guildId, id, messageId, { + back: true, + cancel: true + }) + ] + }); + + return; + } + + const state = this.setupState.get(`${guildId}::${id}::${messageId}`); + + if (state) { + state.finishable = true; + + this.configManager.config[guildId]!.rule_moderation ??= { + enabled: true, + rules: [], + global_disabled_channels: [] + }; + + const finalActions = actions.map(action => ({ + type: action, + duration: action === "mute" ? 2 * 60 * 60 * 1000 : undefined, + notify: true, + reason: "AutoMod: Posted a blocked word" + })) as ModerationActionType[]; + + const firstWordFilterIndex = this.configManager.config[ + guildId + ]!.rule_moderation.rules.findIndex(rule => rule.type === "word_filter"); + + if (firstWordFilterIndex !== -1) { + this.configManager.config[guildId]!.rule_moderation.rules[ + firstWordFilterIndex + ].actions = finalActions; + } else { + this.configManager.config[guildId]!.rule_moderation.rules.push({ + type: "word_filter", + words: [], + actions: finalActions, + bail: false, + bypasses: [], + enabled: true, + is_bypasser: false, + name: "Word Filter", + mode: "normal", + normalize: true, + tokens: [], + exceptions: {}, + for: {} + }); + } + + await this.configManager.write({ guild: true, system: false }); + await this.configManager.load(); + + await this.pushState(guildId, id, messageId, { + embeds: [ + this.embed( + ["Message Moderation Rules", "Create Rule", "Actions"], + `${emoji(this.application, "check")} Successfully added actions for this rule.`, + { + color: Colors.Success + } + ) + ], + components: [ + ...this.moderationRuleCreateActionRow(guildId), + this.selectMenu(guildId, true), + this.buttonRow(guildId, id, messageId, { + back: true, + cancel: true, + finish: true + }) + ] + }); + } + } + + public async handleModerationRuleWordAdd( + guildId: string, + id: string, + messageId: string, + interaction: ButtonInteraction + ) { + const state = this.setupState.get(`${guildId}::${id}::${messageId}`); + + if (!state) { + return; + } + + this.ping(guildId, id, messageId); + + const modal = new ModalBuilder() + .setTitle("Add Keywords") + .setCustomId(`setup::${guildId}::rule_moderation_word_modal`) + .setComponents( + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setLabel("Keywords") + .setCustomId("keywords") + .setPlaceholder("Enter keywords to add (separate with spaces)") + .setMinLength(1) + .setRequired(true) + .setStyle(TextInputStyle.Paragraph) + ) + ); + + await interaction.showModal(modal).catch(this.application.logger.error); + } + + private moderationRuleCreateActionRow(guildId: string, disabled = false) { + return [ + new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId(`setup::${guildId}::rule_moderation::words`) + .setLabel("Add keywords") + .setStyle(ButtonStyle.Secondary) + .setDisabled(disabled) + ), + new ActionRowBuilder().addComponents( + new StringSelectMenuBuilder() + .setCustomId(`setup::${guildId}::rule_moderation::actions_select`) + .setPlaceholder("Select actions") + .setMinValues(1) + .setMaxValues(4) + .addOptions([ + { + label: "Delete Flagged Message", + value: "delete_message" + }, + { + label: "Mute for 2 hours", + value: "mute" + }, + { + label: "Kick", + value: "kick" + }, + { + label: "Ban", + value: "ban" + }, + { + label: "Clear Messages", + value: "clear" + } + ]) + .setDisabled(disabled) + ) + ]; + } + + public async handleModerationRuleCreate( + guildId: string, + id: string, + messageId: string, + interaction: ButtonInteraction + ) { + await this.defer(interaction); + this.resetState(guildId, id, messageId); + + await this.pushState(guildId, id, messageId, { + embeds: [ + this.embed( + ["Message Moderation Rules", "Create Rule"], + "Configure moderation rules for this server.", + { + color: Colors.Primary + } + ) + ], + components: [ + ...this.moderationRuleCreateActionRow(guildId), + this.selectMenu(guildId, true), + this.buttonRow(guildId, id, messageId, { + back: true, + cancel: true, + finish: false + }) + ] + }); + } + + public async handleSpamProtectionSetup( + guildId: string, + id: string, + messageId: string, + interaction: StringSelectMenuInteraction + ) { + await this.defer(interaction); + this.resetState(guildId, id, messageId); + + await this.pushState(guildId, id, messageId, { + embeds: [ + this.embed(["Spam Protection"], "Please configure the following options.", { + color: Colors.Primary + }) + ], + components: [ + this.spamProtectionButtons(guildId), + this.selectMenu(guildId, true), + this.buttonRow(guildId, id, messageId, { + back: true, + cancel: true, + finish: false + }) + ] + }); + } + + private async handleSpamProtectionActionsUpdate( + guildId: string, + id: string, + messageId: string, + interaction: StringSelectMenuInteraction + ) { + await this.defer(interaction); + this.resetState(guildId, id, messageId); + + const actions = interaction.values as Array<"mute" | "kick" | "ban" | "clear">; + + if (!actions.length) { + await this.pushState(guildId, id, messageId, { + embeds: [ + this.embed( + ["Spam Protection", "Actions"], + `${emoji(this.application, "error")} Please select at least one action.`, + { + color: Colors.Danger + } + ) + ], + components: [ + this.spamProtectionButtons(guildId, false), + this.selectMenu(guildId, true), + this.buttonRow(guildId, id, messageId, { + back: true, + cancel: true, + finish: false + }) + ] + }); + + return; + } + + const state = this.setupState.get(`${guildId}::${id}::${messageId}`); + + if (state) { + state.finishable = true; + } + + this.configManager.config[guildId]!.antispam ??= { + enabled: true, + actions: [ + { + type: "mute", + duration: 2 * 60 * 60 * 1000, + notify: true, + reason: "AutoMod: Spam Detected" + } + ], + limit: 5, + timeframe: 10000, + channels: { list: [], mode: "exclude" } + }; + + this.configManager.config[guildId]!.antispam.actions = actions.map(action => ({ + type: action, + duration: action === "mute" ? 2 * 60 * 60 * 1000 : undefined, + notify: true, + reason: "AutoMod: Spam Detected" + })) as ModerationActionType[]; + + await this.configManager.write({ guild: true, system: false }); + await this.configManager.load(); + + await this.pushState(guildId, id, messageId, { + embeds: [ + this.embed( + ["Spam Protection", "Actions"], + `${emoji(this.application, "check")} Successfully updated the spam protection actions.`, + { + color: Colors.Success + } + ) + ], + components: [ + this.spamProtectionButtons(guildId, true), + this.selectMenu(guildId, true), + this.buttonRow(guildId, id, messageId, { + back: true, + cancel: true, + finish: true + }) + ] + }); + } + + private async handleSpamProtectionActionsUpdateRequest( + guildId: string, + id: string, + messageId: string, + interaction: ButtonInteraction + ) { + await this.defer(interaction); + this.resetState(guildId, id, messageId); + + await this.pushState(guildId, id, messageId, { + embeds: [ + this.embed(["Spam Protection", "Actions"], "Please select the actions.", { + color: Colors.Primary + }) + ], + components: [ + new ActionRowBuilder().addComponents( + new StringSelectMenuBuilder() + .setCustomId(`setup::${guildId}::spam_protection::actions_select`) + .setPlaceholder("Select actions") + .setMinValues(1) + .setMaxValues(4) + .addOptions([ + { + label: "Mute for 2 hours", + value: "mute" + }, + { + label: "Kick", + value: "kick" + }, + { + label: "Ban", + value: "ban" + }, + { + label: "Clear Messages", + value: "clear" + } + ]) + ), + this.spamProtectionButtons(guildId, false), + this.selectMenu(guildId, true), + this.buttonRow(guildId, id, messageId, { + back: true, + cancel: true, + finish: false + }) + ] + }); + } + + private async handleSpamProtectionThresholdsUpdateRequest( + guildId: string, + id: string, + messageId: string, + interaction: ButtonInteraction + ) { + const state = this.setupState.get(`${guildId}::${id}::${messageId}`); + + if (!state) { + return; + } + + const modal = new ModalBuilder() + .setTitle("Spam Protection Thresholds") + .setCustomId(`setup::${guildId}::spam_protection_modal`) + .setComponents( + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setLabel("Threshold") + .setCustomId("threshold") + .setPlaceholder("Enter a threshold") + .setRequired(true) + .setStyle(TextInputStyle.Short) + ), + new ActionRowBuilder().addComponents( + new TextInputBuilder() + .setLabel("Timeframe") + .setCustomId("timeframe") + .setPlaceholder("Timeframe in milliseconds") + .setRequired(true) + .setStyle(TextInputStyle.Short) + ) + ); + + await interaction.showModal(modal).catch(this.application.logger.error); + } + + private async handleSpamProtectionThresholdsUpdate( + guildId: string, + id: string, + messageId: string, + interaction: ModalSubmitInteraction + ) { + await this.defer(interaction); + this.resetState(guildId, id, messageId); + + const threshold = interaction.fields.getTextInputValue("threshold"); + const timeframe = interaction.fields.getTextInputValue("timeframe"); + + if ( + !threshold || + !timeframe || + isNaN(+threshold) || + isNaN(+timeframe) || + -(-threshold) <= 0 || + -(-timeframe) <= 0 + ) { + await this.pushState(guildId, id, messageId, { + embeds: [ + this.embed( + ["Spam Protection", "Thresholds"], + `${emoji(this.application, "error")} Please provide a valid threshold and timeframe.`, + { + color: Colors.Danger + } + ) + ], + components: [ + this.spamProtectionButtons(guildId, false), + this.selectMenu(guildId, true), + this.buttonRow(guildId, id, messageId, { + back: true, + cancel: true, + finish: false + }) + ] + }); + + return; + } + + const state = this.setupState.get(`${guildId}::${id}::${messageId}`); + + if (state) { + state.finishable = true; + } + + this.configManager.config[guildId]!.antispam ??= { + enabled: true, + actions: [ + { + type: "mute", + duration: 2 * 60 * 60 * 1000, + notify: true, + reason: "AutoMod: Spam Detected" + } + ], + limit: +threshold, + timeframe: +timeframe, + channels: { list: [], mode: "exclude" } + }; + + this.configManager.config[guildId]!.antispam.limit = +threshold; + this.configManager.config[guildId]!.antispam.timeframe = +timeframe; + await this.configManager.write({ guild: true, system: false }); + + await this.pushState(guildId, id, messageId, { + embeds: [ + this.embed( + ["Spam Protection", "Thresholds"], + `${emoji(this.application, "check")} Successfully updated the spam protection thresholds.`, + { + color: Colors.Success + } + ) + ], + components: [ + this.spamProtectionButtons(guildId, true), + this.selectMenu(guildId, true), + this.buttonRow(guildId, id, messageId, { + back: true, + cancel: true, + finish: true + }) + ] + }); + } } export default GuildSetupService; diff --git a/tsconfig.json b/tsconfig.json index 277a5d2fe..99f0ee5b8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -33,12 +33,7 @@ "@root/*": ["./*"] } }, - "include": [ - "src/main/typescript/types/global/globals.d.ts", - "src/**/*.ts", - "drizzle.config.ts", - "commitlint.config.ts" - ], + "include": ["src/main/typescript/types/global/globals.d.ts", "src/**/*.ts"], "exclude": [ "./backup", "./tmp", @@ -58,6 +53,8 @@ "./build_src/**", "./node_modules", "./storage", - "build.blaze.ts" + "build.blaze.ts", + "drizzle.config.ts", + "commitlint.config.ts" ] }