feat: add automod start_thread action (#274)
Co-authored-by: Almeida <almeidx@pm.me>
This commit is contained in:
parent
92dfbca362
commit
f9c0e661af
3 changed files with 100 additions and 0 deletions
|
@ -167,6 +167,9 @@ const configPreprocessor: ConfigPreprocessorFn<AutomodPluginType> = (options) =>
|
||||||
if (rule["actions"]["log"] == null) {
|
if (rule["actions"]["log"] == null) {
|
||||||
rule["actions"]["log"] = true;
|
rule["actions"]["log"] = true;
|
||||||
}
|
}
|
||||||
|
if (rule["actions"]["clean"] && rule["actions"]["start_thread"]) {
|
||||||
|
throw new StrictValidationError([`Cannot have both clean and start_thread at rule '${rule.name}'`]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { ReplyAction } from "./reply";
|
||||||
import { SetAntiraidLevelAction } from "./setAntiraidLevel";
|
import { SetAntiraidLevelAction } from "./setAntiraidLevel";
|
||||||
import { SetCounterAction } from "./setCounter";
|
import { SetCounterAction } from "./setCounter";
|
||||||
import { SetSlowmodeAction } from "./setSlowmode";
|
import { SetSlowmodeAction } from "./setSlowmode";
|
||||||
|
import { StartThreadAction } from "./startThread";
|
||||||
import { WarnAction } from "./warn";
|
import { WarnAction } from "./warn";
|
||||||
|
|
||||||
export const availableActions: Record<string, AutomodActionBlueprint<any>> = {
|
export const availableActions: Record<string, AutomodActionBlueprint<any>> = {
|
||||||
|
@ -33,6 +34,7 @@ export const availableActions: Record<string, AutomodActionBlueprint<any>> = {
|
||||||
add_to_counter: AddToCounterAction,
|
add_to_counter: AddToCounterAction,
|
||||||
set_counter: SetCounterAction,
|
set_counter: SetCounterAction,
|
||||||
set_slowmode: SetSlowmodeAction,
|
set_slowmode: SetSlowmodeAction,
|
||||||
|
start_thread: StartThreadAction,
|
||||||
archive_thread: ArchiveThreadAction,
|
archive_thread: ArchiveThreadAction,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -52,5 +54,6 @@ export const AvailableActions = t.type({
|
||||||
add_to_counter: AddToCounterAction.configType,
|
add_to_counter: AddToCounterAction.configType,
|
||||||
set_counter: SetCounterAction.configType,
|
set_counter: SetCounterAction.configType,
|
||||||
set_slowmode: SetSlowmodeAction.configType,
|
set_slowmode: SetSlowmodeAction.configType,
|
||||||
|
start_thread: StartThreadAction.configType,
|
||||||
archive_thread: ArchiveThreadAction.configType,
|
archive_thread: ArchiveThreadAction.configType,
|
||||||
});
|
});
|
||||||
|
|
94
backend/src/plugins/Automod/actions/startThread.ts
Normal file
94
backend/src/plugins/Automod/actions/startThread.ts
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
import { GuildFeature, ThreadAutoArchiveDuration } from "discord-api-types";
|
||||||
|
import { TextChannel } from "discord.js";
|
||||||
|
import * as t from "io-ts";
|
||||||
|
import { renderTemplate, TemplateSafeValueContainer } from "../../../templateFormatter";
|
||||||
|
import { ChannelTypeStrings } from "../../../types";
|
||||||
|
import { convertDelayStringToMS, MINUTES, tDelayString, tNullable } from "../../../utils";
|
||||||
|
import { savedMessageToTemplateSafeSavedMessage, userToTemplateSafeUser } from "../../../utils/templateSafeObjects";
|
||||||
|
import { automodAction } from "../helpers";
|
||||||
|
|
||||||
|
export const StartThreadAction = automodAction({
|
||||||
|
configType: t.type({
|
||||||
|
name: tNullable(t.string),
|
||||||
|
auto_archive: tDelayString,
|
||||||
|
private: tNullable(t.boolean),
|
||||||
|
slowmode: tNullable(tDelayString),
|
||||||
|
limit_per_channel: tNullable(t.number),
|
||||||
|
}),
|
||||||
|
|
||||||
|
defaultConfig: {
|
||||||
|
limit_per_channel: 5,
|
||||||
|
},
|
||||||
|
|
||||||
|
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 !== ChannelTypeStrings.TEXT || !channel.isText()) return false; // for some reason the typing here for channel.type defaults to ThreadChannelTypes (?)
|
||||||
|
// check against max threads per channel
|
||||||
|
if (actionConfig.limit_per_channel && actionConfig.limit_per_channel > 0) {
|
||||||
|
const threadCount = channel.threads.cache.filter(
|
||||||
|
(tr) =>
|
||||||
|
tr.ownerId === pluginData.client.user!.id && !tr.deleted && !tr.archived && tr.parentId === channel.id,
|
||||||
|
).size;
|
||||||
|
if (threadCount >= actionConfig.limit_per_channel) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
const guild = pluginData.guild;
|
||||||
|
const archiveSet = actionConfig.auto_archive
|
||||||
|
? Math.ceil(Math.max(convertDelayStringToMS(actionConfig.auto_archive) ?? 0, 0) / MINUTES)
|
||||||
|
: ThreadAutoArchiveDuration.OneDay;
|
||||||
|
let autoArchive: ThreadAutoArchiveDuration;
|
||||||
|
if (archiveSet === ThreadAutoArchiveDuration.OneDay) {
|
||||||
|
autoArchive = ThreadAutoArchiveDuration.OneDay;
|
||||||
|
} else if (
|
||||||
|
archiveSet === ThreadAutoArchiveDuration.ThreeDays &&
|
||||||
|
guild.features.includes(GuildFeature.ThreeDayThreadArchive)
|
||||||
|
) {
|
||||||
|
autoArchive = ThreadAutoArchiveDuration.ThreeDays;
|
||||||
|
} else if (
|
||||||
|
archiveSet === ThreadAutoArchiveDuration.OneWeek &&
|
||||||
|
guild.features.includes(GuildFeature.SevenDayThreadArchive)
|
||||||
|
) {
|
||||||
|
autoArchive = ThreadAutoArchiveDuration.OneWeek;
|
||||||
|
} else {
|
||||||
|
autoArchive = ThreadAutoArchiveDuration.OneHour;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const threadContext of threads) {
|
||||||
|
const channel = pluginData.guild.channels.cache.get(threadContext.message!.channel_id) as TextChannel;
|
||||||
|
const renderThreadName = async (str: string) =>
|
||||||
|
renderTemplate(
|
||||||
|
str,
|
||||||
|
new TemplateSafeValueContainer({
|
||||||
|
user: userToTemplateSafeUser(threadContext.user!),
|
||||||
|
msg: savedMessageToTemplateSafeSavedMessage(threadContext.message!),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const threadName = await renderThreadName(actionConfig.name ?? "{user.tag}s thread");
|
||||||
|
const thread = await channel.threads
|
||||||
|
.create({
|
||||||
|
name: threadName,
|
||||||
|
autoArchiveDuration: autoArchive,
|
||||||
|
type:
|
||||||
|
actionConfig.private && guild.features.includes(GuildFeature.PrivateThreads)
|
||||||
|
? ChannelTypeStrings.PRIVATE_THREAD
|
||||||
|
: ChannelTypeStrings.PUBLIC_THREAD,
|
||||||
|
startMessage:
|
||||||
|
!actionConfig.private && guild.features.includes(GuildFeature.PrivateThreads)
|
||||||
|
? threadContext.message!.id
|
||||||
|
: undefined,
|
||||||
|
})
|
||||||
|
.catch(noop);
|
||||||
|
if (actionConfig.slowmode && thread) {
|
||||||
|
const dur = Math.ceil(Math.max(convertDelayStringToMS(actionConfig.slowmode) ?? 0, 0) / 1000);
|
||||||
|
if (dur > 0) {
|
||||||
|
await thread.setRateLimitPerUser(dur).catch(noop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
Loading…
Add table
Add a link
Reference in a new issue