This commit is contained in:
Skillz4Killz
2022-06-15 15:34:02 +00:00
committed by GitHub
22 changed files with 796 additions and 3 deletions
+19
View File
@@ -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));
}
View File
@@ -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),
);
}
+73
View File
@@ -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[];
}
+14
View File
@@ -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);
}
+17
View File
@@ -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];
}));
}
+5
View File
@@ -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
View File
@@ -1,4 +1,5 @@
export * from "./scheduledEvents/mod.ts";
export * from "./automod/mod.ts";
export * from "./createGuild.ts";
export * from "./deleteGuild.ts";
+92
View File
@@ -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 -1
View File
@@ -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;
}
+245
View File
@@ -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
View File
@@ -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,
}),
);
}
+33
View File
@@ -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> {}
+36
View File
@@ -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> {}
+104
View File
@@ -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 */
+22
View File
@@ -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
+8
View File
@@ -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}`;