mirror of
https://github.com/ZeppelinBot/Zeppelin.git
synced 2025-07-07 11:07:19 +00:00
refactor: replace io-ts with zod
This commit is contained in:
parent
fafaefa1fb
commit
28692962bc
161 changed files with 1450 additions and 2105 deletions
|
@ -1,4 +1,4 @@
|
|||
import { configUtils, CooldownManager } from "knub";
|
||||
import { CooldownManager } from "knub";
|
||||
import { GuildAntiraidLevels } from "../../data/GuildAntiraidLevels";
|
||||
import { GuildArchives } from "../../data/GuildArchives";
|
||||
import { GuildLogs } from "../../data/GuildLogs";
|
||||
|
@ -8,7 +8,6 @@ import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners";
|
|||
import { MINUTES, SECONDS } from "../../utils";
|
||||
import { registerEventListenersFromMap } from "../../utils/registerEventListenersFromMap";
|
||||
import { unregisterEventListenersFromMap } from "../../utils/unregisterEventListenersFromMap";
|
||||
import { parseIoTsSchema, StrictValidationError } from "../../validatorUtils";
|
||||
import { CountersPlugin } from "../Counters/CountersPlugin";
|
||||
import { InternalPosterPlugin } from "../InternalPoster/InternalPosterPlugin";
|
||||
import { LogsPlugin } from "../Logs/LogsPlugin";
|
||||
|
@ -17,7 +16,6 @@ import { MutesPlugin } from "../Mutes/MutesPlugin";
|
|||
import { PhishermanPlugin } from "../Phisherman/PhishermanPlugin";
|
||||
import { RoleManagerPlugin } from "../RoleManager/RoleManagerPlugin";
|
||||
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
|
||||
import { availableActions } from "./actions/availableActions";
|
||||
import { AntiraidClearCmd } from "./commands/AntiraidClearCmd";
|
||||
import { SetAntiraidCmd } from "./commands/SetAntiraidCmd";
|
||||
import { ViewAntiraidCmd } from "./commands/ViewAntiraidCmd";
|
||||
|
@ -35,8 +33,7 @@ import { clearOldRecentNicknameChanges } from "./functions/clearOldNicknameChang
|
|||
import { clearOldRecentActions } from "./functions/clearOldRecentActions";
|
||||
import { clearOldRecentSpam } from "./functions/clearOldRecentSpam";
|
||||
import { pluginInfo } from "./info";
|
||||
import { availableTriggers } from "./triggers/availableTriggers";
|
||||
import { AutomodPluginType, ConfigSchema } from "./types";
|
||||
import { AutomodPluginType, zAutomodConfig } from "./types";
|
||||
|
||||
const defaultOptions = {
|
||||
config: {
|
||||
|
@ -61,129 +58,6 @@ const defaultOptions = {
|
|||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* Config preprocessor to set default values for triggers and perform extra validation
|
||||
* TODO: Separate input and output types
|
||||
*/
|
||||
const configParser = (input: unknown) => {
|
||||
const rules = (input as any).rules;
|
||||
if (rules) {
|
||||
// Loop through each rule
|
||||
for (const [name, rule] of Object.entries(rules)) {
|
||||
if (rule == null) {
|
||||
delete rules[name];
|
||||
continue;
|
||||
}
|
||||
|
||||
rule["name"] = name;
|
||||
|
||||
// If the rule doesn't have an explicitly set "enabled" property, set it to true
|
||||
if (rule["enabled"] == null) {
|
||||
rule["enabled"] = true;
|
||||
}
|
||||
|
||||
if (rule["allow_further_rules"] == null) {
|
||||
rule["allow_further_rules"] = false;
|
||||
}
|
||||
|
||||
if (rule["affects_bots"] == null) {
|
||||
rule["affects_bots"] = false;
|
||||
}
|
||||
|
||||
if (rule["affects_self"] == null) {
|
||||
rule["affects_self"] = false;
|
||||
}
|
||||
|
||||
// Loop through the rule's triggers
|
||||
if (rule["triggers"]) {
|
||||
for (const triggerObj of rule["triggers"]) {
|
||||
for (const triggerName in triggerObj) {
|
||||
if (!availableTriggers[triggerName]) {
|
||||
throw new StrictValidationError([`Unknown trigger '${triggerName}' in rule '${rule["name"]}'`]);
|
||||
}
|
||||
|
||||
const triggerBlueprint = availableTriggers[triggerName];
|
||||
|
||||
if (typeof triggerBlueprint.defaultConfig === "object" && triggerBlueprint.defaultConfig != null) {
|
||||
triggerObj[triggerName] = configUtils.mergeConfig(
|
||||
triggerBlueprint.defaultConfig,
|
||||
triggerObj[triggerName] || {},
|
||||
);
|
||||
} else {
|
||||
triggerObj[triggerName] = triggerObj[triggerName] || triggerBlueprint.defaultConfig;
|
||||
}
|
||||
|
||||
if (triggerObj[triggerName].match_attachment_type) {
|
||||
const white = triggerObj[triggerName].match_attachment_type.whitelist_enabled;
|
||||
const black = triggerObj[triggerName].match_attachment_type.blacklist_enabled;
|
||||
|
||||
if (white && black) {
|
||||
throw new StrictValidationError([
|
||||
`Cannot have both blacklist and whitelist enabled at rule <${rule["name"]}/match_attachment_type>`,
|
||||
]);
|
||||
} else if (!white && !black) {
|
||||
throw new StrictValidationError([
|
||||
`Must have either blacklist or whitelist enabled at rule <${rule["name"]}/match_attachment_type>`,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (triggerObj[triggerName].match_mime_type) {
|
||||
const white = triggerObj[triggerName].match_mime_type.whitelist_enabled;
|
||||
const black = triggerObj[triggerName].match_mime_type.blacklist_enabled;
|
||||
|
||||
if (white && black) {
|
||||
throw new StrictValidationError([
|
||||
`Cannot have both blacklist and whitelist enabled at rule <${rule["name"]}/match_mime_type>`,
|
||||
]);
|
||||
} else if (!white && !black) {
|
||||
throw new StrictValidationError([
|
||||
`Must have either blacklist or whitelist enabled at rule <${rule["name"]}/match_mime_type>`,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rule["actions"]) {
|
||||
for (const actionName in rule["actions"]) {
|
||||
if (!availableActions[actionName]) {
|
||||
throw new StrictValidationError([`Unknown action '${actionName}' in rule '${rule["name"]}'`]);
|
||||
}
|
||||
|
||||
const actionBlueprint = availableActions[actionName];
|
||||
const actionConfig = rule["actions"][actionName];
|
||||
|
||||
if (typeof actionConfig !== "object" || Array.isArray(actionConfig) || actionConfig == null) {
|
||||
rule["actions"][actionName] = actionConfig;
|
||||
} else {
|
||||
rule["actions"][actionName] = configUtils.mergeConfig(actionBlueprint.defaultConfig, actionConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enable logging of automod actions by default
|
||||
if (rule["actions"]) {
|
||||
for (const actionName in rule["actions"]) {
|
||||
if (!availableActions[actionName]) {
|
||||
throw new StrictValidationError([`Unknown action '${actionName}' in rule '${rule["name"]}'`]);
|
||||
}
|
||||
}
|
||||
|
||||
if (rule["actions"]["log"] == null) {
|
||||
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"]}'`]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parseIoTsSchema(ConfigSchema, input);
|
||||
};
|
||||
|
||||
export const AutomodPlugin = zeppelinGuildPlugin<AutomodPluginType>()({
|
||||
name: "automod",
|
||||
showInDocs: true,
|
||||
|
@ -201,7 +75,7 @@ export const AutomodPlugin = zeppelinGuildPlugin<AutomodPluginType>()({
|
|||
],
|
||||
|
||||
defaultOptions,
|
||||
configParser,
|
||||
configParser: (input) => zAutomodConfig.parse(input),
|
||||
|
||||
customOverrideCriteriaFunctions: {
|
||||
antiraid_level: (pluginData, matchParams, value) => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { PermissionFlagsBits, Snowflake } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import { nonNullish, unique } from "../../../utils";
|
||||
import z from "zod";
|
||||
import { nonNullish, unique, zSnowflake } from "../../../utils";
|
||||
import { canAssignRole } from "../../../utils/canAssignRole";
|
||||
import { getMissingPermissions } from "../../../utils/getMissingPermissions";
|
||||
import { missingPermissionError } from "../../../utils/missingPermissionError";
|
||||
|
@ -11,9 +11,10 @@ import { automodAction } from "../helpers";
|
|||
|
||||
const p = PermissionFlagsBits;
|
||||
|
||||
const configSchema = z.array(zSnowflake);
|
||||
|
||||
export const AddRolesAction = automodAction({
|
||||
configType: t.array(t.string),
|
||||
defaultConfig: [],
|
||||
configSchema,
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig, ruleName }) {
|
||||
const members = unique(contexts.map((c) => c.member).filter(nonNullish));
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { zBoundedCharacters } from "../../../utils";
|
||||
import { CountersPlugin } from "../../Counters/CountersPlugin";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin";
|
||||
import { automodAction } from "../helpers";
|
||||
|
||||
export const AddToCounterAction = automodAction({
|
||||
configType: t.type({
|
||||
counter: t.string,
|
||||
amount: t.number,
|
||||
}),
|
||||
const configSchema = z.object({
|
||||
counter: zBoundedCharacters(0, 100),
|
||||
amount: z.number(),
|
||||
});
|
||||
|
||||
defaultConfig: {},
|
||||
export const AddToCounterAction = automodAction({
|
||||
configSchema,
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig, ruleName }) {
|
||||
const countersPlugin = pluginData.getPlugin(CountersPlugin);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Snowflake } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import { erisAllowedMentionsToDjsMentionOptions } from "src/utils/erisAllowedMentionsToDjsMentionOptions";
|
||||
import z from "zod";
|
||||
import { LogType } from "../../../data/LogType";
|
||||
import {
|
||||
createTypedTemplateSafeValueContainer,
|
||||
|
@ -12,10 +12,12 @@ import {
|
|||
chunkMessageLines,
|
||||
isTruthy,
|
||||
messageLink,
|
||||
tAllowedMentions,
|
||||
tNormalizedNullOptional,
|
||||
validateAndParseMessageContent,
|
||||
verboseChannelMention,
|
||||
zAllowedMentions,
|
||||
zBoundedCharacters,
|
||||
zNullishToUndefined,
|
||||
zSnowflake
|
||||
} from "../../../utils";
|
||||
import { messageIsEmpty } from "../../../utils/messageIsEmpty";
|
||||
import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects";
|
||||
|
@ -23,14 +25,14 @@ import { InternalPosterPlugin } from "../../InternalPoster/InternalPosterPlugin"
|
|||
import { LogsPlugin } from "../../Logs/LogsPlugin";
|
||||
import { automodAction } from "../helpers";
|
||||
|
||||
export const AlertAction = automodAction({
|
||||
configType: t.type({
|
||||
channel: t.string,
|
||||
text: t.string,
|
||||
allowed_mentions: tNormalizedNullOptional(tAllowedMentions),
|
||||
}),
|
||||
const configSchema = z.object({
|
||||
channel: zSnowflake,
|
||||
text: zBoundedCharacters(1, 4000),
|
||||
allowed_mentions: zNullishToUndefined(zAllowedMentions.nullable().default(null)),
|
||||
});
|
||||
|
||||
defaultConfig: {},
|
||||
export const AlertAction = automodAction({
|
||||
configSchema,
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig, ruleName, matchResult }) {
|
||||
const channel = pluginData.guild.channels.cache.get(actionConfig.channel as Snowflake);
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { AnyThreadChannel } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { noop } from "../../../utils";
|
||||
import { automodAction } from "../helpers";
|
||||
|
||||
const configSchema = z.strictObject({});
|
||||
|
||||
export const ArchiveThreadAction = automodAction({
|
||||
configType: t.type({}),
|
||||
defaultConfig: {},
|
||||
configSchema,
|
||||
|
||||
async apply({ pluginData, contexts }) {
|
||||
const threads = contexts
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import * as t from "io-ts";
|
||||
import { AutomodActionBlueprint } from "../helpers";
|
||||
import { AddRolesAction } from "./addRoles";
|
||||
import { AddToCounterAction } from "./addToCounter";
|
||||
|
@ -19,7 +18,7 @@ import { SetSlowmodeAction } from "./setSlowmode";
|
|||
import { StartThreadAction } from "./startThread";
|
||||
import { WarnAction } from "./warn";
|
||||
|
||||
export const availableActions: Record<string, AutomodActionBlueprint<any>> = {
|
||||
export const availableActions = {
|
||||
clean: CleanAction,
|
||||
warn: WarnAction,
|
||||
mute: MuteAction,
|
||||
|
@ -38,25 +37,4 @@ export const availableActions: Record<string, AutomodActionBlueprint<any>> = {
|
|||
start_thread: StartThreadAction,
|
||||
archive_thread: ArchiveThreadAction,
|
||||
change_perms: ChangePermsAction,
|
||||
};
|
||||
|
||||
export const AvailableActions = t.type({
|
||||
clean: CleanAction.configType,
|
||||
warn: WarnAction.configType,
|
||||
mute: MuteAction.configType,
|
||||
kick: KickAction.configType,
|
||||
ban: BanAction.configType,
|
||||
alert: AlertAction.configType,
|
||||
change_nickname: ChangeNicknameAction.configType,
|
||||
log: LogAction.configType,
|
||||
add_roles: AddRolesAction.configType,
|
||||
remove_roles: RemoveRolesAction.configType,
|
||||
set_antiraid_level: SetAntiraidLevelAction.configType,
|
||||
reply: ReplyAction.configType,
|
||||
add_to_counter: AddToCounterAction.configType,
|
||||
set_counter: SetCounterAction.configType,
|
||||
set_slowmode: SetSlowmodeAction.configType,
|
||||
start_thread: StartThreadAction.configType,
|
||||
archive_thread: ArchiveThreadAction.configType,
|
||||
change_perms: ChangePermsAction.configType,
|
||||
});
|
||||
} satisfies Record<string, AutomodActionBlueprint<any>>;
|
||||
|
|
|
@ -1,25 +1,23 @@
|
|||
import * as t from "io-ts";
|
||||
import { convertDelayStringToMS, nonNullish, tDelayString, tNullable, unique } from "../../../utils";
|
||||
import z from "zod";
|
||||
import { convertDelayStringToMS, nonNullish, unique, zBoundedCharacters, zDelayString, zSnowflake } from "../../../utils";
|
||||
import { CaseArgs } from "../../Cases/types";
|
||||
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
|
||||
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
|
||||
import { automodAction } from "../helpers";
|
||||
import { zNotify } from "../types";
|
||||
|
||||
const configSchema = z.strictObject({
|
||||
reason: zBoundedCharacters(0, 4000).nullable().default(null),
|
||||
duration: zDelayString.nullable().default(null),
|
||||
notify: zNotify.nullable().default(null),
|
||||
notifyChannel: zSnowflake.nullable().default(null),
|
||||
deleteMessageDays: z.number().nullable().default(null),
|
||||
postInCaseLog: z.boolean().nullable().default(null),
|
||||
hide_case: z.boolean().nullable().default(false),
|
||||
});
|
||||
|
||||
export const BanAction = automodAction({
|
||||
configType: t.type({
|
||||
reason: tNullable(t.string),
|
||||
duration: tNullable(tDelayString),
|
||||
notify: tNullable(t.string),
|
||||
notifyChannel: tNullable(t.string),
|
||||
deleteMessageDays: tNullable(t.number),
|
||||
postInCaseLog: tNullable(t.boolean),
|
||||
hide_case: tNullable(t.boolean),
|
||||
}),
|
||||
|
||||
defaultConfig: {
|
||||
notify: null, // Use defaults from ModActions
|
||||
hide_case: false,
|
||||
},
|
||||
configSchema,
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig, matchResult }) {
|
||||
const reason = actionConfig.reason || "Kicked automatically";
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
import * as t from "io-ts";
|
||||
import { nonNullish, unique } from "../../../utils";
|
||||
import z from "zod";
|
||||
import { nonNullish, unique, zBoundedCharacters } from "../../../utils";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin";
|
||||
import { automodAction } from "../helpers";
|
||||
|
||||
export const ChangeNicknameAction = automodAction({
|
||||
configType: t.union([
|
||||
t.string,
|
||||
t.type({
|
||||
name: t.string,
|
||||
configSchema: z.union([
|
||||
zBoundedCharacters(0, 32),
|
||||
z.strictObject({
|
||||
name: zBoundedCharacters(0, 32),
|
||||
}),
|
||||
]),
|
||||
|
||||
defaultConfig: {},
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig }) {
|
||||
const members = unique(contexts.map((c) => c.member).filter(nonNullish));
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { PermissionsBitField, PermissionsString } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import { U } from "ts-toolbelt";
|
||||
import z from "zod";
|
||||
import { TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter";
|
||||
import { isValidSnowflake, noop, tNullable, tPartialDictionary } from "../../../utils";
|
||||
import { isValidSnowflake, keys, noop, zSnowflake } from "../../../utils";
|
||||
import {
|
||||
guildToTemplateSafeGuild,
|
||||
savedMessageToTemplateSafeSavedMessage,
|
||||
|
@ -59,16 +60,19 @@ const realToLegacyMap = Object.entries(legacyPermMap).reduce((map, pair) => {
|
|||
return map;
|
||||
}, {}) as Record<keyof typeof PermissionsBitField.Flags, keyof typeof legacyPermMap>;
|
||||
|
||||
const permissionNames = keys(PermissionsBitField.Flags) as U.ListOf<keyof typeof PermissionsBitField.Flags>;
|
||||
const legacyPermissionNames = keys(legacyPermMap) as U.ListOf<keyof typeof legacyPermMap>;
|
||||
const allPermissionNames = [...permissionNames, ...legacyPermissionNames] as const;
|
||||
|
||||
export const ChangePermsAction = automodAction({
|
||||
configType: t.type({
|
||||
target: t.string,
|
||||
channel: tNullable(t.string),
|
||||
perms: tPartialDictionary(
|
||||
t.union([t.keyof(PermissionsBitField.Flags), t.keyof(legacyPermMap)]),
|
||||
tNullable(t.boolean),
|
||||
configSchema: z.strictObject({
|
||||
target: zSnowflake,
|
||||
channel: zSnowflake.nullable().default(null),
|
||||
perms: z.record(
|
||||
z.enum(allPermissionNames),
|
||||
z.boolean().nullable(),
|
||||
),
|
||||
}),
|
||||
defaultConfig: {},
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig }) {
|
||||
const user = contexts.find((c) => c.user)?.user;
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import { GuildTextBasedChannel, Snowflake } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { LogType } from "../../../data/LogType";
|
||||
import { noop } from "../../../utils";
|
||||
import { automodAction } from "../helpers";
|
||||
|
||||
export const CleanAction = automodAction({
|
||||
configType: t.boolean,
|
||||
defaultConfig: false,
|
||||
configSchema: z.boolean().default(false),
|
||||
|
||||
async apply({ pluginData, contexts, ruleName }) {
|
||||
const messageIdsToDeleteByChannelId: Map<string, string[]> = new Map();
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { zBoundedCharacters } from "../../../utils";
|
||||
import { automodAction } from "../helpers";
|
||||
|
||||
export const ExampleAction = automodAction({
|
||||
configType: t.type({
|
||||
someValue: t.string,
|
||||
configSchema: z.strictObject({
|
||||
someValue: zBoundedCharacters(0, 1000),
|
||||
}),
|
||||
|
||||
defaultConfig: {},
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async apply({ pluginData, contexts, actionConfig }) {
|
||||
// TODO: Everything
|
||||
|
|
|
@ -1,24 +1,20 @@
|
|||
import * as t from "io-ts";
|
||||
import { asyncMap, nonNullish, resolveMember, tNullable, unique } from "../../../utils";
|
||||
import z from "zod";
|
||||
import { asyncMap, nonNullish, resolveMember, unique, zBoundedCharacters, zSnowflake } from "../../../utils";
|
||||
import { CaseArgs } from "../../Cases/types";
|
||||
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
|
||||
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
|
||||
import { automodAction } from "../helpers";
|
||||
import { zNotify } from "../types";
|
||||
|
||||
export const KickAction = automodAction({
|
||||
configType: t.type({
|
||||
reason: tNullable(t.string),
|
||||
notify: tNullable(t.string),
|
||||
notifyChannel: tNullable(t.string),
|
||||
postInCaseLog: tNullable(t.boolean),
|
||||
hide_case: tNullable(t.boolean),
|
||||
configSchema: z.strictObject({
|
||||
reason: zBoundedCharacters(0, 4000).nullable().default(null),
|
||||
notify: zNotify.nullable().default(null),
|
||||
notifyChannel: zSnowflake.nullable().default(null),
|
||||
postInCaseLog: z.boolean().nullable().default(null),
|
||||
hide_case: z.boolean().nullable().default(false),
|
||||
}),
|
||||
|
||||
defaultConfig: {
|
||||
notify: null, // Use defaults from ModActions
|
||||
hide_case: false,
|
||||
},
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig, matchResult }) {
|
||||
const reason = actionConfig.reason || "Kicked automatically";
|
||||
const contactMethods = actionConfig.notify ? resolveActionContactMethods(pluginData, actionConfig) : undefined;
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { isTruthy, unique } from "../../../utils";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin";
|
||||
import { automodAction } from "../helpers";
|
||||
|
||||
export const LogAction = automodAction({
|
||||
configType: t.boolean,
|
||||
defaultConfig: true,
|
||||
configSchema: z.boolean().default(true),
|
||||
|
||||
async apply({ pluginData, contexts, ruleName, matchResult }) {
|
||||
const users = unique(contexts.map((c) => c.user)).filter(isTruthy);
|
||||
|
|
|
@ -1,29 +1,25 @@
|
|||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError";
|
||||
import { convertDelayStringToMS, nonNullish, tDelayString, tNullable, unique } from "../../../utils";
|
||||
import { convertDelayStringToMS, nonNullish, unique, zBoundedCharacters, zDelayString, zSnowflake } from "../../../utils";
|
||||
import { CaseArgs } from "../../Cases/types";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin";
|
||||
import { MutesPlugin } from "../../Mutes/MutesPlugin";
|
||||
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
|
||||
import { automodAction } from "../helpers";
|
||||
import { zNotify } from "../types";
|
||||
|
||||
export const MuteAction = automodAction({
|
||||
configType: t.type({
|
||||
reason: tNullable(t.string),
|
||||
duration: tNullable(tDelayString),
|
||||
notify: tNullable(t.string),
|
||||
notifyChannel: tNullable(t.string),
|
||||
remove_roles_on_mute: tNullable(t.union([t.boolean, t.array(t.string)])),
|
||||
restore_roles_on_mute: tNullable(t.union([t.boolean, t.array(t.string)])),
|
||||
postInCaseLog: tNullable(t.boolean),
|
||||
hide_case: tNullable(t.boolean),
|
||||
configSchema: z.strictObject({
|
||||
reason: zBoundedCharacters(0, 4000).nullable().default(null),
|
||||
duration: zDelayString.nullable().default(null),
|
||||
notify: zNotify.nullable().default(null),
|
||||
notifyChannel: zSnowflake.nullable().default(null),
|
||||
remove_roles_on_mute: z.union([z.boolean(), z.array(zSnowflake)]).nullable().default(null),
|
||||
restore_roles_on_mute: z.union([z.boolean(), z.array(zSnowflake)]).nullable().default(null),
|
||||
postInCaseLog: z.boolean().nullable().default(null),
|
||||
hide_case: z.boolean().nullable().default(false),
|
||||
}),
|
||||
|
||||
defaultConfig: {
|
||||
notify: null, // Use defaults from ModActions
|
||||
hide_case: false,
|
||||
},
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig, ruleName, matchResult }) {
|
||||
const duration = actionConfig.duration ? convertDelayStringToMS(actionConfig.duration)! : undefined;
|
||||
const reason = actionConfig.reason || "Muted automatically";
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { PermissionFlagsBits, Snowflake } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import { nonNullish, unique } from "../../../utils";
|
||||
import z from "zod";
|
||||
import { nonNullish, unique, zSnowflake } from "../../../utils";
|
||||
import { canAssignRole } from "../../../utils/canAssignRole";
|
||||
import { getMissingPermissions } from "../../../utils/getMissingPermissions";
|
||||
import { memberRolesLock } from "../../../utils/lockNameHelpers";
|
||||
|
@ -12,9 +12,7 @@ import { automodAction } from "../helpers";
|
|||
const p = PermissionFlagsBits;
|
||||
|
||||
export const RemoveRolesAction = automodAction({
|
||||
configType: t.array(t.string),
|
||||
|
||||
defaultConfig: [],
|
||||
configSchema: z.array(zSnowflake).default([]),
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig, ruleName }) {
|
||||
const members = unique(contexts.map((c) => c.member).filter(nonNullish));
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import { GuildTextBasedChannel, MessageCreateOptions, PermissionsBitField, Snowflake, User } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter";
|
||||
import {
|
||||
convertDelayStringToMS,
|
||||
noop,
|
||||
renderRecursively,
|
||||
tDelayString,
|
||||
tMessageContent,
|
||||
tNullable,
|
||||
unique,
|
||||
validateAndParseMessageContent,
|
||||
verboseChannelMention,
|
||||
zBoundedCharacters,
|
||||
zDelayString,
|
||||
zMessageContent
|
||||
} from "../../../utils";
|
||||
import { hasDiscordPermissions } from "../../../utils/hasDiscordPermissions";
|
||||
import { messageIsEmpty } from "../../../utils/messageIsEmpty";
|
||||
|
@ -20,17 +20,15 @@ import { automodAction } from "../helpers";
|
|||
import { AutomodContext } from "../types";
|
||||
|
||||
export const ReplyAction = automodAction({
|
||||
configType: t.union([
|
||||
t.string,
|
||||
t.type({
|
||||
text: tMessageContent,
|
||||
auto_delete: tNullable(t.union([tDelayString, t.number])),
|
||||
inline: tNullable(t.boolean),
|
||||
configSchema: z.union([
|
||||
zBoundedCharacters(0, 4000),
|
||||
z.strictObject({
|
||||
text: zMessageContent,
|
||||
auto_delete: z.union([zDelayString, z.number()]).nullable().default(null),
|
||||
inline: z.boolean().default(false),
|
||||
}),
|
||||
]),
|
||||
|
||||
defaultConfig: {},
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig, ruleName }) {
|
||||
const contextsWithTextChannels = contexts
|
||||
.filter((c) => c.message?.channel_id)
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import * as t from "io-ts";
|
||||
import { tNullable } from "../../../utils";
|
||||
import { zBoundedCharacters } from "../../../utils";
|
||||
import { setAntiraidLevel } from "../functions/setAntiraidLevel";
|
||||
import { automodAction } from "../helpers";
|
||||
|
||||
export const SetAntiraidLevelAction = automodAction({
|
||||
configType: tNullable(t.string),
|
||||
defaultConfig: "",
|
||||
configSchema: zBoundedCharacters(0, 100).nullable(),
|
||||
|
||||
async apply({ pluginData, actionConfig }) {
|
||||
setAntiraidLevel(pluginData, actionConfig ?? null);
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { zBoundedCharacters } from "../../../utils";
|
||||
import { CountersPlugin } from "../../Counters/CountersPlugin";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin";
|
||||
import { automodAction } from "../helpers";
|
||||
|
||||
export const SetCounterAction = automodAction({
|
||||
configType: t.type({
|
||||
counter: t.string,
|
||||
value: t.number,
|
||||
configSchema: z.strictObject({
|
||||
counter: zBoundedCharacters(0, 100),
|
||||
value: z.number(),
|
||||
}),
|
||||
|
||||
defaultConfig: {},
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig, ruleName }) {
|
||||
const countersPlugin = pluginData.getPlugin(CountersPlugin);
|
||||
if (!countersPlugin.counterExists(actionConfig.counter)) {
|
||||
|
|
|
@ -1,19 +1,15 @@
|
|||
import { ChannelType, GuildTextBasedChannel, Snowflake } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import { convertDelayStringToMS, isDiscordAPIError, tDelayString, tNullable } from "../../../utils";
|
||||
import z from "zod";
|
||||
import { convertDelayStringToMS, isDiscordAPIError, zDelayString, zSnowflake } from "../../../utils";
|
||||
import { LogsPlugin } from "../../Logs/LogsPlugin";
|
||||
import { automodAction } from "../helpers";
|
||||
|
||||
export const SetSlowmodeAction = automodAction({
|
||||
configType: t.type({
|
||||
channels: t.array(t.string),
|
||||
duration: tNullable(tDelayString),
|
||||
configSchema: z.strictObject({
|
||||
channels: z.array(zSnowflake),
|
||||
duration: zDelayString.nullable().default("10s"),
|
||||
}),
|
||||
|
||||
defaultConfig: {
|
||||
duration: "10s",
|
||||
},
|
||||
|
||||
async apply({ pluginData, actionConfig }) {
|
||||
const slowmodeMs = Math.max(actionConfig.duration ? convertDelayStringToMS(actionConfig.duration)! : 0, 0);
|
||||
|
||||
|
|
|
@ -5,9 +5,9 @@ import {
|
|||
ThreadAutoArchiveDuration,
|
||||
ThreadChannel,
|
||||
} from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter";
|
||||
import { MINUTES, convertDelayStringToMS, noop, tDelayString, tNullable } from "../../../utils";
|
||||
import { MINUTES, convertDelayStringToMS, noop, zBoundedCharacters, zDelayString } from "../../../utils";
|
||||
import { savedMessageToTemplateSafeSavedMessage, userToTemplateSafeUser } from "../../../utils/templateSafeObjects";
|
||||
import { automodAction } from "../helpers";
|
||||
|
||||
|
@ -19,18 +19,14 @@ const validThreadAutoArchiveDurations: ThreadAutoArchiveDuration[] = [
|
|||
];
|
||||
|
||||
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),
|
||||
configSchema: z.strictObject({
|
||||
name: zBoundedCharacters(1, 100).nullable(),
|
||||
auto_archive: zDelayString,
|
||||
private: z.boolean().default(false),
|
||||
slowmode: zDelayString.nullable().default(null),
|
||||
limit_per_channel: z.number().nullable().default(5),
|
||||
}),
|
||||
|
||||
defaultConfig: {
|
||||
limit_per_channel: 5,
|
||||
},
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig }) {
|
||||
// check if the message still exists, we don't want to create threads for deleted messages
|
||||
const threads = contexts.filter((c) => {
|
||||
|
|
|
@ -1,24 +1,20 @@
|
|||
import * as t from "io-ts";
|
||||
import { asyncMap, nonNullish, resolveMember, tNullable, unique } from "../../../utils";
|
||||
import z from "zod";
|
||||
import { asyncMap, nonNullish, resolveMember, unique, zBoundedCharacters, zSnowflake } from "../../../utils";
|
||||
import { CaseArgs } from "../../Cases/types";
|
||||
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
|
||||
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
|
||||
import { automodAction } from "../helpers";
|
||||
import { zNotify } from "../types";
|
||||
|
||||
export const WarnAction = automodAction({
|
||||
configType: t.type({
|
||||
reason: tNullable(t.string),
|
||||
notify: tNullable(t.string),
|
||||
notifyChannel: tNullable(t.string),
|
||||
postInCaseLog: tNullable(t.boolean),
|
||||
hide_case: tNullable(t.boolean),
|
||||
configSchema: z.strictObject({
|
||||
reason: zBoundedCharacters(0, 4000).nullable().default(null),
|
||||
notify: zNotify.nullable().default(null),
|
||||
notifyChannel: zSnowflake.nullable().default(null),
|
||||
postInCaseLog: z.boolean().nullable().default(null),
|
||||
hide_case: z.boolean().nullable().default(false),
|
||||
}),
|
||||
|
||||
defaultConfig: {
|
||||
notify: null, // Use defaults from ModActions
|
||||
hide_case: false,
|
||||
},
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig, matchResult }) {
|
||||
const reason = actionConfig.reason || "Warned automatically";
|
||||
const contactMethods = actionConfig.notify ? resolveActionContactMethods(pluginData, actionConfig) : undefined;
|
||||
|
|
|
@ -1,28 +1,27 @@
|
|||
import * as t from "io-ts";
|
||||
import { SavedMessage } from "../../../data/entities/SavedMessage";
|
||||
import { humanizeDurationShort } from "../../../humanizeDurationShort";
|
||||
import { getBaseUrl } from "../../../pluginUtils";
|
||||
import { convertDelayStringToMS, sorter, tDelayString, tNullable } from "../../../utils";
|
||||
import { convertDelayStringToMS, sorter, zDelayString } from "../../../utils";
|
||||
import { RecentActionType } from "../constants";
|
||||
import { automodTrigger } from "../helpers";
|
||||
import { findRecentSpam } from "./findRecentSpam";
|
||||
import { getMatchingMessageRecentActions } from "./getMatchingMessageRecentActions";
|
||||
import { getMessageSpamIdentifier } from "./getSpamIdentifier";
|
||||
|
||||
const MessageSpamTriggerConfig = t.type({
|
||||
amount: t.number,
|
||||
within: tDelayString,
|
||||
per_channel: tNullable(t.boolean),
|
||||
});
|
||||
import z from "zod";
|
||||
|
||||
interface TMessageSpamMatchResultType {
|
||||
archiveId: string;
|
||||
}
|
||||
|
||||
const configSchema = z.strictObject({
|
||||
amount: z.number().int(),
|
||||
within: zDelayString,
|
||||
per_channel: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export function createMessageSpamTrigger(spamType: RecentActionType, prettyName: string) {
|
||||
return automodTrigger<TMessageSpamMatchResultType>()({
|
||||
configType: MessageSpamTriggerConfig,
|
||||
defaultConfig: {},
|
||||
configSchema,
|
||||
|
||||
async match({ pluginData, context, triggerConfig }) {
|
||||
if (!context.message) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as t from "io-ts";
|
||||
import { GuildPluginData } from "knub";
|
||||
import { Awaitable } from "../../utils/typeUtils";
|
||||
import { AutomodContext, AutomodPluginType } from "./types";
|
||||
import z, { ZodTypeAny } from "zod";
|
||||
|
||||
interface BaseAutomodTriggerMatchResult {
|
||||
extraContexts?: AutomodContext[];
|
||||
|
@ -31,21 +31,19 @@ type AutomodTriggerRenderMatchInformationFn<TConfigType, TMatchResultExtra> = (m
|
|||
matchResult: AutomodTriggerMatchResult<TMatchResultExtra>;
|
||||
}) => Awaitable<string>;
|
||||
|
||||
export interface AutomodTriggerBlueprint<TConfigType extends t.Any, TMatchResultExtra> {
|
||||
configType: TConfigType;
|
||||
defaultConfig: Partial<t.TypeOf<TConfigType>>;
|
||||
|
||||
match: AutomodTriggerMatchFn<t.TypeOf<TConfigType>, TMatchResultExtra>;
|
||||
renderMatchInformation: AutomodTriggerRenderMatchInformationFn<t.TypeOf<TConfigType>, TMatchResultExtra>;
|
||||
export interface AutomodTriggerBlueprint<TConfigSchema extends ZodTypeAny, TMatchResultExtra> {
|
||||
configSchema: TConfigSchema;
|
||||
match: AutomodTriggerMatchFn<z.output<TConfigSchema>, TMatchResultExtra>;
|
||||
renderMatchInformation: AutomodTriggerRenderMatchInformationFn<z.output<TConfigSchema>, TMatchResultExtra>;
|
||||
}
|
||||
|
||||
export function automodTrigger<TMatchResultExtra>(): <TConfigType extends t.Any>(
|
||||
blueprint: AutomodTriggerBlueprint<TConfigType, TMatchResultExtra>,
|
||||
) => AutomodTriggerBlueprint<TConfigType, TMatchResultExtra>;
|
||||
export function automodTrigger<TMatchResultExtra>(): <TConfigSchema extends ZodTypeAny>(
|
||||
blueprint: AutomodTriggerBlueprint<TConfigSchema, TMatchResultExtra>,
|
||||
) => AutomodTriggerBlueprint<TConfigSchema, TMatchResultExtra>;
|
||||
|
||||
export function automodTrigger<TConfigType extends t.Any>(
|
||||
blueprint: AutomodTriggerBlueprint<TConfigType, unknown>,
|
||||
): AutomodTriggerBlueprint<TConfigType, unknown>;
|
||||
export function automodTrigger<TConfigSchema extends ZodTypeAny>(
|
||||
blueprint: AutomodTriggerBlueprint<TConfigSchema, unknown>,
|
||||
): AutomodTriggerBlueprint<TConfigSchema, unknown>;
|
||||
|
||||
export function automodTrigger(...args) {
|
||||
if (args.length) {
|
||||
|
@ -63,15 +61,13 @@ type AutomodActionApplyFn<TConfigType> = (meta: {
|
|||
matchResult: AutomodTriggerMatchResult;
|
||||
}) => Awaitable<void>;
|
||||
|
||||
export interface AutomodActionBlueprint<TConfigType extends t.Any> {
|
||||
configType: TConfigType;
|
||||
defaultConfig: Partial<t.TypeOf<TConfigType>>;
|
||||
|
||||
apply: AutomodActionApplyFn<t.TypeOf<TConfigType>>;
|
||||
export interface AutomodActionBlueprint<TConfigSchema extends ZodTypeAny> {
|
||||
configSchema: TConfigSchema;
|
||||
apply: AutomodActionApplyFn<z.output<TConfigSchema>>;
|
||||
}
|
||||
|
||||
export function automodAction<TConfigType extends t.Any>(
|
||||
blueprint: AutomodActionBlueprint<TConfigType>,
|
||||
): AutomodActionBlueprint<TConfigType> {
|
||||
export function automodAction<TConfigSchema extends ZodTypeAny>(
|
||||
blueprint: AutomodActionBlueprint<TConfigSchema>,
|
||||
): AutomodActionBlueprint<TConfigSchema> {
|
||||
return blueprint;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { trimPluginDescription } from "../../utils";
|
||||
import { ZeppelinGuildPluginBlueprint } from "../ZeppelinPluginBlueprint";
|
||||
import { ConfigSchema } from "./types";
|
||||
import { zAutomodConfig } from "./types";
|
||||
|
||||
export const pluginInfo: ZeppelinGuildPluginBlueprint["info"] = {
|
||||
prettyName: "Automod",
|
||||
|
@ -100,5 +100,5 @@ export const pluginInfo: ZeppelinGuildPluginBlueprint["info"] = {
|
|||
{matchSummary}
|
||||
~~~
|
||||
`),
|
||||
configSchema: ConfigSchema,
|
||||
configSchema: zAutomodConfig,
|
||||
};
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
import * as t from "io-ts";
|
||||
import { tNullable } from "../../../utils";
|
||||
import { automodTrigger } from "../helpers";
|
||||
import z from "zod";
|
||||
|
||||
interface AntiraidLevelTriggerResult {}
|
||||
|
||||
export const AntiraidLevelTrigger = automodTrigger<AntiraidLevelTriggerResult>()({
|
||||
configType: t.type({
|
||||
level: tNullable(t.string),
|
||||
}),
|
||||
const configSchema = z.strictObject({
|
||||
level: z.nullable(z.string().max(100)),
|
||||
});
|
||||
|
||||
defaultConfig: {},
|
||||
export const AntiraidLevelTrigger = automodTrigger<AntiraidLevelTriggerResult>()({
|
||||
configSchema,
|
||||
|
||||
async match({ triggerConfig, context }) {
|
||||
if (!context.antiraid) {
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { Snowflake } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import { verboseChannelMention } from "../../../utils";
|
||||
import { automodTrigger } from "../helpers";
|
||||
import z from "zod";
|
||||
|
||||
interface AnyMessageResultType {}
|
||||
|
||||
export const AnyMessageTrigger = automodTrigger<AnyMessageResultType>()({
|
||||
configType: t.type({}),
|
||||
const configSchema = z.strictObject({});
|
||||
|
||||
defaultConfig: {},
|
||||
export const AnyMessageTrigger = automodTrigger<AnyMessageResultType>()({
|
||||
configSchema,
|
||||
|
||||
async match({ context }) {
|
||||
if (!context.message) {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import * as t from "io-ts";
|
||||
import { AutomodTriggerBlueprint } from "../helpers";
|
||||
import { AntiraidLevelTrigger } from "./antiraidLevel";
|
||||
import { AnyMessageTrigger } from "./anyMessage";
|
||||
|
@ -45,6 +44,7 @@ export const availableTriggers: Record<string, AutomodTriggerBlueprint<any, any>
|
|||
match_attachment_type: MatchAttachmentTypeTrigger,
|
||||
match_mime_type: MatchMimeTypeTrigger,
|
||||
member_join: MemberJoinTrigger,
|
||||
member_leave: MemberLeaveTrigger,
|
||||
role_added: RoleAddedTrigger,
|
||||
role_removed: RoleRemovedTrigger,
|
||||
|
||||
|
@ -76,46 +76,3 @@ export const availableTriggers: Record<string, AutomodTriggerBlueprint<any, any>
|
|||
thread_archive: ThreadArchiveTrigger,
|
||||
thread_unarchive: ThreadUnarchiveTrigger,
|
||||
};
|
||||
|
||||
export const AvailableTriggers = t.type({
|
||||
any_message: AnyMessageTrigger.configType,
|
||||
|
||||
match_words: MatchWordsTrigger.configType,
|
||||
match_regex: MatchRegexTrigger.configType,
|
||||
match_invites: MatchInvitesTrigger.configType,
|
||||
match_links: MatchLinksTrigger.configType,
|
||||
match_attachment_type: MatchAttachmentTypeTrigger.configType,
|
||||
match_mime_type: MatchMimeTypeTrigger.configType,
|
||||
member_join: MemberJoinTrigger.configType,
|
||||
member_leave: MemberLeaveTrigger.configType,
|
||||
role_added: RoleAddedTrigger.configType,
|
||||
role_removed: RoleRemovedTrigger.configType,
|
||||
|
||||
message_spam: MessageSpamTrigger.configType,
|
||||
mention_spam: MentionSpamTrigger.configType,
|
||||
link_spam: LinkSpamTrigger.configType,
|
||||
attachment_spam: AttachmentSpamTrigger.configType,
|
||||
emoji_spam: EmojiSpamTrigger.configType,
|
||||
line_spam: LineSpamTrigger.configType,
|
||||
character_spam: CharacterSpamTrigger.configType,
|
||||
member_join_spam: MemberJoinSpamTrigger.configType,
|
||||
sticker_spam: StickerSpamTrigger.configType,
|
||||
thread_create_spam: ThreadCreateSpamTrigger.configType,
|
||||
|
||||
counter_trigger: CounterTrigger.configType,
|
||||
|
||||
note: NoteTrigger.configType,
|
||||
warn: WarnTrigger.configType,
|
||||
mute: MuteTrigger.configType,
|
||||
unmute: UnmuteTrigger.configType,
|
||||
kick: KickTrigger.configType,
|
||||
ban: BanTrigger.configType,
|
||||
unban: UnbanTrigger.configType,
|
||||
|
||||
antiraid_level: AntiraidLevelTrigger.configType,
|
||||
|
||||
thread_create: ThreadCreateTrigger.configType,
|
||||
thread_delete: ThreadDeleteTrigger.configType,
|
||||
thread_archive: ThreadArchiveTrigger.configType,
|
||||
thread_unarchive: ThreadUnarchiveTrigger.configType,
|
||||
});
|
||||
|
|
|
@ -1,19 +1,16 @@
|
|||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
// tslint:disable-next-line:no-empty-interface
|
||||
interface BanTriggerResultType {}
|
||||
|
||||
export const BanTrigger = automodTrigger<BanTriggerResultType>()({
|
||||
configType: t.type({
|
||||
manual: t.boolean,
|
||||
automatic: t.boolean,
|
||||
}),
|
||||
const configSchema = z.strictObject({
|
||||
manual: z.boolean().default(true),
|
||||
automatic: z.boolean().default(true),
|
||||
});
|
||||
|
||||
defaultConfig: {
|
||||
manual: true,
|
||||
automatic: true,
|
||||
},
|
||||
export const BanTrigger = automodTrigger<BanTriggerResultType>()({
|
||||
configSchema,
|
||||
|
||||
async match({ context, triggerConfig }) {
|
||||
if (context.modAction?.type !== "ban") {
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
import * as t from "io-ts";
|
||||
import { tNullable } from "../../../utils";
|
||||
import z from "zod";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
// tslint:disable-next-line
|
||||
interface CounterTriggerResult {}
|
||||
|
||||
export const CounterTrigger = automodTrigger<CounterTriggerResult>()({
|
||||
configType: t.type({
|
||||
counter: t.string,
|
||||
trigger: t.string,
|
||||
reverse: tNullable(t.boolean),
|
||||
}),
|
||||
const configSchema = z.strictObject({
|
||||
counter: z.string().max(100),
|
||||
trigger: z.string().max(100),
|
||||
reverse: z.boolean().optional(),
|
||||
});
|
||||
|
||||
defaultConfig: {},
|
||||
export const CounterTrigger = automodTrigger<CounterTriggerResult>()({
|
||||
configSchema,
|
||||
|
||||
async match({ triggerConfig, context }) {
|
||||
if (!context.counterTrigger) {
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
interface ExampleMatchResultType {
|
||||
isBanana: boolean;
|
||||
}
|
||||
|
||||
export const ExampleTrigger = automodTrigger<ExampleMatchResultType>()({
|
||||
configType: t.type({
|
||||
allowedFruits: t.array(t.string),
|
||||
}),
|
||||
const configSchema = z.strictObject({
|
||||
allowedFruits: z.array(z.string().max(100)).max(50).default(["peach", "banana"]),
|
||||
});
|
||||
|
||||
defaultConfig: {
|
||||
allowedFruits: ["peach", "banana"],
|
||||
},
|
||||
export const ExampleTrigger = automodTrigger<ExampleMatchResultType>()({
|
||||
configSchema,
|
||||
|
||||
async match({ triggerConfig, context }) {
|
||||
const foundFruit = triggerConfig.allowedFruits.find((fruit) => context.message?.data.content === fruit);
|
||||
|
|
|
@ -1,19 +1,16 @@
|
|||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
// tslint:disable-next-line:no-empty-interface
|
||||
interface KickTriggerResultType {}
|
||||
|
||||
export const KickTrigger = automodTrigger<KickTriggerResultType>()({
|
||||
configType: t.type({
|
||||
manual: t.boolean,
|
||||
automatic: t.boolean,
|
||||
}),
|
||||
const configSchema = z.strictObject({
|
||||
manual: z.boolean().default(true),
|
||||
automatic: z.boolean().default(true),
|
||||
});
|
||||
|
||||
defaultConfig: {
|
||||
manual: true,
|
||||
automatic: true,
|
||||
},
|
||||
export const KickTrigger = automodTrigger<KickTriggerResultType>()({
|
||||
configSchema,
|
||||
|
||||
async match({ context, triggerConfig }) {
|
||||
if (context.modAction?.type !== "kick") {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { escapeInlineCode, Snowflake } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { asSingleLine, messageSummary, verboseChannelMention } from "../../../utils";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
|
@ -8,20 +8,31 @@ interface MatchResultType {
|
|||
mode: "blacklist" | "whitelist";
|
||||
}
|
||||
|
||||
export const MatchAttachmentTypeTrigger = automodTrigger<MatchResultType>()({
|
||||
configType: t.type({
|
||||
filetype_blacklist: t.array(t.string),
|
||||
blacklist_enabled: t.boolean,
|
||||
filetype_whitelist: t.array(t.string),
|
||||
whitelist_enabled: t.boolean,
|
||||
}),
|
||||
const configSchema = z.strictObject({
|
||||
filetype_blacklist: z.array(z.string().max(32)).max(255).default([]),
|
||||
blacklist_enabled: z.boolean().default(false),
|
||||
filetype_whitelist: z.array(z.string().max(32)).max(255).default([]),
|
||||
whitelist_enabled: z.boolean().default(false),
|
||||
}).transform((parsed, ctx) => {
|
||||
if (parsed.blacklist_enabled && parsed.whitelist_enabled) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Cannot have both blacklist and whitelist enabled",
|
||||
});
|
||||
return z.NEVER;
|
||||
}
|
||||
if (! parsed.blacklist_enabled && ! parsed.whitelist_enabled) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Must have either blacklist or whitelist enabled",
|
||||
});
|
||||
return z.NEVER;
|
||||
}
|
||||
return parsed;
|
||||
});
|
||||
|
||||
defaultConfig: {
|
||||
filetype_blacklist: [],
|
||||
blacklist_enabled: false,
|
||||
filetype_whitelist: [],
|
||||
whitelist_enabled: false,
|
||||
},
|
||||
export const MatchAttachmentTypeTrigger = automodTrigger<MatchResultType>()({
|
||||
configSchema,
|
||||
|
||||
async match({ context, triggerConfig: trigger }) {
|
||||
if (!context.message) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as t from "io-ts";
|
||||
import { getInviteCodesInString, GuildInvite, isGuildInvite, resolveInvite, tNullable } from "../../../utils";
|
||||
import z from "zod";
|
||||
import { getInviteCodesInString, GuildInvite, isGuildInvite, resolveInvite, zSnowflake } from "../../../utils";
|
||||
import { getTextMatchPartialSummary } from "../functions/getTextMatchPartialSummary";
|
||||
import { MatchableTextType, matchMultipleTextTypesOnMessage } from "../functions/matchMultipleTextTypesOnMessage";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
@ -10,30 +10,22 @@ interface MatchResultType {
|
|||
invite?: GuildInvite;
|
||||
}
|
||||
|
||||
export const MatchInvitesTrigger = automodTrigger<MatchResultType>()({
|
||||
configType: t.type({
|
||||
include_guilds: tNullable(t.array(t.string)),
|
||||
exclude_guilds: tNullable(t.array(t.string)),
|
||||
include_invite_codes: tNullable(t.array(t.string)),
|
||||
exclude_invite_codes: tNullable(t.array(t.string)),
|
||||
allow_group_dm_invites: t.boolean,
|
||||
match_messages: t.boolean,
|
||||
match_embeds: t.boolean,
|
||||
match_visible_names: t.boolean,
|
||||
match_usernames: t.boolean,
|
||||
match_nicknames: t.boolean,
|
||||
match_custom_status: t.boolean,
|
||||
}),
|
||||
const configSchema = z.strictObject({
|
||||
include_guilds: z.array(zSnowflake).max(255).optional(),
|
||||
exclude_guilds: z.array(zSnowflake).max(255).optional(),
|
||||
include_invite_codes: z.array(z.string().max(32)).max(255).optional(),
|
||||
exclude_invite_codes: z.array(z.string().max(32)).max(255).optional(),
|
||||
allow_group_dm_invites: z.boolean().default(false),
|
||||
match_messages: z.boolean().default(true),
|
||||
match_embeds: z.boolean().default(false),
|
||||
match_visible_names: z.boolean().default(false),
|
||||
match_usernames: z.boolean().default(false),
|
||||
match_nicknames: z.boolean().default(false),
|
||||
match_custom_status: z.boolean().default(false),
|
||||
});
|
||||
|
||||
defaultConfig: {
|
||||
allow_group_dm_invites: false,
|
||||
match_messages: true,
|
||||
match_embeds: false,
|
||||
match_visible_names: false,
|
||||
match_usernames: false,
|
||||
match_nicknames: false,
|
||||
match_custom_status: false,
|
||||
},
|
||||
export const MatchInvitesTrigger = automodTrigger<MatchResultType>()({
|
||||
configSchema,
|
||||
|
||||
async match({ pluginData, context, triggerConfig: trigger }) {
|
||||
if (!context.message) {
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { escapeInlineCode } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { allowTimeout } from "../../../RegExpRunner";
|
||||
import { phishermanDomainIsSafe } from "../../../data/Phisherman";
|
||||
import { getUrlsInString, tNullable } from "../../../utils";
|
||||
import { getUrlsInString, zRegex } from "../../../utils";
|
||||
import { mergeRegexes } from "../../../utils/mergeRegexes";
|
||||
import { mergeWordsIntoRegex } from "../../../utils/mergeWordsIntoRegex";
|
||||
import { TRegex } from "../../../validatorUtils";
|
||||
import { PhishermanPlugin } from "../../Phisherman/PhishermanPlugin";
|
||||
import { getTextMatchPartialSummary } from "../functions/getTextMatchPartialSummary";
|
||||
import { MatchableTextType, matchMultipleTextTypesOnMessage } from "../functions/matchMultipleTextTypesOnMessage";
|
||||
|
@ -21,40 +20,29 @@ const regexCache = new WeakMap<any, RegExp[]>();
|
|||
|
||||
const quickLinkCheck = /^https?:\/\//i;
|
||||
|
||||
export const MatchLinksTrigger = automodTrigger<MatchResultType>()({
|
||||
configType: t.type({
|
||||
include_domains: tNullable(t.array(t.string)),
|
||||
exclude_domains: tNullable(t.array(t.string)),
|
||||
include_subdomains: t.boolean,
|
||||
include_words: tNullable(t.array(t.string)),
|
||||
exclude_words: tNullable(t.array(t.string)),
|
||||
include_regex: tNullable(t.array(TRegex)),
|
||||
exclude_regex: tNullable(t.array(TRegex)),
|
||||
phisherman: tNullable(
|
||||
t.type({
|
||||
include_suspected: tNullable(t.boolean),
|
||||
include_verified: tNullable(t.boolean),
|
||||
}),
|
||||
),
|
||||
only_real_links: t.boolean,
|
||||
match_messages: t.boolean,
|
||||
match_embeds: t.boolean,
|
||||
match_visible_names: t.boolean,
|
||||
match_usernames: t.boolean,
|
||||
match_nicknames: t.boolean,
|
||||
match_custom_status: t.boolean,
|
||||
}),
|
||||
const configSchema = z.strictObject({
|
||||
include_domains: z.array(z.string().max(255)).max(255).optional(),
|
||||
exclude_domains: z.array(z.string().max(255)).max(255).optional(),
|
||||
include_subdomains: z.boolean().default(true),
|
||||
include_words: z.array(z.string().max(2000)).max(512).optional(),
|
||||
exclude_words: z.array(z.string().max(2000)).max(512).optional(),
|
||||
include_regex: z.array(zRegex(z.string().max(2000))).max(512).optional(),
|
||||
exclude_regex: z.array(zRegex(z.string().max(2000))).max(512).optional(),
|
||||
phisherman: z.strictObject({
|
||||
include_suspected: z.boolean().optional(),
|
||||
include_verified: z.boolean().optional(),
|
||||
}).optional(),
|
||||
only_real_links: z.boolean(),
|
||||
match_messages: z.boolean().default(true),
|
||||
match_embeds: z.boolean().default(true),
|
||||
match_visible_names: z.boolean().default(false),
|
||||
match_usernames: z.boolean().default(false),
|
||||
match_nicknames: z.boolean().default(false),
|
||||
match_custom_status: z.boolean().default(false),
|
||||
});
|
||||
|
||||
defaultConfig: {
|
||||
include_subdomains: true,
|
||||
match_messages: true,
|
||||
match_embeds: false,
|
||||
match_visible_names: false,
|
||||
match_usernames: false,
|
||||
match_nicknames: false,
|
||||
match_custom_status: false,
|
||||
only_real_links: true,
|
||||
},
|
||||
export const MatchLinksTrigger = automodTrigger<MatchResultType>()({
|
||||
configSchema,
|
||||
|
||||
async match({ pluginData, context, triggerConfig: trigger }) {
|
||||
if (!context.message) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { escapeInlineCode } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { asSingleLine, messageSummary, verboseChannelMention } from "../../../utils";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
|
@ -8,20 +8,31 @@ interface MatchResultType {
|
|||
mode: "blacklist" | "whitelist";
|
||||
}
|
||||
|
||||
export const MatchMimeTypeTrigger = automodTrigger<MatchResultType>()({
|
||||
configType: t.type({
|
||||
mime_type_blacklist: t.array(t.string),
|
||||
blacklist_enabled: t.boolean,
|
||||
mime_type_whitelist: t.array(t.string),
|
||||
whitelist_enabled: t.boolean,
|
||||
}),
|
||||
const configSchema = z.strictObject({
|
||||
mime_type_blacklist: z.array(z.string().max(255)).max(255).default([]),
|
||||
blacklist_enabled: z.boolean().default(false),
|
||||
mime_type_whitelist: z.array(z.string().max(255)).max(255).default([]),
|
||||
whitelist_enabled: z.boolean().default(false),
|
||||
}).transform((parsed, ctx) => {
|
||||
if (parsed.blacklist_enabled && parsed.whitelist_enabled) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Cannot have both blacklist and whitelist enabled",
|
||||
});
|
||||
return z.NEVER;
|
||||
}
|
||||
if (! parsed.blacklist_enabled && ! parsed.whitelist_enabled) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Must have either blacklist or whitelist enabled",
|
||||
});
|
||||
return z.NEVER;
|
||||
}
|
||||
return parsed;
|
||||
});
|
||||
|
||||
defaultConfig: {
|
||||
mime_type_blacklist: [],
|
||||
blacklist_enabled: false,
|
||||
mime_type_whitelist: [],
|
||||
whitelist_enabled: false,
|
||||
},
|
||||
export const MatchMimeTypeTrigger = automodTrigger<MatchResultType>()({
|
||||
configSchema,
|
||||
|
||||
async match({ context, triggerConfig: trigger }) {
|
||||
if (!context.message) return;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { allowTimeout } from "../../../RegExpRunner";
|
||||
import { zRegex } from "../../../utils";
|
||||
import { mergeRegexes } from "../../../utils/mergeRegexes";
|
||||
import { normalizeText } from "../../../utils/normalizeText";
|
||||
import { stripMarkdown } from "../../../utils/stripMarkdown";
|
||||
import { TRegex } from "../../../validatorUtils";
|
||||
import { getTextMatchPartialSummary } from "../functions/getTextMatchPartialSummary";
|
||||
import { MatchableTextType, matchMultipleTextTypesOnMessage } from "../functions/matchMultipleTextTypesOnMessage";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
@ -13,33 +13,23 @@ interface MatchResultType {
|
|||
type: MatchableTextType;
|
||||
}
|
||||
|
||||
const configSchema = z.strictObject({
|
||||
patterns: z.array(zRegex(z.string().max(2000))).max(512),
|
||||
case_sensitive: z.boolean().default(false),
|
||||
normalize: z.boolean().default(false),
|
||||
strip_markdown: z.boolean().default(false),
|
||||
match_messages: z.boolean().default(true),
|
||||
match_embeds: z.boolean().default(false),
|
||||
match_visible_names: z.boolean().default(false),
|
||||
match_usernames: z.boolean().default(false),
|
||||
match_nicknames: z.boolean().default(false),
|
||||
match_custom_status: z.boolean().default(false),
|
||||
});
|
||||
|
||||
const regexCache = new WeakMap<any, RegExp[]>();
|
||||
|
||||
export const MatchRegexTrigger = automodTrigger<MatchResultType>()({
|
||||
configType: t.type({
|
||||
patterns: t.array(TRegex),
|
||||
case_sensitive: t.boolean,
|
||||
normalize: t.boolean,
|
||||
strip_markdown: t.boolean,
|
||||
match_messages: t.boolean,
|
||||
match_embeds: t.boolean,
|
||||
match_visible_names: t.boolean,
|
||||
match_usernames: t.boolean,
|
||||
match_nicknames: t.boolean,
|
||||
match_custom_status: t.boolean,
|
||||
}),
|
||||
|
||||
defaultConfig: {
|
||||
case_sensitive: false,
|
||||
normalize: false,
|
||||
strip_markdown: false,
|
||||
match_messages: true,
|
||||
match_embeds: false,
|
||||
match_visible_names: false,
|
||||
match_usernames: false,
|
||||
match_nicknames: false,
|
||||
match_custom_status: false,
|
||||
},
|
||||
configSchema,
|
||||
|
||||
async match({ pluginData, context, triggerConfig: trigger }) {
|
||||
if (!context.message) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import escapeStringRegexp from "escape-string-regexp";
|
||||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { normalizeText } from "../../../utils/normalizeText";
|
||||
import { stripMarkdown } from "../../../utils/stripMarkdown";
|
||||
import { getTextMatchPartialSummary } from "../functions/getTextMatchPartialSummary";
|
||||
|
@ -13,37 +13,24 @@ interface MatchResultType {
|
|||
|
||||
const regexCache = new WeakMap<any, RegExp[]>();
|
||||
|
||||
export const MatchWordsTrigger = automodTrigger<MatchResultType>()({
|
||||
configType: t.type({
|
||||
words: t.array(t.string),
|
||||
case_sensitive: t.boolean,
|
||||
only_full_words: t.boolean,
|
||||
normalize: t.boolean,
|
||||
loose_matching: t.boolean,
|
||||
loose_matching_threshold: t.number,
|
||||
strip_markdown: t.boolean,
|
||||
match_messages: t.boolean,
|
||||
match_embeds: t.boolean,
|
||||
match_visible_names: t.boolean,
|
||||
match_usernames: t.boolean,
|
||||
match_nicknames: t.boolean,
|
||||
match_custom_status: t.boolean,
|
||||
}),
|
||||
const configSchema = z.strictObject({
|
||||
words: z.array(z.string().max(2000)).max(512),
|
||||
case_sensitive: z.boolean().default(false),
|
||||
only_full_words: z.boolean().default(true),
|
||||
normalize: z.boolean().default(false),
|
||||
loose_matching: z.boolean().default(false),
|
||||
loose_matching_threshold: z.number().int().default(4),
|
||||
strip_markdown: z.boolean().default(false),
|
||||
match_messages: z.boolean().default(true),
|
||||
match_embeds: z.boolean().default(false),
|
||||
match_visible_names: z.boolean().default(false),
|
||||
match_usernames: z.boolean().default(false),
|
||||
match_nicknames: z.boolean().default(false),
|
||||
match_custom_status: z.boolean().default(false),
|
||||
});
|
||||
|
||||
defaultConfig: {
|
||||
case_sensitive: false,
|
||||
only_full_words: true,
|
||||
normalize: false,
|
||||
loose_matching: false,
|
||||
loose_matching_threshold: 4,
|
||||
strip_markdown: false,
|
||||
match_messages: true,
|
||||
match_embeds: false,
|
||||
match_visible_names: false,
|
||||
match_usernames: false,
|
||||
match_nicknames: false,
|
||||
match_custom_status: false,
|
||||
},
|
||||
export const MatchWordsTrigger = automodTrigger<MatchResultType>()({
|
||||
configSchema,
|
||||
|
||||
async match({ pluginData, context, triggerConfig: trigger }) {
|
||||
if (!context.message) {
|
||||
|
|
|
@ -1,17 +1,14 @@
|
|||
import * as t from "io-ts";
|
||||
import { convertDelayStringToMS, tDelayString } from "../../../utils";
|
||||
import z from "zod";
|
||||
import { convertDelayStringToMS, zDelayString } from "../../../utils";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
export const MemberJoinTrigger = automodTrigger<unknown>()({
|
||||
configType: t.type({
|
||||
only_new: t.boolean,
|
||||
new_threshold: tDelayString,
|
||||
}),
|
||||
const configSchema = z.strictObject({
|
||||
only_new: z.boolean().default(false),
|
||||
new_threshold: zDelayString.default("1h"),
|
||||
});
|
||||
|
||||
defaultConfig: {
|
||||
only_new: false,
|
||||
new_threshold: "1h",
|
||||
},
|
||||
export const MemberJoinTrigger = automodTrigger<unknown>()({
|
||||
configSchema,
|
||||
|
||||
async match({ context, triggerConfig }) {
|
||||
if (!context.joined || !context.member) {
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
import * as t from "io-ts";
|
||||
import { convertDelayStringToMS, tDelayString } from "../../../utils";
|
||||
import z from "zod";
|
||||
import { convertDelayStringToMS, zDelayString } from "../../../utils";
|
||||
import { RecentActionType } from "../constants";
|
||||
import { findRecentSpam } from "../functions/findRecentSpam";
|
||||
import { getMatchingRecentActions } from "../functions/getMatchingRecentActions";
|
||||
import { sumRecentActionCounts } from "../functions/sumRecentActionCounts";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
export const MemberJoinSpamTrigger = automodTrigger<unknown>()({
|
||||
configType: t.type({
|
||||
amount: t.number,
|
||||
within: tDelayString,
|
||||
}),
|
||||
const configSchema = z.strictObject({
|
||||
amount: z.number().int(),
|
||||
within: zDelayString,
|
||||
});
|
||||
|
||||
defaultConfig: {},
|
||||
export const MemberJoinSpamTrigger = automodTrigger<unknown>()({
|
||||
configSchema,
|
||||
|
||||
async match({ pluginData, context, triggerConfig }) {
|
||||
if (!context.joined || !context.member) {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
export const MemberLeaveTrigger = automodTrigger<unknown>()({
|
||||
configType: t.type({}),
|
||||
const configSchema = z.strictObject({});
|
||||
|
||||
defaultConfig: {},
|
||||
export const MemberLeaveTrigger = automodTrigger<unknown>()({
|
||||
configSchema,
|
||||
|
||||
async match({ context }) {
|
||||
if (!context.joined || !context.member) {
|
||||
|
|
|
@ -1,19 +1,16 @@
|
|||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
// tslint:disable-next-line:no-empty-interface
|
||||
interface MuteTriggerResultType {}
|
||||
|
||||
export const MuteTrigger = automodTrigger<MuteTriggerResultType>()({
|
||||
configType: t.type({
|
||||
manual: t.boolean,
|
||||
automatic: t.boolean,
|
||||
}),
|
||||
const configSchema = z.strictObject({
|
||||
manual: z.boolean().default(true),
|
||||
automatic: z.boolean().default(true),
|
||||
});
|
||||
|
||||
defaultConfig: {
|
||||
manual: true,
|
||||
automatic: true,
|
||||
},
|
||||
export const MuteTrigger = automodTrigger<MuteTriggerResultType>()({
|
||||
configSchema,
|
||||
|
||||
async match({ context, triggerConfig }) {
|
||||
if (context.modAction?.type !== "mute") {
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
// tslint:disable-next-line:no-empty-interface
|
||||
interface NoteTriggerResultType {}
|
||||
|
||||
const configSchema = z.strictObject({});
|
||||
|
||||
export const NoteTrigger = automodTrigger<NoteTriggerResultType>()({
|
||||
configType: t.type({}),
|
||||
defaultConfig: {},
|
||||
configSchema,
|
||||
|
||||
async match({ context }) {
|
||||
if (context.modAction?.type !== "note") {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Snowflake } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import { renderUserUsername } from "../../../utils";
|
||||
import z from "zod";
|
||||
import { renderUserUsername, zSnowflake } from "../../../utils";
|
||||
import { consumeIgnoredRoleChange } from "../functions/ignoredRoleChanges";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
|
@ -8,10 +8,13 @@ interface RoleAddedMatchResult {
|
|||
matchedRoleId: string;
|
||||
}
|
||||
|
||||
export const RoleAddedTrigger = automodTrigger<RoleAddedMatchResult>()({
|
||||
configType: t.union([t.string, t.array(t.string)]),
|
||||
const configSchema = z.union([
|
||||
zSnowflake,
|
||||
z.array(zSnowflake).max(255),
|
||||
]).default([]);
|
||||
|
||||
defaultConfig: "",
|
||||
export const RoleAddedTrigger = automodTrigger<RoleAddedMatchResult>()({
|
||||
configSchema,
|
||||
|
||||
async match({ triggerConfig, context, pluginData }) {
|
||||
if (!context.member || !context.rolesChanged || context.rolesChanged.added!.length === 0) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Snowflake } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import { renderUserUsername } from "../../../utils";
|
||||
import z from "zod";
|
||||
import { renderUserUsername, zSnowflake } from "../../../utils";
|
||||
import { consumeIgnoredRoleChange } from "../functions/ignoredRoleChanges";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
|
@ -8,10 +8,13 @@ interface RoleAddedMatchResult {
|
|||
matchedRoleId: string;
|
||||
}
|
||||
|
||||
export const RoleRemovedTrigger = automodTrigger<RoleAddedMatchResult>()({
|
||||
configType: t.union([t.string, t.array(t.string)]),
|
||||
const configSchema = z.union([
|
||||
zSnowflake,
|
||||
z.array(zSnowflake).max(255),
|
||||
]).default([]);
|
||||
|
||||
defaultConfig: "",
|
||||
export const RoleRemovedTrigger = automodTrigger<RoleAddedMatchResult>()({
|
||||
configSchema,
|
||||
|
||||
async match({ triggerConfig, context, pluginData }) {
|
||||
if (!context.member || !context.rolesChanged || context.rolesChanged.removed!.length === 0) {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { User, escapeBold, type Snowflake } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import { tNullable } from "../../../utils";
|
||||
import z from "zod";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
interface ThreadArchiveResult {
|
||||
|
@ -11,12 +10,12 @@ interface ThreadArchiveResult {
|
|||
matchedThreadOwner: User | undefined;
|
||||
}
|
||||
|
||||
export const ThreadArchiveTrigger = automodTrigger<ThreadArchiveResult>()({
|
||||
configType: t.type({
|
||||
locked: tNullable(t.boolean),
|
||||
}),
|
||||
const configSchema = z.strictObject({
|
||||
locked: z.boolean().optional(),
|
||||
});
|
||||
|
||||
defaultConfig: {},
|
||||
export const ThreadArchiveTrigger = automodTrigger<ThreadArchiveResult>()({
|
||||
configSchema,
|
||||
|
||||
async match({ context, triggerConfig }) {
|
||||
if (!context.threadChange?.archived) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { User, escapeBold, type Snowflake } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
interface ThreadCreateResult {
|
||||
|
@ -10,9 +10,10 @@ interface ThreadCreateResult {
|
|||
matchedThreadOwner: User | undefined;
|
||||
}
|
||||
|
||||
const configSchema = z.strictObject({});
|
||||
|
||||
export const ThreadCreateTrigger = automodTrigger<ThreadCreateResult>()({
|
||||
configType: t.type({}),
|
||||
defaultConfig: {},
|
||||
configSchema,
|
||||
|
||||
async match({ context }) {
|
||||
if (!context.threadChange?.created) {
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
import * as t from "io-ts";
|
||||
import { convertDelayStringToMS, tDelayString } from "../../../utils";
|
||||
import z from "zod";
|
||||
import { convertDelayStringToMS, zDelayString } from "../../../utils";
|
||||
import { RecentActionType } from "../constants";
|
||||
import { findRecentSpam } from "../functions/findRecentSpam";
|
||||
import { getMatchingRecentActions } from "../functions/getMatchingRecentActions";
|
||||
import { sumRecentActionCounts } from "../functions/sumRecentActionCounts";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
export const ThreadCreateSpamTrigger = automodTrigger<unknown>()({
|
||||
configType: t.type({
|
||||
amount: t.number,
|
||||
within: tDelayString,
|
||||
}),
|
||||
const configSchema = z.strictObject({
|
||||
amount: z.number().int(),
|
||||
within: zDelayString,
|
||||
});
|
||||
|
||||
defaultConfig: {},
|
||||
export const ThreadCreateSpamTrigger = automodTrigger<unknown>()({
|
||||
configSchema,
|
||||
|
||||
async match({ pluginData, context, triggerConfig }) {
|
||||
if (!context.threadChange?.created) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { User, escapeBold, type Snowflake } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
interface ThreadDeleteResult {
|
||||
|
@ -10,9 +10,10 @@ interface ThreadDeleteResult {
|
|||
matchedThreadOwner: User | undefined;
|
||||
}
|
||||
|
||||
const configSchema = z.strictObject({});
|
||||
|
||||
export const ThreadDeleteTrigger = automodTrigger<ThreadDeleteResult>()({
|
||||
configType: t.type({}),
|
||||
defaultConfig: {},
|
||||
configSchema,
|
||||
|
||||
async match({ context }) {
|
||||
if (!context.threadChange?.deleted) {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { User, escapeBold, type Snowflake } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import { tNullable } from "../../../utils";
|
||||
import z from "zod";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
interface ThreadUnarchiveResult {
|
||||
|
@ -11,12 +10,12 @@ interface ThreadUnarchiveResult {
|
|||
matchedThreadOwner: User | undefined;
|
||||
}
|
||||
|
||||
export const ThreadUnarchiveTrigger = automodTrigger<ThreadUnarchiveResult>()({
|
||||
configType: t.type({
|
||||
locked: tNullable(t.boolean),
|
||||
}),
|
||||
const configSchema = z.strictObject({
|
||||
locked: z.boolean().optional(),
|
||||
});
|
||||
|
||||
defaultConfig: {},
|
||||
export const ThreadUnarchiveTrigger = automodTrigger<ThreadUnarchiveResult>()({
|
||||
configSchema,
|
||||
|
||||
async match({ context, triggerConfig }) {
|
||||
if (!context.threadChange?.unarchived) {
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
// tslint:disable-next-line:no-empty-interface
|
||||
interface UnbanTriggerResultType {}
|
||||
|
||||
const configSchema = z.strictObject({});
|
||||
|
||||
export const UnbanTrigger = automodTrigger<UnbanTriggerResultType>()({
|
||||
configType: t.type({}),
|
||||
defaultConfig: {},
|
||||
configSchema,
|
||||
|
||||
async match({ context }) {
|
||||
if (context.modAction?.type !== "unban") {
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
// tslint:disable-next-line:no-empty-interface
|
||||
interface UnmuteTriggerResultType {}
|
||||
|
||||
const configSchema = z.strictObject({});
|
||||
|
||||
export const UnmuteTrigger = automodTrigger<UnmuteTriggerResultType>()({
|
||||
configType: t.type({}),
|
||||
defaultConfig: {},
|
||||
configSchema,
|
||||
|
||||
async match({ context }) {
|
||||
if (context.modAction?.type !== "unmute") {
|
||||
|
|
|
@ -1,19 +1,16 @@
|
|||
import * as t from "io-ts";
|
||||
import z from "zod";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
// tslint:disable-next-line:no-empty-interface
|
||||
interface WarnTriggerResultType {}
|
||||
|
||||
export const WarnTrigger = automodTrigger<WarnTriggerResultType>()({
|
||||
configType: t.type({
|
||||
manual: t.boolean,
|
||||
automatic: t.boolean,
|
||||
}),
|
||||
const configSchema = z.strictObject({
|
||||
manual: z.boolean().default(true),
|
||||
automatic: z.boolean().default(true),
|
||||
});
|
||||
|
||||
defaultConfig: {
|
||||
manual: true,
|
||||
automatic: true,
|
||||
},
|
||||
export const WarnTrigger = automodTrigger<WarnTriggerResultType>()({
|
||||
configSchema,
|
||||
|
||||
async match({ context, triggerConfig }) {
|
||||
if (context.modAction?.type !== "warn") {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { GuildMember, GuildTextBasedChannel, PartialGuildMember, ThreadChannel, User } from "discord.js";
|
||||
import * as t from "io-ts";
|
||||
import { BasePluginType, CooldownManager } from "knub";
|
||||
import z from "zod";
|
||||
import { Queue } from "../../Queue";
|
||||
import { RegExpRunner } from "../../RegExpRunner";
|
||||
import { GuildAntiraidLevels } from "../../data/GuildAntiraidLevels";
|
||||
|
@ -8,39 +8,81 @@ import { GuildArchives } from "../../data/GuildArchives";
|
|||
import { GuildLogs } from "../../data/GuildLogs";
|
||||
import { GuildSavedMessages } from "../../data/GuildSavedMessages";
|
||||
import { SavedMessage } from "../../data/entities/SavedMessage";
|
||||
import { tNullable } from "../../utils";
|
||||
import { entries, zBoundedRecord, zDelayString } from "../../utils";
|
||||
import { CounterEvents } from "../Counters/types";
|
||||
import { ModActionType, ModActionsEvents } from "../ModActions/types";
|
||||
import { MutesEvents } from "../Mutes/types";
|
||||
import { AvailableActions } from "./actions/availableActions";
|
||||
import { availableActions } from "./actions/availableActions";
|
||||
import { RecentActionType } from "./constants";
|
||||
import { AvailableTriggers } from "./triggers/availableTriggers";
|
||||
import { availableTriggers } from "./triggers/availableTriggers";
|
||||
|
||||
import Timeout = NodeJS.Timeout;
|
||||
|
||||
export const Rule = t.type({
|
||||
enabled: t.boolean,
|
||||
name: t.string,
|
||||
presets: tNullable(t.array(t.string)),
|
||||
affects_bots: t.boolean,
|
||||
affects_self: t.boolean,
|
||||
triggers: t.array(t.partial(AvailableTriggers.props)),
|
||||
actions: t.partial(AvailableActions.props),
|
||||
cooldown: tNullable(t.string),
|
||||
allow_further_rules: t.boolean,
|
||||
});
|
||||
export type TRule = t.TypeOf<typeof Rule>;
|
||||
export type ZTriggersMapHelper = {
|
||||
[TriggerName in keyof typeof availableTriggers]: typeof availableTriggers[TriggerName]["configSchema"];
|
||||
};
|
||||
const zTriggersMap = z.strictObject(entries(availableTriggers).reduce((map, [triggerName, trigger]) => {
|
||||
map[triggerName] = trigger.configSchema;
|
||||
return map;
|
||||
}, {} as ZTriggersMapHelper)).partial();
|
||||
|
||||
export const ConfigSchema = t.type({
|
||||
rules: t.record(t.string, Rule),
|
||||
antiraid_levels: t.array(t.string),
|
||||
can_set_antiraid: t.boolean,
|
||||
can_view_antiraid: t.boolean,
|
||||
type ZActionsMapHelper = {
|
||||
[ActionName in keyof typeof availableActions]: typeof availableActions[ActionName]["configSchema"];
|
||||
};
|
||||
const zActionsMap = z.strictObject(entries(availableActions).reduce((map, [actionName, action]) => {
|
||||
// @ts-expect-error TS can't infer this properly but it works fine thanks to our helper
|
||||
map[actionName] = action.configSchema;
|
||||
return map;
|
||||
}, {} as ZActionsMapHelper)).partial();
|
||||
|
||||
const zRule = z.strictObject({
|
||||
enabled: z.boolean().default(true),
|
||||
// Typed as "never" because you are not expected to supply this directly.
|
||||
// The transform instead picks it up from the property key and the output type is a string.
|
||||
name: z.never().optional().transform((_, ctx) => {
|
||||
const ruleName = String(ctx.path[ctx.path.length - 2]).trim();
|
||||
if (! ruleName) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Automod rules must have names",
|
||||
});
|
||||
return z.NEVER;
|
||||
}
|
||||
return ruleName;
|
||||
}),
|
||||
presets: z.array(z.string().max(100)).max(25).default([]),
|
||||
affects_bots: z.boolean().default(false),
|
||||
affects_self: z.boolean().default(false),
|
||||
cooldown: zDelayString.nullable().default(null),
|
||||
allow_further_rules: z.boolean().default(false),
|
||||
triggers: z.array(zTriggersMap),
|
||||
actions: zActionsMap.refine(
|
||||
(v) => ! (v.clean && v.start_thread),
|
||||
{
|
||||
message: "Cannot have both clean and start_thread active at the same time",
|
||||
}
|
||||
),
|
||||
});
|
||||
export type TRule = z.infer<typeof zRule>;
|
||||
|
||||
export const zNotify = z.union([
|
||||
z.literal("dm"),
|
||||
z.literal("channel"),
|
||||
]);
|
||||
|
||||
export const zAutomodConfig = z.strictObject({
|
||||
rules: zBoundedRecord(
|
||||
z.record(z.string().max(100), zRule),
|
||||
0,
|
||||
100,
|
||||
),
|
||||
antiraid_levels: z.array(z.string().max(100)).max(10),
|
||||
can_set_antiraid: z.boolean(),
|
||||
can_view_antiraid: z.boolean(),
|
||||
});
|
||||
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
|
||||
|
||||
export interface AutomodPluginType extends BasePluginType {
|
||||
config: TConfigSchema;
|
||||
config: z.output<typeof zAutomodConfig>;
|
||||
|
||||
customOverrideCriteria: {
|
||||
antiraid_level?: string;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue