diff --git a/assets/openapi.json b/assets/openapi.json index ee6ccf9e2..b14e83480 100644 Binary files a/assets/openapi.json and b/assets/openapi.json differ diff --git a/assets/schemas.json b/assets/schemas.json index d30f608f8..5d46e5e5c 100644 Binary files a/assets/schemas.json and b/assets/schemas.json differ diff --git a/scripts/schemaExclusions.json b/scripts/schemaExclusions.json index 13775ed62..6c5ad24f2 100644 --- a/scripts/schemaExclusions.json +++ b/scripts/schemaExclusions.json @@ -7,7 +7,9 @@ ], "includeRe": [ "^MessageComponentType\\..*", - "^InteractionCallbackType\\..*" + "^InteractionCallbackType\\..*", + "^AutomodRuleTriggerType\\..*", + "^AutomodRuleActionType\\..*" ], "manual": [ "DefaultSchema", diff --git a/src/api/routes/guilds/#guild_id/auto-moderation/rules.ts b/src/api/routes/guilds/#guild_id/auto-moderation/rules.ts index 2e1d71d2e..e5dc29e90 100644 --- a/src/api/routes/guilds/#guild_id/auto-moderation/rules.ts +++ b/src/api/routes/guilds/#guild_id/auto-moderation/rules.ts @@ -47,7 +47,7 @@ router.get( router.post( "/", route({ - // requestBody: "AutomodRuleSchema", + requestBody: "AutomodRuleSchema", permission: "MANAGE_GUILD", responses: { 200: { @@ -63,25 +63,26 @@ router.post( }), async (req: Request, res: Response) => { const { guild_id } = req.params as { [key: string]: string }; - if (req.user_id !== req.body.creator_id) throw new HTTPError("You can't create a rule for someone else", 403); - if (guild_id !== req.body.guild_id) throw new HTTPError("You can't create a rule for another guild", 403); - - if (req.body.id) { - throw new HTTPError("You can't specify an ID for a new rule", 400); - } + if ("id" in req.body) throw new HTTPError("You can't specify an ID for a new rule", 400); + if ("user_id" in req.body && req.user_id !== req.body.creator_id) throw new HTTPError("You can't create a rule for someone else", 403); + if ("guild_id" in req.body && guild_id !== req.body.guild_id) throw new HTTPError("You can't create a rule for another guild", 403); const data = req.body as AutomodRuleSchema; const created = AutomodRule.create({ + ...{ exempt_roles: [], exempt_channels: [] }, ...data, creator: await User.findOneOrFail({ - where: { id: data.creator_id }, + where: { id: req.user_id }, }), guild_id: req.params.guild_id as string, }); + created.position = await AutomodRule.count({ where: { guild_id } }); + const savedRule = await AutomodRule.save(created); + console.log(savedRule); return res.json(savedRule); }, ); @@ -89,7 +90,7 @@ router.post( router.patch( "/:rule_id", route({ - // requestBody: "AutomodRuleSchema + requestBody: "AutomodRuleSchema", permission: "MANAGE_GUILD", responses: { 200: { @@ -110,9 +111,13 @@ router.patch( }); if (!rule) throw new HTTPError("Automod rule not found", 404); + if ("id" in req.body) throw new HTTPError("You can't specify an ID for a rule", 400); + if ("user_id" in req.body && req.user_id !== req.body.creator_id) throw new HTTPError("You can't modify a rule for someone else", 403); + if ("guild_id" in req.body && req.params.guild_id !== req.body.guild_id) throw new HTTPError("You can't modify a rule for another guild", 403); + const data = req.body as AutomodRuleSchema; - AutomodRule.merge(rule, data); + AutomodRule.merge(rule, data); const savedRule = await AutomodRule.save(rule); return res.json(savedRule); }, diff --git a/src/database/entities/AutomodRule.ts b/src/database/entities/AutomodRule.ts index 5f74ee9b6..93d60cd7b 100644 --- a/src/database/entities/AutomodRule.ts +++ b/src/database/entities/AutomodRule.ts @@ -17,7 +17,7 @@ */ import { BaseClass } from "./BaseClass"; -import { Entity, JoinColumn, ManyToOne, Column } from "typeorm"; +import { Entity, JoinColumn, ManyToOne, Column, RelationId } from "typeorm"; import { User } from "./User"; import { AutomodAction, AutomodRuleEventType, AutomodRuleTriggerMetadata, AutomodRuleTriggerType } from "@spacebar/schemas"; @@ -25,6 +25,10 @@ import { AutomodAction, AutomodRuleEventType, AutomodRuleTriggerMetadata, Automo name: "automod_rules", }) export class AutomodRule extends BaseClass { + @Column() + @RelationId((automod: AutomodRule) => automod.creator) + creator_id: string; + @JoinColumn({ name: "creator_id", foreignKeyConstraintName: "FK_automod_rule_creator_id" }) @ManyToOne(() => User, { onDelete: "CASCADE" }) creator: User; @@ -57,8 +61,7 @@ export class AutomodRule extends BaseClass { type: "jsonb", nullable: true, }) - trigger_metadata?: // this is null for "Block suspected spam content" - AutomodRuleTriggerMetadata; + trigger_metadata?: AutomodRuleTriggerMetadata | undefined; // may be undefined for trigger types that have no metadata @Column({ type: "jsonb", diff --git a/src/schemas/api/guilds/Automod.ts b/src/schemas/api/guilds/Automod.ts index 1c663776e..7f990edeb 100644 --- a/src/schemas/api/guilds/Automod.ts +++ b/src/schemas/api/guilds/Automod.ts @@ -1,6 +1,6 @@ /* Spacebar: A FOSS re-implementation and extension of the Discord.com backend. - Copyright (C) 2025 Spacebar and Spacebar Contributors + Copyright (C) 2026 Spacebar and Spacebar Contributors This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published @@ -18,21 +18,59 @@ import { Snowflake } from "../../Identifiers"; -export type AutomodRuleTriggerMetadata = AutomodMentionSpamRule | AutomodSuspectedSpamRule | AutomodCommonlyFlaggedWordsRule | AutomodCustomWordsRule; +interface BaseAutomodRuleSchema { + enabled: boolean; + event_type: AutomodRuleEventType; + exempt_channels?: Snowflake[]; + exempt_roles?: Snowflake[]; + name: string; + actions: AutomodAction[]; +} -export class AutomodMentionSpamRule { +type WithTrigger = BaseAutomodRuleSchema & { + trigger_type: T; +} & M extends never + ? { trigger_metadata?: never } + : { trigger_metadata: M }; + +type KeywordRule = WithTrigger; +type HarmfulLinkRule = WithTrigger; +type SpamRule = WithTrigger; +type KeywordPresetRule = WithTrigger; +type MentionSpamRule = WithTrigger; +type UserProfileRule = WithTrigger; +type GuildPolicyRule = WithTrigger; + +export type AutomodRuleSchema = KeywordRule | HarmfulLinkRule | SpamRule | KeywordPresetRule | MentionSpamRule | UserProfileRule | GuildPolicyRule; + +export type AutomodRuleSchemaWithId = AutomodRuleSchema & { + id: Snowflake; + creator_id: Snowflake; + guild_id: Snowflake; + position: number; + exempt_roles?: Snowflake[]; +}; + +export type AutomodRuleTriggerMetadata = AutomodCustomWordsRuleMetadata | AutomodKeywordPresetRuleMetadata | AutomodMentionSpamRuleMetadata | AutomodUserProfileRuleMetadata; + +export interface AutomodMentionSpamRuleMetadata { mention_total_limit: number; mention_raid_protection_enabled: boolean; } -export class AutomodSuspectedSpamRule {} - -export class AutomodCommonlyFlaggedWordsRule { +export interface AutomodKeywordPresetRuleMetadata { allow_list: string[]; presets: AutomodKeywordPresetType[]; } -export class AutomodCustomWordsRule { +export interface AutomodCustomWordsRuleMetadata { + allow_list: string[]; + keyword_filter: string[]; + regex_patterns: string[]; +} + +// the same as custom words, but separate to allow divergence +export interface AutomodUserProfileRuleMetadata { allow_list: string[]; keyword_filter: string[]; regex_patterns: string[]; @@ -42,6 +80,7 @@ export enum AutomodRuleEventType { MESSAGE_SEND = 1, GUILD_MEMBER_EVENT = 2, } + export enum AutomodRuleTriggerType { KEYWORD = 1, HARMFUL_LINK = 2, @@ -68,27 +107,19 @@ export enum AutomodRuleActionType { export type AutomodAction = | { type: AutomodRuleActionType.BLOCK_MESSAGE; - metadata: { - custom_message?: string; - }; + metadata: { custom_message?: string }; } | { type: AutomodRuleActionType.SEND_ALERT_MESSAGE; - metadata: { - channel_id: Snowflake; - }; + metadata: { channel_id: Snowflake }; } | { type: AutomodRuleActionType.TIMEOUT_USER; - metadata: { - duration_seconds: number; - }; + metadata: { duration_seconds: number }; } | { type: AutomodRuleActionType.QUARANTINE_USER; - metadata: { - duration_seconds: number; - }; + metadata: { duration_seconds: number }; }; // TODO diff --git a/src/schemas/uncategorised/AutomodRuleSchema.ts b/src/schemas/uncategorised/AutomodRuleSchema.ts deleted file mode 100644 index 55625bf2e..000000000 --- a/src/schemas/uncategorised/AutomodRuleSchema.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - Spacebar: A FOSS re-implementation and extension of the Discord.com backend. - Copyright (C) 2024 Spacebar and Spacebar Contributors - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -export interface AutomodMentionSpamRuleSchema { - mention_total_limit: number; - mention_raid_protection_enabled: boolean; -} - -// export interface AutomodSuspectedSpamRuleSchema {} -export type AutomodSuspectedSpamRuleSchema = Record; // hack to represent an empty object - -export interface AutomodCommonlyFlaggedWordsRuleSchema { - allow_list: [string]; - presets: [number]; -} - -export interface AutomodCustomWordsRuleSchema { - allow_list: [string]; - keyword_filter: [string]; - regex_patterns: [string]; -} - -export interface AutomodRuleSchema { - creator_id: string; - enabled: boolean; - event_type: number; // No idea... - exempt_channels: [string]; - exempt_roles: [string]; - guild_id: string; - name: string; - position: number; - trigger_type: number; //AutomodTriggerTypes - trigger_metadata: AutomodMentionSpamRuleSchema | AutomodSuspectedSpamRuleSchema | AutomodCommonlyFlaggedWordsRuleSchema | AutomodCustomWordsRuleSchema; -} - -export interface AutomodRuleSchemaWithId extends AutomodRuleSchema { - id: string; -} diff --git a/src/schemas/uncategorised/index.ts b/src/schemas/uncategorised/index.ts index ee1b01f57..d79e7fb6f 100644 --- a/src/schemas/uncategorised/index.ts +++ b/src/schemas/uncategorised/index.ts @@ -18,7 +18,6 @@ export * from "./ActivitySchema"; export * from "./ApplicationAuthorizeSchema"; -export * from "./AutomodRuleSchema"; export * from "./BackupCodesChallengeSchema"; export * from "./BanCreateSchema"; export * from "./BanModeratorSchema";