From b7afb82ddbb099bea8a87c8f65818fc16e9b773b Mon Sep 17 00:00:00 2001 From: metal Date: Tue, 31 Aug 2021 11:52:13 +0000 Subject: [PATCH] initial --- backend/src/plugins/Automod/AutomodPlugin.ts | 3 + .../Automod/actions/availableActions.ts | 3 + .../plugins/Automod/actions/startThread.ts | 59 +++++++++++++++++++ 3 files changed, 65 insertions(+) create mode 100644 backend/src/plugins/Automod/actions/startThread.ts diff --git a/backend/src/plugins/Automod/AutomodPlugin.ts b/backend/src/plugins/Automod/AutomodPlugin.ts index 80d160e3..7748b1b2 100644 --- a/backend/src/plugins/Automod/AutomodPlugin.ts +++ b/backend/src/plugins/Automod/AutomodPlugin.ts @@ -146,6 +146,9 @@ const configPreprocessor: ConfigPreprocessorFn = options => { if (rule["actions"]["log"] == null) { rule["actions"]["log"] = true; } + if (rule["actions"]["clean"] === true && rule["actions"]["start_thread"]) { + throw new StrictValidationError([`Cannot have both clean and start_thread at rule '${rule.name}'`]); + } } } } diff --git a/backend/src/plugins/Automod/actions/availableActions.ts b/backend/src/plugins/Automod/actions/availableActions.ts index fbdcf8f9..f6596914 100644 --- a/backend/src/plugins/Automod/actions/availableActions.ts +++ b/backend/src/plugins/Automod/actions/availableActions.ts @@ -14,6 +14,7 @@ import { ReplyAction } from "./reply"; import { SetAntiraidLevelAction } from "./setAntiraidLevel"; import { SetCounterAction } from "./setCounter"; import { SetSlowmodeAction } from "./setSlowmode"; +import { StartThreadAction } from "./startThread"; import { WarnAction } from "./warn"; export const availableActions: Record> = { @@ -32,6 +33,7 @@ export const availableActions: Record> = { add_to_counter: AddToCounterAction, set_counter: SetCounterAction, set_slowmode: SetSlowmodeAction, + start_thread: StartThreadAction, }; export const AvailableActions = t.type({ @@ -50,4 +52,5 @@ export const AvailableActions = t.type({ add_to_counter: AddToCounterAction.configType, set_counter: SetCounterAction.configType, set_slowmode: SetSlowmodeAction.configType, + start_thread: StartThreadAction.configType, }); diff --git a/backend/src/plugins/Automod/actions/startThread.ts b/backend/src/plugins/Automod/actions/startThread.ts new file mode 100644 index 00000000..22e5be21 --- /dev/null +++ b/backend/src/plugins/Automod/actions/startThread.ts @@ -0,0 +1,59 @@ +import { ThreadAutoArchiveDuration } from "discord-api-types"; +import { TextChannel } from "discord.js"; +import * as t from "io-ts"; +import { renderTemplate, TemplateSafeValueContainer } from "src/templateFormatter"; +import { tDelayString, tNullable } from "src/utils"; +import { userToTemplateSafeUser } from "src/utils/templateSafeObjects"; +import { noop } from "../../../utils"; +import { automodAction } from "../helpers"; + +export const StartThreadAction = automodAction({ + configType: t.type({ + name: tNullable(t.string), + auto_archive: tNullable(t.number), + private: tNullable(t.boolean), + slowmode: tNullable(tDelayString), + }), + + defaultConfig: {}, + + async apply({ pluginData, contexts, actionConfig, ruleName }) { + // check if the message still exists, we don't want to create threads for deleted messages + const threads = contexts.filter(c => { + if (!c.message || !c.user) return false; + const channel = pluginData.guild.channels.cache.get(c.message.channel_id); + if (channel?.type !== "GUILD_TEXT" || !channel.isText()) return false; // for some reason the typing here for channel.type defaults to ThreadChannelTypes (?) + return channel.messages.cache.has(c.message.id); + }); + let autoArchive: ThreadAutoArchiveDuration; + if (actionConfig.auto_archive === 1440) { + autoArchive = ThreadAutoArchiveDuration.OneDay; + } else if (actionConfig.auto_archive === 4320) { + autoArchive = ThreadAutoArchiveDuration.ThreeDays; + } else if (actionConfig.auto_archive === 10080) { + autoArchive = ThreadAutoArchiveDuration.OneWeek; + } else { + autoArchive = ThreadAutoArchiveDuration.OneHour; + } + + for (const c of threads) { + const channel = pluginData.guild.channels.cache.get(c.message!.channel_id) as TextChannel; + const renderThreadName = async str => + renderTemplate( + str, + new TemplateSafeValueContainer({ + user: userToTemplateSafeUser(c.user!), + }), + ); + const threadName = await renderThreadName(actionConfig.name ?? "{user.username}#{user.discriminator}s thread"); + await channel.threads + .create({ + name: threadName, + autoArchiveDuration: autoArchive, + type: actionConfig.private ? "GUILD_PRIVATE_THREAD" : "GUILD_PUBLIC_THREAD", + startMessage: !actionConfig.private ? c.message!.id : undefined, + }) + .catch(noop); + } + }, +});