mirror of
https://github.com/discordeno/discordeno.git
synced 2026-09-17 08:47:22 +00:00
Merge branch 'main' of https://github.com/discordeno/discordeno into main
This commit is contained in:
@@ -71,6 +71,8 @@ import { StickerPack, transformSticker, transformStickerPack } from "./transform
|
||||
import { GetGatewayBot, transformGatewayBot } from "./transformers/gatewayBot.ts";
|
||||
import {
|
||||
DiscordApplicationCommandOptionChoice,
|
||||
DiscordAutoModerationActionExecution,
|
||||
DiscordAutoModerationRule,
|
||||
DiscordEmoji,
|
||||
DiscordGatewayPayload,
|
||||
DiscordInteractionDataOption,
|
||||
@@ -139,6 +141,11 @@ import { transformEmbedToDiscordEmbed } from "./transformers/reverse/embed.ts";
|
||||
import { transformComponentToDiscordComponent } from "./transformers/reverse/component.ts";
|
||||
import { getBotIdFromToken, removeTokenPrefix } from "./util/token.ts";
|
||||
import { CreateShardManager } from "./gateway/manager/shardManager.ts";
|
||||
import { AutoModerationRule, transformAutoModerationRule } from "./transformers/automodRule.ts";
|
||||
import {
|
||||
AutoModerationActionExecution,
|
||||
transformAutoModerationActionExecution,
|
||||
} from "./transformers/automodActionExecution.ts";
|
||||
|
||||
export function createBot(options: CreateBotOptions): Bot {
|
||||
const bot = {
|
||||
@@ -203,6 +210,10 @@ export function createEventHandlers(
|
||||
|
||||
return {
|
||||
debug: events.debug ?? ignore,
|
||||
automodRuleCreate: events.automodRuleCreate ?? ignore,
|
||||
automodRuleUpdate: events.automodRuleUpdate ?? ignore,
|
||||
automodRuleDelete: events.automodRuleDelete ?? ignore,
|
||||
automodActionExecution: events.automodActionExecution ?? ignore,
|
||||
threadCreate: events.threadCreate ?? ignore,
|
||||
threadDelete: events.threadDelete ?? ignore,
|
||||
threadMemberUpdate: events.threadMemberUpdate ?? ignore,
|
||||
@@ -388,6 +399,8 @@ export interface Transformers {
|
||||
};
|
||||
snowflake: (snowflake: string) => bigint;
|
||||
gatewayBot: (payload: DiscordGetGatewayBot) => GetGatewayBot;
|
||||
automodRule: (bot: Bot, payload: DiscordAutoModerationRule) => AutoModerationRule;
|
||||
automodActionExecution: (bot: Bot, payload: DiscordAutoModerationActionExecution) => AutoModerationActionExecution;
|
||||
channel: (bot: Bot, payload: { channel: DiscordChannel } & { guildId?: bigint }) => Channel;
|
||||
guild: (bot: Bot, payload: { guild: DiscordGuild } & { shardId: number }) => Guild;
|
||||
user: (bot: Bot, payload: DiscordUser) => User;
|
||||
@@ -437,6 +450,8 @@ export function createTransformers(options: Partial<Transformers>) {
|
||||
embed: options.reverse?.embed || transformEmbedToDiscordEmbed,
|
||||
component: options.reverse?.component || transformComponentToDiscordComponent,
|
||||
},
|
||||
automodRule: options.automodRule || transformAutoModerationRule,
|
||||
automodActionExecution: options.automodActionExecution || transformAutoModerationActionExecution,
|
||||
activity: options.activity || transformActivity,
|
||||
application: options.application || transformApplication,
|
||||
attachment: options.attachment || transformAttachment,
|
||||
@@ -482,6 +497,10 @@ export function createTransformers(options: Partial<Transformers>) {
|
||||
|
||||
export interface EventHandlers {
|
||||
debug: (text: string, ...args: any[]) => unknown;
|
||||
automodRuleCreate: (bot: Bot, rule: AutoModerationRule) => unknown;
|
||||
automodRuleUpdate: (bot: Bot, rule: AutoModerationRule) => unknown;
|
||||
automodRuleDelete: (bot: Bot, rule: AutoModerationRule) => unknown;
|
||||
automodActionExecution: (bot: Bot, payload: AutoModerationActionExecution) => unknown;
|
||||
threadCreate: (bot: Bot, thread: Channel) => unknown;
|
||||
threadDelete: (bot: Bot, thread: Channel) => unknown;
|
||||
threadMemberUpdate: (bot: Bot, payload: {
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { Bot } from "../../../bot.ts";
|
||||
import { DiscordAutoModerationActionExecution, DiscordGatewayPayload } from "../../../types/discord.ts";
|
||||
|
||||
/** Requires the MANAGE_GUILD permission. */
|
||||
export function handleAutoModerationActionExecution(bot: Bot, data: DiscordGatewayPayload, shardId: number) {
|
||||
const payload = data.d as DiscordAutoModerationActionExecution;
|
||||
bot.events.automodActionExecution(bot, bot.transformers.automodActionExecution(bot, payload));
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { Bot } from "../../../bot.ts";
|
||||
import { DiscordAutoModerationRule, DiscordGatewayPayload } from "../../../types/discord.ts";
|
||||
|
||||
/** Requires the MANAGE_GUILD permission. */
|
||||
export function handleAutoModerationRuleCreate(bot: Bot, data: DiscordGatewayPayload, shardId: number) {
|
||||
const payload = data.d as DiscordAutoModerationRule;
|
||||
bot.events.automodRuleCreate(bot, bot.transformers.automodRule(bot, payload));
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { Bot } from "../../../bot.ts";
|
||||
import { DiscordAutoModerationRule, DiscordGatewayPayload } from "../../../types/discord.ts";
|
||||
|
||||
/** Requires the MANAGE_GUILD permission. */
|
||||
export function handleAutoModerationRuleDelete(bot: Bot, data: DiscordGatewayPayload, shardId: number) {
|
||||
const payload = data.d as DiscordAutoModerationRule;
|
||||
bot.events.automodRuleDelete(bot, bot.transformers.automodRule(bot, payload));
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { Bot } from "../../../bot.ts";
|
||||
import { DiscordAutoModerationRule, DiscordGatewayPayload } from "../../../types/discord.ts";
|
||||
|
||||
/** Requires the MANAGE_GUILD permission. */
|
||||
export function handleAutoModerationRuleUpdate(bot: Bot, data: DiscordGatewayPayload, shardId: number) {
|
||||
const payload = data.d as DiscordAutoModerationRule;
|
||||
bot.events.automodRuleUpdate(bot, bot.transformers.automodRule(bot, payload));
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import { Bot } from "../../../bot.ts";
|
||||
import {
|
||||
AutoModerationActionType,
|
||||
AutoModerationEventTypes,
|
||||
AutoModerationTriggerTypes,
|
||||
DiscordAutoModerationRule,
|
||||
DiscordAutoModerationRuleTriggerMetadataPresets,
|
||||
} from "../../../types/discord.ts";
|
||||
|
||||
/** Get a rule currently configured for guild. */
|
||||
export async function createAutomodRule(bot: Bot, guildId: bigint, options: CreateAutoModerationRuleOptions) {
|
||||
const rule = await bot.rest.runMethod<DiscordAutoModerationRule>(
|
||||
bot.rest,
|
||||
"POST",
|
||||
bot.constants.routes.AUTOMOD_RULES(guildId),
|
||||
{
|
||||
name: options.name,
|
||||
event_type: options.eventType,
|
||||
trigger_type: options.triggerType,
|
||||
trigger_metadata: {
|
||||
keyword_filter: options.triggerMetadata.keywordFilter,
|
||||
presets: options.triggerMetadata.presets,
|
||||
},
|
||||
actions: options.actions.map((action) => ({
|
||||
type: action.type,
|
||||
metadata: action.metadata
|
||||
? {
|
||||
channel_id: action.metadata.channelId?.toString(),
|
||||
duration_seconds: action.metadata.durationSeconds,
|
||||
}
|
||||
: undefined,
|
||||
})),
|
||||
enabled: options.enabled ?? true,
|
||||
exempt_roles: options.exemptRoles?.map((id) => id.toString()),
|
||||
exempt_channels: options.exemptChannels?.map((id) => id.toString()),
|
||||
},
|
||||
);
|
||||
|
||||
return bot.transformers.automodRule(bot, rule);
|
||||
}
|
||||
|
||||
export interface CreateAutoModerationRuleOptions {
|
||||
/** The name of the rule. */
|
||||
name: string;
|
||||
/** The type of event to trigger the rule on. */
|
||||
eventType: AutoModerationEventTypes;
|
||||
/** The type of trigger to use for the rule. */
|
||||
triggerType: AutoModerationTriggerTypes;
|
||||
/** The metadata to use for the trigger. */
|
||||
triggerMetadata: {
|
||||
// TODO: discord is considering renaming this before release
|
||||
/** The keywords needed to match. Only present when TriggerType.Keyword */
|
||||
keywordFilter?: string[];
|
||||
/** The pre-defined lists of words to match from. Only present when TriggerType.KeywordPreset */
|
||||
presets?: DiscordAutoModerationRuleTriggerMetadataPresets[];
|
||||
};
|
||||
/** The actions that will trigger for this rule */
|
||||
actions: {
|
||||
/** The type of action to take when a rule is triggered */
|
||||
type: AutoModerationActionType;
|
||||
/** additional metadata needed during execution for this specific action type */
|
||||
metadata?: {
|
||||
/** The id of channel to which user content should be logged. Only in SendAlertMessage */
|
||||
channelId?: bigint;
|
||||
/** Timeout duration in seconds. Max is 2419200(4 weeks). Only supported for TriggerType.Keyword */
|
||||
durationSeconds?: number;
|
||||
};
|
||||
}[];
|
||||
/** Whether the rule should be enabled, true by default. */
|
||||
enabled?: boolean;
|
||||
/** The role ids that should not be effected by the rule */
|
||||
exemptRoles?: bigint[];
|
||||
/** The channel ids that should not be effected by the rule. */
|
||||
exemptChannels?: bigint[];
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Bot } from "../../../bot.ts";
|
||||
|
||||
/** Delete a rule currently configured for guild. */
|
||||
export async function deleteAutomodRule(bot: Bot, guildId: bigint, ruleId: bigint) {
|
||||
await bot.rest.runMethod<undefined>(
|
||||
bot.rest,
|
||||
"DELETE",
|
||||
bot.constants.routes.AUTOMOD_RULE(guildId, ruleId),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { Bot } from "../../../bot.ts";
|
||||
import {
|
||||
AutoModerationActionType,
|
||||
AutoModerationEventTypes,
|
||||
DiscordAutoModerationRule,
|
||||
DiscordAutoModerationRuleTriggerMetadataPresets,
|
||||
} from "../../../types/discord.ts";
|
||||
|
||||
/** Edit a rule currently configured for guild. */
|
||||
export async function editAutomodRule(bot: Bot, guildId: bigint, options: Partial<EditAutoModerationRuleOptions>) {
|
||||
const rule = await bot.rest.runMethod<DiscordAutoModerationRule>(
|
||||
bot.rest,
|
||||
"PATCH",
|
||||
bot.constants.routes.AUTOMOD_RULES(guildId),
|
||||
{
|
||||
name: options.name,
|
||||
event_type: options.eventType,
|
||||
trigger_metadata: options.triggerMetadata
|
||||
? {
|
||||
keyword_filter: options.triggerMetadata.keywordFilter,
|
||||
presets: options.triggerMetadata.presets,
|
||||
}
|
||||
: undefined,
|
||||
actions: options.actions?.map((action) => ({
|
||||
type: action.type,
|
||||
metadata: {
|
||||
channel_id: action.metadata.channelId?.toString(),
|
||||
duration_seconds: action.metadata.durationSeconds,
|
||||
},
|
||||
})),
|
||||
enabled: options.enabled ?? true,
|
||||
exempt_roles: options.exemptRoles?.map((id) => id.toString()),
|
||||
exempt_channels: options.exemptChannels?.map((id) => id.toString()),
|
||||
},
|
||||
);
|
||||
|
||||
return bot.transformers.automodRule(bot, rule);
|
||||
}
|
||||
|
||||
export interface EditAutoModerationRuleOptions {
|
||||
/** The name of the rule. */
|
||||
name: string;
|
||||
/** The type of event to trigger the rule on. */
|
||||
eventType: AutoModerationEventTypes;
|
||||
/** The metadata to use for the trigger. */
|
||||
triggerMetadata: {
|
||||
// TODO: discord is considering renaming this before release
|
||||
/** The keywords needed to match. Only present when TriggerType.Keyword */
|
||||
keywordFilter?: string[];
|
||||
// TODO: discord is considering renaming this before release
|
||||
// TODO: This may need a special type or enum
|
||||
/** The pre-defined lists of words to match from. Only present when TriggerType.KeywordPreset */
|
||||
presets?: DiscordAutoModerationRuleTriggerMetadataPresets[];
|
||||
};
|
||||
/** The actions that will trigger for this rule */
|
||||
actions: {
|
||||
/** The type of action to take when a rule is triggered */
|
||||
type: AutoModerationActionType;
|
||||
/** additional metadata needed during execution for this specific action type */
|
||||
metadata: {
|
||||
/** The id of channel to which user content should be logged. Only in SendAlertMessage */
|
||||
channelId?: bigint;
|
||||
/** Timeout duration in seconds. Only supported for TriggerType.Keyword */
|
||||
durationSeconds?: number;
|
||||
};
|
||||
}[];
|
||||
/** Whether the rule should be enabled. */
|
||||
enabled?: boolean;
|
||||
/** The role ids that should not be effected by the rule */
|
||||
exemptRoles?: bigint[];
|
||||
/** The channel ids that should not be effected by the rule. */
|
||||
exemptChannels?: bigint[];
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Bot } from "../../../bot.ts";
|
||||
import { DiscordAutoModerationRule } from "../../../types/discord.ts";
|
||||
import { Collection } from "../../../util/collection.ts";
|
||||
|
||||
/** Get a rule currently configured for guild. */
|
||||
export async function getAutomodRule(bot: Bot, guildId: bigint, ruleId: bigint) {
|
||||
const rule = await bot.rest.runMethod<DiscordAutoModerationRule>(
|
||||
bot.rest,
|
||||
"GET",
|
||||
bot.constants.routes.AUTOMOD_RULE(guildId, ruleId),
|
||||
);
|
||||
|
||||
return bot.transformers.automodRule(bot, rule);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Bot } from "../../../bot.ts";
|
||||
import { DiscordAutoModerationRule } from "../../../types/discord.ts";
|
||||
import { Collection } from "../../../util/collection.ts";
|
||||
|
||||
/** Get a list of all rules currently configured for guild. */
|
||||
export async function getAutomodRules(bot: Bot, guildId: bigint) {
|
||||
const rules = await bot.rest.runMethod<DiscordAutoModerationRule[]>(
|
||||
bot.rest,
|
||||
"GET",
|
||||
bot.constants.routes.AUTOMOD_RULES(guildId),
|
||||
);
|
||||
|
||||
return new Collection(rules.map((r) => {
|
||||
const rule = bot.transformers.automodRule(bot, r);
|
||||
return [rule.id, rule];
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export * from "./getAutomodRule.ts";
|
||||
export * from "./getAutomodRules.ts";
|
||||
export * from "./createAutomodRule.ts";
|
||||
export * from "./editAutomodRule.ts";
|
||||
export * from "./deleteAutomodRule.ts";
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from "./scheduledEvents/mod.ts";
|
||||
export * from "./automod/mod.ts";
|
||||
|
||||
export * from "./createGuild.ts";
|
||||
export * from "./deleteGuild.ts";
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import { AutoModerationActionType, BotWithCache, PermissionStrings } from "../../deps.ts";
|
||||
import { requireBotGuildPermissions } from "../permissions.ts";
|
||||
|
||||
export function getAutomodRule(bot: BotWithCache) {
|
||||
const getAutomodRuleOld = bot.helpers.getAutomodRule;
|
||||
|
||||
bot.helpers.getAutomodRule = async function (guildId, ruleId) {
|
||||
requireBotGuildPermissions(bot, guildId, ["MANAGE_GUILD"]);
|
||||
|
||||
return await getAutomodRuleOld(guildId, ruleId);
|
||||
};
|
||||
}
|
||||
|
||||
export function getAutomodRules(bot: BotWithCache) {
|
||||
const getAutomodRulesOld = bot.helpers.getAutomodRules;
|
||||
|
||||
bot.helpers.getAutomodRules = async function (guildId) {
|
||||
requireBotGuildPermissions(bot, guildId, ["MANAGE_GUILD"]);
|
||||
|
||||
return await getAutomodRulesOld(guildId);
|
||||
};
|
||||
}
|
||||
|
||||
export function createAutomodRule(bot: BotWithCache) {
|
||||
const createAutomodRuleOld = bot.helpers.createAutomodRule;
|
||||
|
||||
bot.helpers.createAutomodRule = async function (guildId, options) {
|
||||
requireBotGuildPermissions(bot, guildId, ["MANAGE_GUILD"]);
|
||||
|
||||
for (const action of options.actions) {
|
||||
// Check for maximum duration seconds
|
||||
if (action.metadata?.durationSeconds && action.metadata.durationSeconds > 2419200) {
|
||||
console.log(
|
||||
`[Warning] Automod action duration seconds is too high: ${action.metadata.durationSeconds}. Setting to Discord's allowed maximum.`,
|
||||
);
|
||||
// Discords max is 4 weeks
|
||||
action.metadata.durationSeconds = 2419200;
|
||||
}
|
||||
|
||||
// Timeout actions require perm check
|
||||
if (action.type === AutoModerationActionType.Timeout) {
|
||||
requireBotGuildPermissions(bot, guildId, ["MODERATE_MEMBERS"]);
|
||||
}
|
||||
}
|
||||
|
||||
return await createAutomodRuleOld(guildId, options);
|
||||
};
|
||||
}
|
||||
|
||||
export function editAutomodRule(bot: BotWithCache) {
|
||||
const editAutomodRuleOld = bot.helpers.editAutomodRule;
|
||||
|
||||
bot.helpers.editAutomodRule = async function (guildId, options) {
|
||||
requireBotGuildPermissions(bot, guildId, ["MANAGE_GUILD"]);
|
||||
|
||||
for (const action of options.actions ?? []) {
|
||||
// Check for maximum duration seconds
|
||||
if (action.metadata?.durationSeconds && action.metadata.durationSeconds > 2419200) {
|
||||
console.log(
|
||||
`[Warning] Automod action duration seconds is too high: ${action.metadata.durationSeconds}. Setting to Discord's allowed maximum.`,
|
||||
);
|
||||
// Discords max is 4 weeks
|
||||
action.metadata.durationSeconds = 2419200;
|
||||
}
|
||||
|
||||
// Timeout actions require perm check
|
||||
if (action.type === AutoModerationActionType.Timeout) {
|
||||
requireBotGuildPermissions(bot, guildId, ["MODERATE_MEMBERS"]);
|
||||
}
|
||||
}
|
||||
|
||||
return await editAutomodRuleOld(guildId, options);
|
||||
};
|
||||
}
|
||||
|
||||
export function deleteAutomodRule(bot: BotWithCache) {
|
||||
const deleteAutomodRuleOld = bot.helpers.deleteAutomodRule;
|
||||
|
||||
bot.helpers.deleteAutomodRule = async function (guildId, options) {
|
||||
requireBotGuildPermissions(bot, guildId, ["MANAGE_GUILD"]);
|
||||
|
||||
return await deleteAutomodRuleOld(guildId, options);
|
||||
};
|
||||
}
|
||||
|
||||
export default function setupAutoModerationPermChecks(bot: BotWithCache) {
|
||||
getAutomodRule(bot);
|
||||
getAutomodRules(bot);
|
||||
createAutomodRule(bot);
|
||||
editAutomodRule(bot);
|
||||
deleteAutomodRule(bot);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { RestRequestRejection } from "./rest.ts";
|
||||
|
||||
export function convertRestError(errorStack: Error, data: RestRequestRejection): Error {
|
||||
errorStack.message = `[${data.status}] ${data.error}`;
|
||||
errorStack.message = `[${data.status}] ${data.error}\n${data.body}`;
|
||||
return errorStack;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
import { AutoModerationActionType, AutoModerationEventTypes, AutoModerationTriggerTypes } from "../../types/discord.ts";
|
||||
import { assertEquals, assertExists } from "../deps.ts";
|
||||
import { loadBot } from "../mod.ts";
|
||||
import { CACHED_COMMUNITY_GUILD_ID } from "../utils.ts";
|
||||
|
||||
Deno.test("[automod] Run automod tests", async (t) => {
|
||||
const bot = loadBot();
|
||||
|
||||
await t.step("[automod] Create a MessageSend rule for Keyword with BlockMessage action.", async () => {
|
||||
const rule = await bot.helpers.createAutomodRule(CACHED_COMMUNITY_GUILD_ID, {
|
||||
name: "test",
|
||||
eventType: AutoModerationEventTypes.MessageSend,
|
||||
triggerType: AutoModerationTriggerTypes.Keyword,
|
||||
triggerMetadata: {
|
||||
keywordFilter: ["iblamewolf"],
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
type: AutoModerationActionType.BlockMessage,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
assertExists(rule.id);
|
||||
|
||||
const fetchedRule = await bot.helpers.getAutomodRule(
|
||||
CACHED_COMMUNITY_GUILD_ID,
|
||||
rule.id,
|
||||
);
|
||||
assertExists(fetchedRule.id);
|
||||
assertEquals(fetchedRule.name, rule.name);
|
||||
assertEquals(fetchedRule.eventType, AutoModerationEventTypes.MessageSend);
|
||||
assertEquals(fetchedRule.triggerType, AutoModerationTriggerTypes.Keyword);
|
||||
assertEquals(fetchedRule.triggerMetadata?.keywordFilter?.[0], "iblamewolf");
|
||||
assertExists(fetchedRule.actions);
|
||||
assertExists(fetchedRule.actions[0]);
|
||||
assertEquals(fetchedRule.actions[0].type, AutoModerationActionType.BlockMessage);
|
||||
|
||||
await bot.helpers.deleteAutomodRule(CACHED_COMMUNITY_GUILD_ID, rule.id);
|
||||
});
|
||||
|
||||
await t.step("[automod] Create a MessageSend rule for Keyword with SendAlertMessage action.", async () => {
|
||||
const channel = await bot.helpers.createChannel(CACHED_COMMUNITY_GUILD_ID, {
|
||||
name: "test",
|
||||
});
|
||||
assertExists(channel.id);
|
||||
|
||||
const rule = await bot.helpers.createAutomodRule(CACHED_COMMUNITY_GUILD_ID, {
|
||||
name: "test",
|
||||
eventType: AutoModerationEventTypes.MessageSend,
|
||||
triggerType: AutoModerationTriggerTypes.Keyword,
|
||||
triggerMetadata: {
|
||||
keywordFilter: ["iblamewolf"],
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
type: AutoModerationActionType.SendAlertMessage,
|
||||
metadata: {
|
||||
channelId: channel.id,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
assertExists(rule.id);
|
||||
|
||||
const fetchedRule = await bot.helpers.getAutomodRule(
|
||||
CACHED_COMMUNITY_GUILD_ID,
|
||||
rule.id,
|
||||
);
|
||||
assertExists(fetchedRule.id);
|
||||
assertEquals(fetchedRule.name, rule.name);
|
||||
assertEquals(fetchedRule.eventType, AutoModerationEventTypes.MessageSend);
|
||||
assertEquals(fetchedRule.triggerType, AutoModerationTriggerTypes.Keyword);
|
||||
assertEquals(fetchedRule.triggerMetadata?.keywordFilter?.[0], "iblamewolf");
|
||||
assertExists(fetchedRule.actions);
|
||||
assertExists(fetchedRule.actions[0]);
|
||||
assertEquals(fetchedRule.actions[0].type, AutoModerationActionType.SendAlertMessage);
|
||||
assertEquals(fetchedRule.actions[0].metadata?.channelId, channel.id);
|
||||
|
||||
await bot.helpers.deleteAutomodRule(CACHED_COMMUNITY_GUILD_ID, rule.id);
|
||||
await bot.helpers.deleteChannel(channel.id);
|
||||
});
|
||||
|
||||
await t.step("[automod] Create a MessageSend rule for Keyword with Timeout action.", async () => {
|
||||
const rule = await bot.helpers.createAutomodRule(CACHED_COMMUNITY_GUILD_ID, {
|
||||
name: "test",
|
||||
eventType: AutoModerationEventTypes.MessageSend,
|
||||
triggerType: AutoModerationTriggerTypes.Keyword,
|
||||
triggerMetadata: {
|
||||
keywordFilter: ["iblamewolf"],
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
type: AutoModerationActionType.Timeout,
|
||||
metadata: {
|
||||
durationSeconds: 10,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
assertExists(rule.id);
|
||||
|
||||
const fetchedRule = await bot.helpers.getAutomodRule(
|
||||
CACHED_COMMUNITY_GUILD_ID,
|
||||
rule.id,
|
||||
);
|
||||
assertExists(fetchedRule.id);
|
||||
assertEquals(fetchedRule.name, rule.name);
|
||||
assertEquals(fetchedRule.eventType, AutoModerationEventTypes.MessageSend);
|
||||
assertEquals(fetchedRule.triggerType, AutoModerationTriggerTypes.Keyword);
|
||||
assertEquals(fetchedRule.triggerMetadata?.keywordFilter?.[0], "iblamewolf");
|
||||
assertExists(fetchedRule.actions);
|
||||
assertExists(fetchedRule.actions[0]);
|
||||
assertEquals(fetchedRule.actions[0].type, AutoModerationActionType.Timeout);
|
||||
assertEquals(fetchedRule.actions[0].metadata?.durationSeconds, 10);
|
||||
|
||||
await bot.helpers.deleteAutomodRule(CACHED_COMMUNITY_GUILD_ID, rule.id);
|
||||
});
|
||||
|
||||
await t.step("[automod] Create a MessageSend rule for Keyword with BlockMessage & Timeout action.", async () => {
|
||||
const rule = await bot.helpers.createAutomodRule(CACHED_COMMUNITY_GUILD_ID, {
|
||||
name: "test",
|
||||
eventType: AutoModerationEventTypes.MessageSend,
|
||||
triggerType: AutoModerationTriggerTypes.Keyword,
|
||||
triggerMetadata: {
|
||||
keywordFilter: ["iblamewolf"],
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
type: AutoModerationActionType.BlockMessage,
|
||||
},
|
||||
{
|
||||
type: AutoModerationActionType.Timeout,
|
||||
metadata: {
|
||||
durationSeconds: 10,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
assertExists(rule.id);
|
||||
|
||||
await bot.helpers.deleteAutomodRule(CACHED_COMMUNITY_GUILD_ID, rule.id);
|
||||
});
|
||||
|
||||
await t.step("[automod] Create a MessageSend rule for Keyword with SendAlertMessage & Timeout action.", async () => {
|
||||
const channel = await bot.helpers.createChannel(CACHED_COMMUNITY_GUILD_ID, {
|
||||
name: "test",
|
||||
});
|
||||
assertExists(channel.id);
|
||||
|
||||
const rule = await bot.helpers.createAutomodRule(CACHED_COMMUNITY_GUILD_ID, {
|
||||
name: "test",
|
||||
eventType: AutoModerationEventTypes.MessageSend,
|
||||
triggerType: AutoModerationTriggerTypes.Keyword,
|
||||
triggerMetadata: {
|
||||
keywordFilter: ["iblamewolf"],
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
type: AutoModerationActionType.SendAlertMessage,
|
||||
metadata: {
|
||||
channelId: channel.id,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: AutoModerationActionType.Timeout,
|
||||
metadata: {
|
||||
durationSeconds: 10,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
assertExists(rule.id);
|
||||
|
||||
await bot.helpers.deleteAutomodRule(CACHED_COMMUNITY_GUILD_ID, rule.id);
|
||||
await bot.helpers.deleteChannel(channel.id);
|
||||
});
|
||||
|
||||
await t.step(
|
||||
"[automod] Create a MessageSend rule for Keyword with BlockMessage & SendAlertMessage & Timeout action.",
|
||||
async (t) => {
|
||||
const channel = await bot.helpers.createChannel(CACHED_COMMUNITY_GUILD_ID, {
|
||||
name: "test",
|
||||
});
|
||||
assertExists(channel.id);
|
||||
|
||||
const rule = await bot.helpers.createAutomodRule(CACHED_COMMUNITY_GUILD_ID, {
|
||||
name: "test",
|
||||
eventType: AutoModerationEventTypes.MessageSend,
|
||||
triggerType: AutoModerationTriggerTypes.Keyword,
|
||||
triggerMetadata: {
|
||||
keywordFilter: ["iblamewolf"],
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
type: AutoModerationActionType.BlockMessage,
|
||||
},
|
||||
{
|
||||
type: AutoModerationActionType.SendAlertMessage,
|
||||
metadata: {
|
||||
channelId: channel.id,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: AutoModerationActionType.Timeout,
|
||||
metadata: {
|
||||
durationSeconds: 10,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
assertExists(rule.id);
|
||||
|
||||
// Get the rule again to make sure it was created correctly
|
||||
await t.step("[automod] Get a automod rule", async () => {
|
||||
const fetchedRule = await bot.helpers.getAutomodRule(
|
||||
CACHED_COMMUNITY_GUILD_ID,
|
||||
rule.id,
|
||||
);
|
||||
assertExists(fetchedRule.id);
|
||||
assertEquals(fetchedRule.name, rule.name);
|
||||
assertEquals(fetchedRule.eventType, AutoModerationEventTypes.MessageSend);
|
||||
assertEquals(fetchedRule.triggerType, AutoModerationTriggerTypes.Keyword);
|
||||
assertEquals(fetchedRule.triggerMetadata?.keywordFilter?.[0], "iblamewolf");
|
||||
assertExists(fetchedRule.actions);
|
||||
assertExists(fetchedRule.actions[0]);
|
||||
assertExists(fetchedRule.actions[1].metadata);
|
||||
assertExists(fetchedRule.actions[2].metadata);
|
||||
assertEquals(fetchedRule.actions[1].metadata.channelId, channel.id);
|
||||
assertEquals(fetchedRule.actions[2].metadata.durationSeconds, 10);
|
||||
assertEquals(fetchedRule.actions[0].type, AutoModerationActionType.BlockMessage);
|
||||
assertEquals(fetchedRule.actions[1].type, AutoModerationActionType.SendAlertMessage);
|
||||
assertEquals(fetchedRule.actions[2].type, AutoModerationActionType.Timeout);
|
||||
});
|
||||
|
||||
await bot.helpers.deleteAutomodRule(CACHED_COMMUNITY_GUILD_ID, rule.id);
|
||||
await bot.helpers.deleteChannel(channel.id);
|
||||
},
|
||||
);
|
||||
});
|
||||
+9
-2
@@ -79,10 +79,17 @@ async function handleRequest(conn: Deno.Conn) {
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("CATCH", requestEvent.request.url, requestEvent.request.method, requestEvent.request.body, error);
|
||||
console.log(
|
||||
"CATCH",
|
||||
requestEvent.request.url,
|
||||
requestEvent.request.method,
|
||||
requestEvent.request.body,
|
||||
error.code,
|
||||
error,
|
||||
);
|
||||
requestEvent.respondWith(
|
||||
new Response(JSON.stringify(error), {
|
||||
status: error.code,
|
||||
status: error.code ?? 469,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Bot } from "../bot.ts";
|
||||
import { DiscordAutoModerationActionExecution } from "../types/discord.ts";
|
||||
import { Optionalize } from "../types/shared.ts";
|
||||
|
||||
export function transformAutoModerationActionExecution(bot: Bot, payload: DiscordAutoModerationActionExecution) {
|
||||
const rule = {
|
||||
content: payload.content,
|
||||
ruleTriggerType: payload.rule_trigger_type,
|
||||
guildId: bot.transformers.snowflake(payload.guild_id),
|
||||
ruleId: bot.transformers.snowflake(payload.rule_id),
|
||||
userId: bot.transformers.snowflake(payload.user_id),
|
||||
channelId: payload.channel_id ? bot.transformers.snowflake(payload.channel_id) : undefined,
|
||||
messageId: payload.message_id ? bot.transformers.snowflake(payload.message_id) : undefined,
|
||||
alertSystemMessageId: payload.alert_system_message_id
|
||||
? bot.transformers.snowflake(payload.alert_system_message_id)
|
||||
: undefined,
|
||||
matchedKeyword: payload.matched_keyword ?? "",
|
||||
matchedContent: payload.matched_content ?? "",
|
||||
action: {
|
||||
type: payload.action.type,
|
||||
metadata: {
|
||||
durationSeconds: payload.action.metadata.duration_seconds,
|
||||
channelId: payload.action.metadata.channel_id
|
||||
? bot.transformers.snowflake(payload.action.metadata.channel_id)
|
||||
: undefined,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return rule as Optionalize<typeof rule>;
|
||||
}
|
||||
|
||||
export interface AutoModerationActionExecution extends ReturnType<typeof transformAutoModerationActionExecution> {}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Bot } from "../bot.ts";
|
||||
import { DiscordAutoModerationRule } from "../types/discord.ts";
|
||||
import { Optionalize } from "../types/shared.ts";
|
||||
|
||||
export function transformAutoModerationRule(bot: Bot, payload: DiscordAutoModerationRule) {
|
||||
const rule = {
|
||||
name: payload.name,
|
||||
eventType: payload.event_type,
|
||||
triggerType: payload.trigger_type,
|
||||
enabled: payload.enabled,
|
||||
id: bot.transformers.snowflake(payload.id),
|
||||
guildId: bot.transformers.snowflake(payload.guild_id),
|
||||
creatorId: bot.transformers.snowflake(payload.creator_id),
|
||||
exemptRoles: payload.exempt_roles.map((id) => bot.transformers.snowflake(id)),
|
||||
exemptChannels: payload.exempt_channels.map((id) => bot.transformers.snowflake(id)),
|
||||
triggerMetadata: payload.trigger_metadata
|
||||
? {
|
||||
keywordFilter: payload.trigger_metadata.keyword_filter,
|
||||
presets: payload.trigger_metadata.presets,
|
||||
}
|
||||
: undefined,
|
||||
actions: payload.actions.map((action) => ({
|
||||
type: action.type,
|
||||
metadata: action.metadata
|
||||
? {
|
||||
channelId: action.metadata.channel_id ? bot.transformers.snowflake(action.metadata.channel_id) : undefined,
|
||||
durationSeconds: action.metadata.duration_seconds,
|
||||
}
|
||||
: undefined,
|
||||
})),
|
||||
};
|
||||
|
||||
return rule as Optionalize<typeof rule>;
|
||||
}
|
||||
|
||||
export interface AutoModerationRule extends ReturnType<typeof transformAutoModerationRule> {}
|
||||
@@ -1400,6 +1400,110 @@ export interface DiscordAuditLog {
|
||||
threads: DiscordChannel[];
|
||||
/** List of guild scheduled events found in the audit log */
|
||||
guild_scheduled_events?: DiscordScheduledEvent[];
|
||||
/** List of auto moderation rules referenced in the audit log */
|
||||
auto_moderation_rules?: DiscordAutoModerationRule[];
|
||||
}
|
||||
|
||||
export interface DiscordAutoModerationRule {
|
||||
/** The id of this rule */
|
||||
id: string;
|
||||
/** The guild id */
|
||||
guild_id: string;
|
||||
/** The name of the rule */
|
||||
name: string;
|
||||
/** The id of the user who created this rule. */
|
||||
creator_id: string;
|
||||
/** Indicates in what event context a rule should be checked. */
|
||||
event_type: AutoModerationEventTypes;
|
||||
/** The type of trigger for this rule */
|
||||
trigger_type: AutoModerationTriggerTypes;
|
||||
/** The metadata used to determine whether a rule should be triggered. */
|
||||
trigger_metadata: DiscordAutoModerationRuleTriggerMetadata;
|
||||
/** Actions which will execute whenever a rule is triggered. */
|
||||
actions: DiscordAutoModerationAction[];
|
||||
/** Whether the rule is enabled. */
|
||||
enabled: boolean;
|
||||
/** The role ids that are whitelisted. Max 20. */
|
||||
exempt_roles: string[];
|
||||
/** The channel ids that are whitelisted. Max 50. */
|
||||
exempt_channels: string[];
|
||||
}
|
||||
|
||||
export enum AutoModerationEventTypes {
|
||||
/** When a user sends a message */
|
||||
MessageSend = 1,
|
||||
}
|
||||
|
||||
export enum AutoModerationTriggerTypes {
|
||||
Keyword = 1,
|
||||
HarmfulLink,
|
||||
Spam,
|
||||
KeywordPreset,
|
||||
}
|
||||
|
||||
export interface DiscordAutoModerationRuleTriggerMetadata {
|
||||
// TODO: discord is considering renaming this before release
|
||||
/** The keywords needed to match. Only present when TriggerType.Keyword */
|
||||
keyword_filter?: string[];
|
||||
/** The pre-defined lists of words to match from. Only present when TriggerType.KeywordPreset */
|
||||
presets?: DiscordAutoModerationRuleTriggerMetadataPresets[];
|
||||
}
|
||||
|
||||
export enum DiscordAutoModerationRuleTriggerMetadataPresets {
|
||||
/** Words that may be considered forms of swearing or cursing */
|
||||
Profanity = 1,
|
||||
/** Words that refer to sexually explicit behavior or activity */
|
||||
SexualContent,
|
||||
/** Personal insults or words that may be considered hate speech */
|
||||
Slurs,
|
||||
}
|
||||
|
||||
export interface DiscordAutoModerationAction {
|
||||
/** The type of action to take when a rule is triggered */
|
||||
type: AutoModerationActionType;
|
||||
/** additional metadata needed during execution for this specific action type */
|
||||
metadata: DiscordAutoModerationActionMetadata;
|
||||
}
|
||||
|
||||
export enum AutoModerationActionType {
|
||||
/** Blocks the content of a message according to the rule */
|
||||
BlockMessage = 1,
|
||||
/** Logs user content to a specified channel */
|
||||
SendAlertMessage,
|
||||
/** Times out user for specified duration */
|
||||
Timeout,
|
||||
}
|
||||
|
||||
export interface DiscordAutoModerationActionMetadata {
|
||||
/** The id of channel to which user content should be logged. Only in ActionType.SendAlertMessage */
|
||||
channel_id?: string;
|
||||
/** Timeout duration in seconds maximum of 2419200 seconds (4 weeks). Only supported for TriggerType.Keyword && Only in ActionType.Timeout */
|
||||
duration_seconds?: number;
|
||||
}
|
||||
|
||||
export interface DiscordAutoModerationActionExecution {
|
||||
/** The id of the guild */
|
||||
guild_id: string;
|
||||
/** The id of the rule that was executed */
|
||||
rule_id: string;
|
||||
/** The id of the user which generated the content which triggered the rule */
|
||||
user_id: string;
|
||||
/** The content from the user */
|
||||
content: string;
|
||||
/** Action which was executed */
|
||||
action: DiscordAutoModerationAction;
|
||||
/** The trigger type of the rule that was executed. */
|
||||
rule_trigger_type: AutoModerationTriggerTypes;
|
||||
/** The id of the channel in which user content was posted */
|
||||
channel_id?: string | null;
|
||||
/** The id of the message. Will not exist if message was blocked by automod or content was not part of any message */
|
||||
message_id?: string | null;
|
||||
/** The id of any system auto moderation messages posted as a result of this action */
|
||||
alert_system_message_id?: string | null;
|
||||
/** The word or phrase that triggerred the rule. */
|
||||
matched_keyword: string | null;
|
||||
/** The substring in content that triggered rule */
|
||||
matched_content: string | null;
|
||||
}
|
||||
|
||||
/** https://discord.com/developers/docs/resources/audit-log#audit-log-entry-object-audit-log-entry-structure */
|
||||
|
||||
@@ -215,6 +215,8 @@ export enum GuildFeatures {
|
||||
PrivateThreads = "PRIVATE_THREADS",
|
||||
/** Guild is able to set role icons */
|
||||
RoleIcons = "ROLE_ICONS",
|
||||
/** Guild has set up auto moderation rules */
|
||||
AutoModeration = "AUTO_MODERATION",
|
||||
}
|
||||
|
||||
/** https://discord.com/developers/docs/resources/guild#guild-object-mfa-level */
|
||||
@@ -332,6 +334,7 @@ export enum MessageTypes {
|
||||
ThreadStarterMessage,
|
||||
GuildInviteReminder,
|
||||
ContextMenuCommand,
|
||||
AutoModerationAction,
|
||||
}
|
||||
|
||||
/** https://discord.com/developers/docs/resources/channel#message-object-message-activity-types */
|
||||
@@ -479,6 +482,14 @@ export enum AuditLogEvents {
|
||||
ThreadDelete,
|
||||
/** Permissions were updated for a command */
|
||||
ApplicationCommandPermissionUpdate = 121,
|
||||
/** Auto moderation rule was created */
|
||||
AutoModerationRuleCreate = 140,
|
||||
/** Auto moderation rule was updated */
|
||||
AutoModerationRuleUpdate,
|
||||
/** Auto moderation rule was deleted */
|
||||
AutoModerationRuleDelete,
|
||||
/** Message was blocked by AutoMod according to a rule. */
|
||||
AutoModerationBlockMessage,
|
||||
}
|
||||
|
||||
export enum ScheduledEventPrivacyLevel {
|
||||
@@ -996,6 +1007,17 @@ export enum GatewayIntents {
|
||||
* - GUILD_SCHEDULED_EVENT_USER_REMOVE this is experimental and unstable.
|
||||
*/
|
||||
GuildScheduledEvents = (1 << 16),
|
||||
|
||||
/**
|
||||
* - AUTO_MODERATION_RULE_CREATE
|
||||
* - AUTO_MODERATION_RULE_UPDATE
|
||||
* - AUTO_MODERATION_RULE_DELETE
|
||||
*/
|
||||
AutoModerationConfiguration = (1 << 20),
|
||||
/**
|
||||
* - AUTO_MODERATION_ACTION_EXECUTION
|
||||
*/
|
||||
AutoModerationExecution = (1 << 21),
|
||||
}
|
||||
|
||||
// ALIASES JUST FOR BETTER UX IN THIS CASE
|
||||
|
||||
@@ -41,6 +41,14 @@ export const routes = {
|
||||
return `/gateway/bot`;
|
||||
},
|
||||
|
||||
// Automod Endpoints
|
||||
AUTOMOD_RULES: (guildId: bigint) => {
|
||||
return `/guilds/${guildId}/auto-moderation/rules`;
|
||||
},
|
||||
AUTOMOD_RULE: (guildId: bigint, ruleId: bigint) => {
|
||||
return `/guilds/${guildId}/auto-moderation/rules/${ruleId}`;
|
||||
},
|
||||
|
||||
// Channel Endpoints
|
||||
CHANNEL: (channelId: bigint) => {
|
||||
return `/channels/${channelId}`;
|
||||
|
||||
Reference in New Issue
Block a user