diff --git a/src/api/routes/guilds/#guild_id/auto-moderation/rules.ts b/src/api/routes/guilds/#guild_id/auto-moderation/rules.ts
new file mode 100644
index 00000000..c09c93c9
--- /dev/null
+++ b/src/api/routes/guilds/#guild_id/auto-moderation/rules.ts
@@ -0,0 +1,147 @@
+/*
+ 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 .
+*/
+
+import { route } from "@spacebar/api";
+import { User, AutomodRuleSchema, AutomodRule } from "@spacebar/util";
+import { Request, Response, Router } from "express";
+import { HTTPError } from "lambert-server";
+
+const router: Router = Router();
+
+router.get(
+ "/",
+ route({
+ permission: ["MANAGE_GUILD"],
+ responses: {
+ 200: {
+ body: "AutomodRuleSchemaWithId[]",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ const rules = await AutomodRule.find({ where: { guild_id } });
+ return res.json(rules);
+ },
+);
+
+router.post(
+ "/",
+ route({
+ // requestBody: "AutomodRuleSchema",
+ permission: ["MANAGE_GUILD"],
+ responses: {
+ 200: {
+ body: "AutomodRuleSchemaWithId",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ 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);
+ }
+
+ const data = req.body as AutomodRuleSchema;
+
+ const created = AutomodRule.create({
+ creator: await User.findOneOrFail({
+ where: { id: data.creator_id },
+ }),
+ ...data,
+ });
+
+ const savedRule = await AutomodRule.save(created);
+ return res.json(savedRule);
+ },
+);
+
+router.patch(
+ "/:rule_id",
+ route({
+ // requestBody: "AutomodRuleSchema
+ permission: ["MANAGE_GUILD"],
+ responses: {
+ 200: {
+ body: "AutomodRuleSchemaWithId",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { rule_id } = req.params;
+ const rule = await AutomodRule.findOneOrFail({
+ where: { id: rule_id },
+ });
+
+ const data = req.body as AutomodRuleSchema;
+
+ AutomodRule.merge(rule, data);
+ const savedRule = await AutomodRule.save(rule);
+ return res.json(savedRule);
+ },
+);
+
+router.delete(
+ "/:rule_id",
+ route({
+ permission: ["MANAGE_GUILD"],
+ responses: {
+ 204: {},
+ 403: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { rule_id } = req.params;
+ await AutomodRule.delete({ id: rule_id });
+ return res.status(204).send();
+ },
+);
+
+export default router;
diff --git a/src/util/entities/AutomodRule.ts b/src/util/entities/AutomodRule.ts
new file mode 100644
index 00000000..7d983ee2
--- /dev/null
+++ b/src/util/entities/AutomodRule.ts
@@ -0,0 +1,88 @@
+/*
+ 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 .
+*/
+
+import { dbEngine, User } from "@spacebar/util";
+import { BaseClass } from "./BaseClass";
+import { Entity, JoinColumn, ManyToOne, Column } from "typeorm";
+
+export class AutomodMentionSpamRule {
+ mention_total_limit: number;
+ mention_raid_protection_enabled: boolean;
+}
+
+export class AutomodSuspectedSpamRule {}
+
+export class AutomodCommonlyFlaggedWordsRule {
+ allow_list: [string];
+ presets: [number];
+}
+
+export class AutomodCustomWordsRule {
+ allow_list: [string];
+ keyword_filter: [string];
+ regex_patterns: [string];
+}
+
+@Entity({
+ name: "automod_rules",
+ engine: dbEngine,
+})
+export class AutomodRule extends BaseClass {
+ @JoinColumn({ name: "creator_id" })
+ @ManyToOne(() => User, { onDelete: "CASCADE" })
+ creator: User;
+
+ @Column()
+ enabled: boolean;
+
+ @Column()
+ event_type: number; // No idea...
+
+ @Column({ type: "simple-array" })
+ exempt_channels: [string];
+
+ @Column({ type: "simple-array" })
+ exempt_roles: [string];
+
+ @Column()
+ guild_id: string;
+
+ @Column()
+ name: string;
+
+ @Column()
+ position: number;
+
+ @Column()
+ trigger_type: number;
+
+ @Column({
+ type: "simple-json",
+ nullable: true,
+ })
+ trigger_metadata?: // this is null for "Block suspected spam content"
+ | AutomodMentionSpamRule
+ | AutomodSuspectedSpamRule
+ | AutomodCommonlyFlaggedWordsRule
+ | AutomodCustomWordsRule;
+
+ @Column({
+ type: "simple-json",
+ })
+ actions: { type: number; metadata: unknown }[];
+}
diff --git a/src/util/entities/index.ts b/src/util/entities/index.ts
index 6f132084..dd967ce5 100644
--- a/src/util/entities/index.ts
+++ b/src/util/entities/index.ts
@@ -1,6 +1,6 @@
/*
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
- Copyright (C) 2023 Spacebar and Spacebar Contributors
+ 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
@@ -19,6 +19,7 @@
export * from "./Application";
export * from "./Attachment";
export * from "./AuditLog";
+export * from "./AutomodRule";
export * from "./BackupCodes";
export * from "./Badge";
export * from "./Ban";
diff --git a/src/util/schemas/AutomodRuleSchema.ts b/src/util/schemas/AutomodRuleSchema.ts
new file mode 100644
index 00000000..55cf2027
--- /dev/null
+++ b/src/util/schemas/AutomodRuleSchema.ts
@@ -0,0 +1,56 @@
+/*
+ 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 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/util/schemas/index.ts b/src/util/schemas/index.ts
index f19eef0d..a7aaa1fa 100644
--- a/src/util/schemas/index.ts
+++ b/src/util/schemas/index.ts
@@ -1,6 +1,6 @@
/*
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
- Copyright (C) 2023 Spacebar and Spacebar Contributors
+ 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
@@ -21,6 +21,7 @@ export * from "./ActivitySchema";
export * from "./ApplicationAuthorizeSchema";
export * from "./ApplicationCreateSchema";
export * from "./ApplicationModifySchema";
+export * from "./AutomodRuleSchema";
export * from "./BackupCodesChallengeSchema";
export * from "./BanCreateSchema";
export * from "./BanModeratorSchema";
diff --git a/src/util/util/Constants.ts b/src/util/util/Constants.ts
index df4501fc..5f89eab7 100644
--- a/src/util/util/Constants.ts
+++ b/src/util/util/Constants.ts
@@ -1,6 +1,6 @@
/*
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
- Copyright (C) 2023 Spacebar and Spacebar Contributors
+ 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
@@ -320,6 +320,20 @@ export const ClientApplicationAssetTypes = {
BIG: 2,
};
+export const AutomodActionTypes = {
+ BLOCK_MESSAGE: 1,
+ SEND_ALERT: 2,
+ TIMEOUT_MEMBER: 3,
+};
+
+export const AutomodTriggerTypes = {
+ CUSTOM_WORDS: 1,
+ UNKNOWN_2: 2,
+ SUSPECTED_SPAM_CONTENT: 3,
+ COMMONLY_FLAGGED_WORDS: 4,
+ MENTION_SPAM: 5,
+};
+
export const Colors = {
DEFAULT: 0x000000,
WHITE: 0xffffff,