3
0
Fork 0
mirror of https://github.com/ZeppelinBot/Zeppelin.git synced 2025-03-15 05:41:51 +00:00

refactor: replace io-ts with zod

This commit is contained in:
Dragory 2024-01-14 14:25:42 +00:00
parent fafaefa1fb
commit 28692962bc
No known key found for this signature in database
161 changed files with 1450 additions and 2105 deletions

View file

@ -22,7 +22,6 @@
"express": "^4.17.0", "express": "^4.17.0",
"fp-ts": "^2.0.1", "fp-ts": "^2.0.1",
"humanize-duration": "^3.15.0", "humanize-duration": "^3.15.0",
"io-ts": "^2.0.0",
"js-yaml": "^3.13.1", "js-yaml": "^3.13.1",
"knub": "^32.0.0-next.16", "knub": "^32.0.0-next.16",
"knub-command-manager": "^9.1.0", "knub-command-manager": "^9.1.0",
@ -4975,14 +4974,6 @@
"resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz",
"integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="
}, },
"node_modules/io-ts": {
"version": "2.2.20",
"resolved": "https://registry.npmjs.org/io-ts/-/io-ts-2.2.20.tgz",
"integrity": "sha512-Rq2BsYmtwS5vVttie4rqrOCIfHCS9TgpRLFpKQCM1wZBBRY9nWVGmEvm2FnDbSE2un1UE39DvFpTR5UL47YDcA==",
"peerDependencies": {
"fp-ts": "^2.5.0"
}
},
"node_modules/iota-array": { "node_modules/iota-array": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/iota-array/-/iota-array-1.0.0.tgz", "resolved": "https://registry.npmjs.org/iota-array/-/iota-array-1.0.0.tgz",

View file

@ -43,7 +43,6 @@
"express": "^4.17.0", "express": "^4.17.0",
"fp-ts": "^2.0.1", "fp-ts": "^2.0.1",
"humanize-duration": "^3.15.0", "humanize-duration": "^3.15.0",
"io-ts": "^2.0.0",
"js-yaml": "^3.13.1", "js-yaml": "^3.13.1",
"knub": "^32.0.0-next.16", "knub": "^32.0.0-next.16",
"knub-command-manager": "^9.1.0", "knub-command-manager": "^9.1.0",

View file

@ -17,6 +17,7 @@ import { createTypeHelper } from "knub-command-manager";
import { import {
channelMentionRegex, channelMentionRegex,
convertDelayStringToMS, convertDelayStringToMS,
inputPatternToRegExp,
isValidSnowflake, isValidSnowflake,
resolveMember, resolveMember,
resolveUser, resolveUser,
@ -26,7 +27,6 @@ import {
} from "./utils"; } from "./utils";
import { isValidTimezone } from "./utils/isValidTimezone"; import { isValidTimezone } from "./utils/isValidTimezone";
import { MessageTarget, resolveMessageTarget } from "./utils/resolveMessageTarget"; import { MessageTarget, resolveMessageTarget } from "./utils/resolveMessageTarget";
import { inputPatternToRegExp } from "./validatorUtils";
export const commandTypes = { export const commandTypes = {
...messageCommandBaseTypeConverters, ...messageCommandBaseTypeConverters,

View file

@ -1,9 +1,9 @@
import { ConfigValidationError, PluginConfigManager } from "knub"; import { PluginConfigManager } from "knub";
import moment from "moment-timezone"; import moment from "moment-timezone";
import { ZodError } from "zod";
import { ZeppelinPlugin } from "./plugins/ZeppelinPlugin"; import { ZeppelinPlugin } from "./plugins/ZeppelinPlugin";
import { guildPlugins } from "./plugins/availablePlugins"; import { guildPlugins } from "./plugins/availablePlugins";
import { PartialZeppelinGuildConfigSchema, ZeppelinGuildConfig } from "./types"; import { ZeppelinGuildConfig, zZeppelinGuildConfig } from "./types";
import { StrictValidationError, decodeAndValidateStrict } from "./validatorUtils";
const pluginNameToPlugin = new Map<string, ZeppelinPlugin>(); const pluginNameToPlugin = new Map<string, ZeppelinPlugin>();
for (const plugin of guildPlugins) { for (const plugin of guildPlugins) {
@ -11,8 +11,10 @@ for (const plugin of guildPlugins) {
} }
export async function validateGuildConfig(config: any): Promise<string | null> { export async function validateGuildConfig(config: any): Promise<string | null> {
const validationResult = decodeAndValidateStrict(PartialZeppelinGuildConfigSchema, config); const validationResult = zZeppelinGuildConfig.safeParse(config);
if (validationResult instanceof StrictValidationError) return validationResult.getErrors(); if (!validationResult.success) {
return validationResult.error.issues.join("\n");
}
const guildConfig = config as ZeppelinGuildConfig; const guildConfig = config as ZeppelinGuildConfig;
@ -41,8 +43,8 @@ export async function validateGuildConfig(config: any): Promise<string | null> {
try { try {
await configManager.init(); await configManager.init();
} catch (err) { } catch (err) {
if (err instanceof ConfigValidationError || err instanceof StrictValidationError) { if (err instanceof ZodError) {
return `${pluginName}: ${err.message}`; return `${pluginName}: ${err.issues.join("\n")}`;
} }
throw err; throw err;

View file

@ -10,22 +10,18 @@ import {
PermissionsBitField, PermissionsBitField,
TextBasedChannel, TextBasedChannel,
} from "discord.js"; } from "discord.js";
import * as t from "io-ts";
import { import {
AnyPluginData, AnyPluginData,
CommandContext, CommandContext,
ConfigValidationError,
ExtendedMatchParams, ExtendedMatchParams,
GuildPluginData, GuildPluginData,
PluginOverrideCriteria, helpers
helpers,
} from "knub"; } from "knub";
import { logger } from "./logger"; import { logger } from "./logger";
import { isStaff } from "./staff"; import { isStaff } from "./staff";
import { TZeppelinKnub } from "./types"; import { TZeppelinKnub } from "./types";
import { errorMessage, successMessage, tNullable } from "./utils"; import { errorMessage, successMessage } from "./utils";
import { Tail } from "./utils/typeUtils"; import { Tail } from "./utils/typeUtils";
import { StrictValidationError, parseIoTsSchema } from "./validatorUtils";
const { getMemberLevel } = helpers; const { getMemberLevel } = helpers;
@ -59,46 +55,6 @@ export async function hasPermission(
return helpers.hasPermission(config, permission); return helpers.hasPermission(config, permission);
} }
const PluginOverrideCriteriaType: t.Type<PluginOverrideCriteria<unknown>> = t.recursion(
"PluginOverrideCriteriaType",
() =>
t.partial({
channel: tNullable(t.union([t.string, t.array(t.string)])),
category: tNullable(t.union([t.string, t.array(t.string)])),
level: tNullable(t.union([t.string, t.array(t.string)])),
user: tNullable(t.union([t.string, t.array(t.string)])),
role: tNullable(t.union([t.string, t.array(t.string)])),
all: tNullable(t.array(PluginOverrideCriteriaType)),
any: tNullable(t.array(PluginOverrideCriteriaType)),
not: tNullable(PluginOverrideCriteriaType),
extra: t.unknown,
}),
);
export function strictValidationErrorToConfigValidationError(err: StrictValidationError) {
return new ConfigValidationError(
err
.getErrors()
.map((e) => e.toString())
.join("\n"),
);
}
export function makeIoTsConfigParser<Schema extends t.Type<any>>(schema: Schema): (input: unknown) => t.TypeOf<Schema> {
return (input: unknown) => {
try {
return parseIoTsSchema(schema, input);
} catch (err) {
if (err instanceof StrictValidationError) {
throw strictValidationErrorToConfigValidationError(err);
}
throw err;
}
};
}
export async function sendSuccessMessage( export async function sendSuccessMessage(
pluginData: AnyPluginData<any>, pluginData: AnyPluginData<any>,
channel: TextBasedChannel, channel: TextBasedChannel,

View file

@ -1,11 +1,10 @@
import { PluginOptions } from "knub"; import { PluginOptions } from "knub";
import { GuildLogs } from "../../data/GuildLogs"; import { GuildLogs } from "../../data/GuildLogs";
import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildSavedMessages } from "../../data/GuildSavedMessages";
import { makeIoTsConfigParser } from "../../pluginUtils";
import { LogsPlugin } from "../Logs/LogsPlugin"; import { LogsPlugin } from "../Logs/LogsPlugin";
import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin"; import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin";
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
import { AutoDeletePluginType, ConfigSchema } from "./types"; import { AutoDeletePluginType, zAutoDeleteConfig } from "./types";
import { onMessageCreate } from "./util/onMessageCreate"; import { onMessageCreate } from "./util/onMessageCreate";
import { onMessageDelete } from "./util/onMessageDelete"; import { onMessageDelete } from "./util/onMessageDelete";
import { onMessageDeleteBulk } from "./util/onMessageDeleteBulk"; import { onMessageDeleteBulk } from "./util/onMessageDeleteBulk";
@ -24,11 +23,11 @@ export const AutoDeletePlugin = zeppelinGuildPlugin<AutoDeletePluginType>()({
prettyName: "Auto-delete", prettyName: "Auto-delete",
description: "Allows Zeppelin to auto-delete messages from a channel after a delay", description: "Allows Zeppelin to auto-delete messages from a channel after a delay",
configurationGuide: "Maximum deletion delay is currently 5 minutes", configurationGuide: "Maximum deletion delay is currently 5 minutes",
configSchema: ConfigSchema, configSchema: zAutoDeleteConfig,
}, },
dependencies: () => [TimeAndDatePlugin, LogsPlugin], dependencies: () => [TimeAndDatePlugin, LogsPlugin],
configParser: makeIoTsConfigParser(ConfigSchema), configParser: (input) => zAutoDeleteConfig.parse(input),
defaultOptions, defaultOptions,
beforeLoad(pluginData) { beforeLoad(pluginData) {

View file

@ -1,10 +1,10 @@
import * as t from "io-ts";
import { BasePluginType } from "knub"; import { BasePluginType } from "knub";
import { GuildLogs } from "../../data/GuildLogs"; import { GuildLogs } from "../../data/GuildLogs";
import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildSavedMessages } from "../../data/GuildSavedMessages";
import { SavedMessage } from "../../data/entities/SavedMessage"; import { SavedMessage } from "../../data/entities/SavedMessage";
import { MINUTES, tDelayString } from "../../utils"; import { MINUTES, zDelayString } from "../../utils";
import Timeout = NodeJS.Timeout; import Timeout = NodeJS.Timeout;
import z from "zod";
export const MAX_DELAY = 5 * MINUTES; export const MAX_DELAY = 5 * MINUTES;
@ -13,14 +13,13 @@ export interface IDeletionQueueItem {
message: SavedMessage; message: SavedMessage;
} }
export const ConfigSchema = t.type({ export const zAutoDeleteConfig = z.strictObject({
enabled: t.boolean, enabled: z.boolean(),
delay: tDelayString, delay: zDelayString,
}); });
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
export interface AutoDeletePluginType extends BasePluginType { export interface AutoDeletePluginType extends BasePluginType {
config: TConfigSchema; config: z.output<typeof zAutoDeleteConfig>;
state: { state: {
guildSavedMessages: GuildSavedMessages; guildSavedMessages: GuildSavedMessages;
guildLogs: GuildLogs; guildLogs: GuildLogs;

View file

@ -1,14 +1,13 @@
import { PluginOptions } from "knub"; import { PluginOptions } from "knub";
import { GuildAutoReactions } from "../../data/GuildAutoReactions"; import { GuildAutoReactions } from "../../data/GuildAutoReactions";
import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildSavedMessages } from "../../data/GuildSavedMessages";
import { makeIoTsConfigParser } from "../../pluginUtils";
import { trimPluginDescription } from "../../utils"; import { trimPluginDescription } from "../../utils";
import { LogsPlugin } from "../Logs/LogsPlugin"; import { LogsPlugin } from "../Logs/LogsPlugin";
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
import { DisableAutoReactionsCmd } from "./commands/DisableAutoReactionsCmd"; import { DisableAutoReactionsCmd } from "./commands/DisableAutoReactionsCmd";
import { NewAutoReactionsCmd } from "./commands/NewAutoReactionsCmd"; import { NewAutoReactionsCmd } from "./commands/NewAutoReactionsCmd";
import { AddReactionsEvt } from "./events/AddReactionsEvt"; import { AddReactionsEvt } from "./events/AddReactionsEvt";
import { AutoReactionsPluginType, ConfigSchema } from "./types"; import { AutoReactionsPluginType, zAutoReactionsConfig } from "./types";
const defaultOptions: PluginOptions<AutoReactionsPluginType> = { const defaultOptions: PluginOptions<AutoReactionsPluginType> = {
config: { config: {
@ -32,7 +31,7 @@ export const AutoReactionsPlugin = zeppelinGuildPlugin<AutoReactionsPluginType>(
description: trimPluginDescription(` description: trimPluginDescription(`
Allows setting up automatic reactions to all new messages on a channel Allows setting up automatic reactions to all new messages on a channel
`), `),
configSchema: ConfigSchema, configSchema: zAutoReactionsConfig,
}, },
// prettier-ignore // prettier-ignore
@ -40,7 +39,7 @@ export const AutoReactionsPlugin = zeppelinGuildPlugin<AutoReactionsPluginType>(
LogsPlugin, LogsPlugin,
], ],
configParser: makeIoTsConfigParser(ConfigSchema), configParser: (input) => zAutoReactionsConfig.parse(input),
defaultOptions, defaultOptions,
// prettier-ignore // prettier-ignore

View file

@ -1,17 +1,16 @@
import * as t from "io-ts";
import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand } from "knub"; import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand } from "knub";
import z from "zod";
import { GuildAutoReactions } from "../../data/GuildAutoReactions"; import { GuildAutoReactions } from "../../data/GuildAutoReactions";
import { GuildLogs } from "../../data/GuildLogs"; import { GuildLogs } from "../../data/GuildLogs";
import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildSavedMessages } from "../../data/GuildSavedMessages";
import { AutoReaction } from "../../data/entities/AutoReaction"; import { AutoReaction } from "../../data/entities/AutoReaction";
export const ConfigSchema = t.type({ export const zAutoReactionsConfig = z.strictObject({
can_manage: t.boolean, can_manage: z.boolean(),
}); });
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
export interface AutoReactionsPluginType extends BasePluginType { export interface AutoReactionsPluginType extends BasePluginType {
config: TConfigSchema; config: z.output<typeof zAutoReactionsConfig>;
state: { state: {
logs: GuildLogs; logs: GuildLogs;
savedMessages: GuildSavedMessages; savedMessages: GuildSavedMessages;

View file

@ -1,4 +1,4 @@
import { configUtils, CooldownManager } from "knub"; import { CooldownManager } from "knub";
import { GuildAntiraidLevels } from "../../data/GuildAntiraidLevels"; import { GuildAntiraidLevels } from "../../data/GuildAntiraidLevels";
import { GuildArchives } from "../../data/GuildArchives"; import { GuildArchives } from "../../data/GuildArchives";
import { GuildLogs } from "../../data/GuildLogs"; import { GuildLogs } from "../../data/GuildLogs";
@ -8,7 +8,6 @@ import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners";
import { MINUTES, SECONDS } from "../../utils"; import { MINUTES, SECONDS } from "../../utils";
import { registerEventListenersFromMap } from "../../utils/registerEventListenersFromMap"; import { registerEventListenersFromMap } from "../../utils/registerEventListenersFromMap";
import { unregisterEventListenersFromMap } from "../../utils/unregisterEventListenersFromMap"; import { unregisterEventListenersFromMap } from "../../utils/unregisterEventListenersFromMap";
import { parseIoTsSchema, StrictValidationError } from "../../validatorUtils";
import { CountersPlugin } from "../Counters/CountersPlugin"; import { CountersPlugin } from "../Counters/CountersPlugin";
import { InternalPosterPlugin } from "../InternalPoster/InternalPosterPlugin"; import { InternalPosterPlugin } from "../InternalPoster/InternalPosterPlugin";
import { LogsPlugin } from "../Logs/LogsPlugin"; import { LogsPlugin } from "../Logs/LogsPlugin";
@ -17,7 +16,6 @@ import { MutesPlugin } from "../Mutes/MutesPlugin";
import { PhishermanPlugin } from "../Phisherman/PhishermanPlugin"; import { PhishermanPlugin } from "../Phisherman/PhishermanPlugin";
import { RoleManagerPlugin } from "../RoleManager/RoleManagerPlugin"; import { RoleManagerPlugin } from "../RoleManager/RoleManagerPlugin";
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
import { availableActions } from "./actions/availableActions";
import { AntiraidClearCmd } from "./commands/AntiraidClearCmd"; import { AntiraidClearCmd } from "./commands/AntiraidClearCmd";
import { SetAntiraidCmd } from "./commands/SetAntiraidCmd"; import { SetAntiraidCmd } from "./commands/SetAntiraidCmd";
import { ViewAntiraidCmd } from "./commands/ViewAntiraidCmd"; import { ViewAntiraidCmd } from "./commands/ViewAntiraidCmd";
@ -35,8 +33,7 @@ import { clearOldRecentNicknameChanges } from "./functions/clearOldNicknameChang
import { clearOldRecentActions } from "./functions/clearOldRecentActions"; import { clearOldRecentActions } from "./functions/clearOldRecentActions";
import { clearOldRecentSpam } from "./functions/clearOldRecentSpam"; import { clearOldRecentSpam } from "./functions/clearOldRecentSpam";
import { pluginInfo } from "./info"; import { pluginInfo } from "./info";
import { availableTriggers } from "./triggers/availableTriggers"; import { AutomodPluginType, zAutomodConfig } from "./types";
import { AutomodPluginType, ConfigSchema } from "./types";
const defaultOptions = { const defaultOptions = {
config: { 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>()({ export const AutomodPlugin = zeppelinGuildPlugin<AutomodPluginType>()({
name: "automod", name: "automod",
showInDocs: true, showInDocs: true,
@ -201,7 +75,7 @@ export const AutomodPlugin = zeppelinGuildPlugin<AutomodPluginType>()({
], ],
defaultOptions, defaultOptions,
configParser, configParser: (input) => zAutomodConfig.parse(input),
customOverrideCriteriaFunctions: { customOverrideCriteriaFunctions: {
antiraid_level: (pluginData, matchParams, value) => { antiraid_level: (pluginData, matchParams, value) => {

View file

@ -1,6 +1,6 @@
import { PermissionFlagsBits, Snowflake } from "discord.js"; import { PermissionFlagsBits, Snowflake } from "discord.js";
import * as t from "io-ts"; import z from "zod";
import { nonNullish, unique } from "../../../utils"; import { nonNullish, unique, zSnowflake } from "../../../utils";
import { canAssignRole } from "../../../utils/canAssignRole"; import { canAssignRole } from "../../../utils/canAssignRole";
import { getMissingPermissions } from "../../../utils/getMissingPermissions"; import { getMissingPermissions } from "../../../utils/getMissingPermissions";
import { missingPermissionError } from "../../../utils/missingPermissionError"; import { missingPermissionError } from "../../../utils/missingPermissionError";
@ -11,9 +11,10 @@ import { automodAction } from "../helpers";
const p = PermissionFlagsBits; const p = PermissionFlagsBits;
const configSchema = z.array(zSnowflake);
export const AddRolesAction = automodAction({ export const AddRolesAction = automodAction({
configType: t.array(t.string), configSchema,
defaultConfig: [],
async apply({ pluginData, contexts, actionConfig, ruleName }) { async apply({ pluginData, contexts, actionConfig, ruleName }) {
const members = unique(contexts.map((c) => c.member).filter(nonNullish)); const members = unique(contexts.map((c) => c.member).filter(nonNullish));

View file

@ -1,15 +1,16 @@
import * as t from "io-ts"; import z from "zod";
import { zBoundedCharacters } from "../../../utils";
import { CountersPlugin } from "../../Counters/CountersPlugin"; import { CountersPlugin } from "../../Counters/CountersPlugin";
import { LogsPlugin } from "../../Logs/LogsPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin";
import { automodAction } from "../helpers"; import { automodAction } from "../helpers";
export const AddToCounterAction = automodAction({ const configSchema = z.object({
configType: t.type({ counter: zBoundedCharacters(0, 100),
counter: t.string, amount: z.number(),
amount: t.number, });
}),
defaultConfig: {}, export const AddToCounterAction = automodAction({
configSchema,
async apply({ pluginData, contexts, actionConfig, ruleName }) { async apply({ pluginData, contexts, actionConfig, ruleName }) {
const countersPlugin = pluginData.getPlugin(CountersPlugin); const countersPlugin = pluginData.getPlugin(CountersPlugin);

View file

@ -1,6 +1,6 @@
import { Snowflake } from "discord.js"; import { Snowflake } from "discord.js";
import * as t from "io-ts";
import { erisAllowedMentionsToDjsMentionOptions } from "src/utils/erisAllowedMentionsToDjsMentionOptions"; import { erisAllowedMentionsToDjsMentionOptions } from "src/utils/erisAllowedMentionsToDjsMentionOptions";
import z from "zod";
import { LogType } from "../../../data/LogType"; import { LogType } from "../../../data/LogType";
import { import {
createTypedTemplateSafeValueContainer, createTypedTemplateSafeValueContainer,
@ -12,10 +12,12 @@ import {
chunkMessageLines, chunkMessageLines,
isTruthy, isTruthy,
messageLink, messageLink,
tAllowedMentions,
tNormalizedNullOptional,
validateAndParseMessageContent, validateAndParseMessageContent,
verboseChannelMention, verboseChannelMention,
zAllowedMentions,
zBoundedCharacters,
zNullishToUndefined,
zSnowflake
} from "../../../utils"; } from "../../../utils";
import { messageIsEmpty } from "../../../utils/messageIsEmpty"; import { messageIsEmpty } from "../../../utils/messageIsEmpty";
import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { userToTemplateSafeUser } from "../../../utils/templateSafeObjects";
@ -23,14 +25,14 @@ import { InternalPosterPlugin } from "../../InternalPoster/InternalPosterPlugin"
import { LogsPlugin } from "../../Logs/LogsPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin";
import { automodAction } from "../helpers"; import { automodAction } from "../helpers";
export const AlertAction = automodAction({ const configSchema = z.object({
configType: t.type({ channel: zSnowflake,
channel: t.string, text: zBoundedCharacters(1, 4000),
text: t.string, allowed_mentions: zNullishToUndefined(zAllowedMentions.nullable().default(null)),
allowed_mentions: tNormalizedNullOptional(tAllowedMentions), });
}),
defaultConfig: {}, export const AlertAction = automodAction({
configSchema,
async apply({ pluginData, contexts, actionConfig, ruleName, matchResult }) { async apply({ pluginData, contexts, actionConfig, ruleName, matchResult }) {
const channel = pluginData.guild.channels.cache.get(actionConfig.channel as Snowflake); const channel = pluginData.guild.channels.cache.get(actionConfig.channel as Snowflake);

View file

@ -1,11 +1,12 @@
import { AnyThreadChannel } from "discord.js"; import { AnyThreadChannel } from "discord.js";
import * as t from "io-ts"; import z from "zod";
import { noop } from "../../../utils"; import { noop } from "../../../utils";
import { automodAction } from "../helpers"; import { automodAction } from "../helpers";
const configSchema = z.strictObject({});
export const ArchiveThreadAction = automodAction({ export const ArchiveThreadAction = automodAction({
configType: t.type({}), configSchema,
defaultConfig: {},
async apply({ pluginData, contexts }) { async apply({ pluginData, contexts }) {
const threads = contexts const threads = contexts

View file

@ -1,4 +1,3 @@
import * as t from "io-ts";
import { AutomodActionBlueprint } from "../helpers"; import { AutomodActionBlueprint } from "../helpers";
import { AddRolesAction } from "./addRoles"; import { AddRolesAction } from "./addRoles";
import { AddToCounterAction } from "./addToCounter"; import { AddToCounterAction } from "./addToCounter";
@ -19,7 +18,7 @@ import { SetSlowmodeAction } from "./setSlowmode";
import { StartThreadAction } from "./startThread"; import { StartThreadAction } from "./startThread";
import { WarnAction } from "./warn"; import { WarnAction } from "./warn";
export const availableActions: Record<string, AutomodActionBlueprint<any>> = { export const availableActions = {
clean: CleanAction, clean: CleanAction,
warn: WarnAction, warn: WarnAction,
mute: MuteAction, mute: MuteAction,
@ -38,25 +37,4 @@ export const availableActions: Record<string, AutomodActionBlueprint<any>> = {
start_thread: StartThreadAction, start_thread: StartThreadAction,
archive_thread: ArchiveThreadAction, archive_thread: ArchiveThreadAction,
change_perms: ChangePermsAction, change_perms: ChangePermsAction,
}; } satisfies Record<string, AutomodActionBlueprint<any>>;
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,
});

View file

@ -1,25 +1,23 @@
import * as t from "io-ts"; import z from "zod";
import { convertDelayStringToMS, nonNullish, tDelayString, tNullable, unique } from "../../../utils"; import { convertDelayStringToMS, nonNullish, unique, zBoundedCharacters, zDelayString, zSnowflake } from "../../../utils";
import { CaseArgs } from "../../Cases/types"; import { CaseArgs } from "../../Cases/types";
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin"; import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods"; import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
import { automodAction } from "../helpers"; 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({ export const BanAction = automodAction({
configType: t.type({ configSchema,
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,
},
async apply({ pluginData, contexts, actionConfig, matchResult }) { async apply({ pluginData, contexts, actionConfig, matchResult }) {
const reason = actionConfig.reason || "Kicked automatically"; const reason = actionConfig.reason || "Kicked automatically";

View file

@ -1,18 +1,16 @@
import * as t from "io-ts"; import z from "zod";
import { nonNullish, unique } from "../../../utils"; import { nonNullish, unique, zBoundedCharacters } from "../../../utils";
import { LogsPlugin } from "../../Logs/LogsPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin";
import { automodAction } from "../helpers"; import { automodAction } from "../helpers";
export const ChangeNicknameAction = automodAction({ export const ChangeNicknameAction = automodAction({
configType: t.union([ configSchema: z.union([
t.string, zBoundedCharacters(0, 32),
t.type({ z.strictObject({
name: t.string, name: zBoundedCharacters(0, 32),
}), }),
]), ]),
defaultConfig: {},
async apply({ pluginData, contexts, actionConfig }) { async apply({ pluginData, contexts, actionConfig }) {
const members = unique(contexts.map((c) => c.member).filter(nonNullish)); const members = unique(contexts.map((c) => c.member).filter(nonNullish));

View file

@ -1,7 +1,8 @@
import { PermissionsBitField, PermissionsString } from "discord.js"; 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 { TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter";
import { isValidSnowflake, noop, tNullable, tPartialDictionary } from "../../../utils"; import { isValidSnowflake, keys, noop, zSnowflake } from "../../../utils";
import { import {
guildToTemplateSafeGuild, guildToTemplateSafeGuild,
savedMessageToTemplateSafeSavedMessage, savedMessageToTemplateSafeSavedMessage,
@ -59,16 +60,19 @@ const realToLegacyMap = Object.entries(legacyPermMap).reduce((map, pair) => {
return map; return map;
}, {}) as Record<keyof typeof PermissionsBitField.Flags, keyof typeof legacyPermMap>; }, {}) 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({ export const ChangePermsAction = automodAction({
configType: t.type({ configSchema: z.strictObject({
target: t.string, target: zSnowflake,
channel: tNullable(t.string), channel: zSnowflake.nullable().default(null),
perms: tPartialDictionary( perms: z.record(
t.union([t.keyof(PermissionsBitField.Flags), t.keyof(legacyPermMap)]), z.enum(allPermissionNames),
tNullable(t.boolean), z.boolean().nullable(),
), ),
}), }),
defaultConfig: {},
async apply({ pluginData, contexts, actionConfig }) { async apply({ pluginData, contexts, actionConfig }) {
const user = contexts.find((c) => c.user)?.user; const user = contexts.find((c) => c.user)?.user;

View file

@ -1,12 +1,11 @@
import { GuildTextBasedChannel, Snowflake } from "discord.js"; import { GuildTextBasedChannel, Snowflake } from "discord.js";
import * as t from "io-ts"; import z from "zod";
import { LogType } from "../../../data/LogType"; import { LogType } from "../../../data/LogType";
import { noop } from "../../../utils"; import { noop } from "../../../utils";
import { automodAction } from "../helpers"; import { automodAction } from "../helpers";
export const CleanAction = automodAction({ export const CleanAction = automodAction({
configType: t.boolean, configSchema: z.boolean().default(false),
defaultConfig: false,
async apply({ pluginData, contexts, ruleName }) { async apply({ pluginData, contexts, ruleName }) {
const messageIdsToDeleteByChannelId: Map<string, string[]> = new Map(); const messageIdsToDeleteByChannelId: Map<string, string[]> = new Map();

View file

@ -1,13 +1,12 @@
import * as t from "io-ts"; import z from "zod";
import { zBoundedCharacters } from "../../../utils";
import { automodAction } from "../helpers"; import { automodAction } from "../helpers";
export const ExampleAction = automodAction({ export const ExampleAction = automodAction({
configType: t.type({ configSchema: z.strictObject({
someValue: t.string, someValue: zBoundedCharacters(0, 1000),
}), }),
defaultConfig: {},
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
async apply({ pluginData, contexts, actionConfig }) { async apply({ pluginData, contexts, actionConfig }) {
// TODO: Everything // TODO: Everything

View file

@ -1,24 +1,20 @@
import * as t from "io-ts"; import z from "zod";
import { asyncMap, nonNullish, resolveMember, tNullable, unique } from "../../../utils"; import { asyncMap, nonNullish, resolveMember, unique, zBoundedCharacters, zSnowflake } from "../../../utils";
import { CaseArgs } from "../../Cases/types"; import { CaseArgs } from "../../Cases/types";
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin"; import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods"; import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
import { automodAction } from "../helpers"; import { automodAction } from "../helpers";
import { zNotify } from "../types";
export const KickAction = automodAction({ export const KickAction = automodAction({
configType: t.type({ configSchema: z.strictObject({
reason: tNullable(t.string), reason: zBoundedCharacters(0, 4000).nullable().default(null),
notify: tNullable(t.string), notify: zNotify.nullable().default(null),
notifyChannel: tNullable(t.string), notifyChannel: zSnowflake.nullable().default(null),
postInCaseLog: tNullable(t.boolean), postInCaseLog: z.boolean().nullable().default(null),
hide_case: tNullable(t.boolean), hide_case: z.boolean().nullable().default(false),
}), }),
defaultConfig: {
notify: null, // Use defaults from ModActions
hide_case: false,
},
async apply({ pluginData, contexts, actionConfig, matchResult }) { async apply({ pluginData, contexts, actionConfig, matchResult }) {
const reason = actionConfig.reason || "Kicked automatically"; const reason = actionConfig.reason || "Kicked automatically";
const contactMethods = actionConfig.notify ? resolveActionContactMethods(pluginData, actionConfig) : undefined; const contactMethods = actionConfig.notify ? resolveActionContactMethods(pluginData, actionConfig) : undefined;

View file

@ -1,11 +1,10 @@
import * as t from "io-ts"; import z from "zod";
import { isTruthy, unique } from "../../../utils"; import { isTruthy, unique } from "../../../utils";
import { LogsPlugin } from "../../Logs/LogsPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin";
import { automodAction } from "../helpers"; import { automodAction } from "../helpers";
export const LogAction = automodAction({ export const LogAction = automodAction({
configType: t.boolean, configSchema: z.boolean().default(true),
defaultConfig: true,
async apply({ pluginData, contexts, ruleName, matchResult }) { async apply({ pluginData, contexts, ruleName, matchResult }) {
const users = unique(contexts.map((c) => c.user)).filter(isTruthy); const users = unique(contexts.map((c) => c.user)).filter(isTruthy);

View file

@ -1,29 +1,25 @@
import * as t from "io-ts"; import z from "zod";
import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError"; 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 { CaseArgs } from "../../Cases/types";
import { LogsPlugin } from "../../Logs/LogsPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin";
import { MutesPlugin } from "../../Mutes/MutesPlugin"; import { MutesPlugin } from "../../Mutes/MutesPlugin";
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods"; import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
import { automodAction } from "../helpers"; import { automodAction } from "../helpers";
import { zNotify } from "../types";
export const MuteAction = automodAction({ export const MuteAction = automodAction({
configType: t.type({ configSchema: z.strictObject({
reason: tNullable(t.string), reason: zBoundedCharacters(0, 4000).nullable().default(null),
duration: tNullable(tDelayString), duration: zDelayString.nullable().default(null),
notify: tNullable(t.string), notify: zNotify.nullable().default(null),
notifyChannel: tNullable(t.string), notifyChannel: zSnowflake.nullable().default(null),
remove_roles_on_mute: tNullable(t.union([t.boolean, t.array(t.string)])), remove_roles_on_mute: z.union([z.boolean(), z.array(zSnowflake)]).nullable().default(null),
restore_roles_on_mute: tNullable(t.union([t.boolean, t.array(t.string)])), restore_roles_on_mute: z.union([z.boolean(), z.array(zSnowflake)]).nullable().default(null),
postInCaseLog: tNullable(t.boolean), postInCaseLog: z.boolean().nullable().default(null),
hide_case: tNullable(t.boolean), hide_case: z.boolean().nullable().default(false),
}), }),
defaultConfig: {
notify: null, // Use defaults from ModActions
hide_case: false,
},
async apply({ pluginData, contexts, actionConfig, ruleName, matchResult }) { async apply({ pluginData, contexts, actionConfig, ruleName, matchResult }) {
const duration = actionConfig.duration ? convertDelayStringToMS(actionConfig.duration)! : undefined; const duration = actionConfig.duration ? convertDelayStringToMS(actionConfig.duration)! : undefined;
const reason = actionConfig.reason || "Muted automatically"; const reason = actionConfig.reason || "Muted automatically";

View file

@ -1,6 +1,6 @@
import { PermissionFlagsBits, Snowflake } from "discord.js"; import { PermissionFlagsBits, Snowflake } from "discord.js";
import * as t from "io-ts"; import z from "zod";
import { nonNullish, unique } from "../../../utils"; import { nonNullish, unique, zSnowflake } from "../../../utils";
import { canAssignRole } from "../../../utils/canAssignRole"; import { canAssignRole } from "../../../utils/canAssignRole";
import { getMissingPermissions } from "../../../utils/getMissingPermissions"; import { getMissingPermissions } from "../../../utils/getMissingPermissions";
import { memberRolesLock } from "../../../utils/lockNameHelpers"; import { memberRolesLock } from "../../../utils/lockNameHelpers";
@ -12,9 +12,7 @@ import { automodAction } from "../helpers";
const p = PermissionFlagsBits; const p = PermissionFlagsBits;
export const RemoveRolesAction = automodAction({ export const RemoveRolesAction = automodAction({
configType: t.array(t.string), configSchema: z.array(zSnowflake).default([]),
defaultConfig: [],
async apply({ pluginData, contexts, actionConfig, ruleName }) { async apply({ pluginData, contexts, actionConfig, ruleName }) {
const members = unique(contexts.map((c) => c.member).filter(nonNullish)); const members = unique(contexts.map((c) => c.member).filter(nonNullish));

View file

@ -1,16 +1,16 @@
import { GuildTextBasedChannel, MessageCreateOptions, PermissionsBitField, Snowflake, User } from "discord.js"; 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 { TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter";
import { import {
convertDelayStringToMS, convertDelayStringToMS,
noop, noop,
renderRecursively, renderRecursively,
tDelayString,
tMessageContent,
tNullable,
unique, unique,
validateAndParseMessageContent, validateAndParseMessageContent,
verboseChannelMention, verboseChannelMention,
zBoundedCharacters,
zDelayString,
zMessageContent
} from "../../../utils"; } from "../../../utils";
import { hasDiscordPermissions } from "../../../utils/hasDiscordPermissions"; import { hasDiscordPermissions } from "../../../utils/hasDiscordPermissions";
import { messageIsEmpty } from "../../../utils/messageIsEmpty"; import { messageIsEmpty } from "../../../utils/messageIsEmpty";
@ -20,17 +20,15 @@ import { automodAction } from "../helpers";
import { AutomodContext } from "../types"; import { AutomodContext } from "../types";
export const ReplyAction = automodAction({ export const ReplyAction = automodAction({
configType: t.union([ configSchema: z.union([
t.string, zBoundedCharacters(0, 4000),
t.type({ z.strictObject({
text: tMessageContent, text: zMessageContent,
auto_delete: tNullable(t.union([tDelayString, t.number])), auto_delete: z.union([zDelayString, z.number()]).nullable().default(null),
inline: tNullable(t.boolean), inline: z.boolean().default(false),
}), }),
]), ]),
defaultConfig: {},
async apply({ pluginData, contexts, actionConfig, ruleName }) { async apply({ pluginData, contexts, actionConfig, ruleName }) {
const contextsWithTextChannels = contexts const contextsWithTextChannels = contexts
.filter((c) => c.message?.channel_id) .filter((c) => c.message?.channel_id)

View file

@ -1,11 +1,9 @@
import * as t from "io-ts"; import { zBoundedCharacters } from "../../../utils";
import { tNullable } from "../../../utils";
import { setAntiraidLevel } from "../functions/setAntiraidLevel"; import { setAntiraidLevel } from "../functions/setAntiraidLevel";
import { automodAction } from "../helpers"; import { automodAction } from "../helpers";
export const SetAntiraidLevelAction = automodAction({ export const SetAntiraidLevelAction = automodAction({
configType: tNullable(t.string), configSchema: zBoundedCharacters(0, 100).nullable(),
defaultConfig: "",
async apply({ pluginData, actionConfig }) { async apply({ pluginData, actionConfig }) {
setAntiraidLevel(pluginData, actionConfig ?? null); setAntiraidLevel(pluginData, actionConfig ?? null);

View file

@ -1,16 +1,15 @@
import * as t from "io-ts"; import z from "zod";
import { zBoundedCharacters } from "../../../utils";
import { CountersPlugin } from "../../Counters/CountersPlugin"; import { CountersPlugin } from "../../Counters/CountersPlugin";
import { LogsPlugin } from "../../Logs/LogsPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin";
import { automodAction } from "../helpers"; import { automodAction } from "../helpers";
export const SetCounterAction = automodAction({ export const SetCounterAction = automodAction({
configType: t.type({ configSchema: z.strictObject({
counter: t.string, counter: zBoundedCharacters(0, 100),
value: t.number, value: z.number(),
}), }),
defaultConfig: {},
async apply({ pluginData, contexts, actionConfig, ruleName }) { async apply({ pluginData, contexts, actionConfig, ruleName }) {
const countersPlugin = pluginData.getPlugin(CountersPlugin); const countersPlugin = pluginData.getPlugin(CountersPlugin);
if (!countersPlugin.counterExists(actionConfig.counter)) { if (!countersPlugin.counterExists(actionConfig.counter)) {

View file

@ -1,19 +1,15 @@
import { ChannelType, GuildTextBasedChannel, Snowflake } from "discord.js"; import { ChannelType, GuildTextBasedChannel, Snowflake } from "discord.js";
import * as t from "io-ts"; import z from "zod";
import { convertDelayStringToMS, isDiscordAPIError, tDelayString, tNullable } from "../../../utils"; import { convertDelayStringToMS, isDiscordAPIError, zDelayString, zSnowflake } from "../../../utils";
import { LogsPlugin } from "../../Logs/LogsPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin";
import { automodAction } from "../helpers"; import { automodAction } from "../helpers";
export const SetSlowmodeAction = automodAction({ export const SetSlowmodeAction = automodAction({
configType: t.type({ configSchema: z.strictObject({
channels: t.array(t.string), channels: z.array(zSnowflake),
duration: tNullable(tDelayString), duration: zDelayString.nullable().default("10s"),
}), }),
defaultConfig: {
duration: "10s",
},
async apply({ pluginData, actionConfig }) { async apply({ pluginData, actionConfig }) {
const slowmodeMs = Math.max(actionConfig.duration ? convertDelayStringToMS(actionConfig.duration)! : 0, 0); const slowmodeMs = Math.max(actionConfig.duration ? convertDelayStringToMS(actionConfig.duration)! : 0, 0);

View file

@ -5,9 +5,9 @@ import {
ThreadAutoArchiveDuration, ThreadAutoArchiveDuration,
ThreadChannel, ThreadChannel,
} from "discord.js"; } from "discord.js";
import * as t from "io-ts"; import z from "zod";
import { TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter"; 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 { savedMessageToTemplateSafeSavedMessage, userToTemplateSafeUser } from "../../../utils/templateSafeObjects";
import { automodAction } from "../helpers"; import { automodAction } from "../helpers";
@ -19,18 +19,14 @@ const validThreadAutoArchiveDurations: ThreadAutoArchiveDuration[] = [
]; ];
export const StartThreadAction = automodAction({ export const StartThreadAction = automodAction({
configType: t.type({ configSchema: z.strictObject({
name: tNullable(t.string), name: zBoundedCharacters(1, 100).nullable(),
auto_archive: tDelayString, auto_archive: zDelayString,
private: tNullable(t.boolean), private: z.boolean().default(false),
slowmode: tNullable(tDelayString), slowmode: zDelayString.nullable().default(null),
limit_per_channel: tNullable(t.number), limit_per_channel: z.number().nullable().default(5),
}), }),
defaultConfig: {
limit_per_channel: 5,
},
async apply({ pluginData, contexts, actionConfig }) { async apply({ pluginData, contexts, actionConfig }) {
// check if the message still exists, we don't want to create threads for deleted messages // check if the message still exists, we don't want to create threads for deleted messages
const threads = contexts.filter((c) => { const threads = contexts.filter((c) => {

View file

@ -1,24 +1,20 @@
import * as t from "io-ts"; import z from "zod";
import { asyncMap, nonNullish, resolveMember, tNullable, unique } from "../../../utils"; import { asyncMap, nonNullish, resolveMember, unique, zBoundedCharacters, zSnowflake } from "../../../utils";
import { CaseArgs } from "../../Cases/types"; import { CaseArgs } from "../../Cases/types";
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin"; import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods"; import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
import { automodAction } from "../helpers"; import { automodAction } from "../helpers";
import { zNotify } from "../types";
export const WarnAction = automodAction({ export const WarnAction = automodAction({
configType: t.type({ configSchema: z.strictObject({
reason: tNullable(t.string), reason: zBoundedCharacters(0, 4000).nullable().default(null),
notify: tNullable(t.string), notify: zNotify.nullable().default(null),
notifyChannel: tNullable(t.string), notifyChannel: zSnowflake.nullable().default(null),
postInCaseLog: tNullable(t.boolean), postInCaseLog: z.boolean().nullable().default(null),
hide_case: tNullable(t.boolean), hide_case: z.boolean().nullable().default(false),
}), }),
defaultConfig: {
notify: null, // Use defaults from ModActions
hide_case: false,
},
async apply({ pluginData, contexts, actionConfig, matchResult }) { async apply({ pluginData, contexts, actionConfig, matchResult }) {
const reason = actionConfig.reason || "Warned automatically"; const reason = actionConfig.reason || "Warned automatically";
const contactMethods = actionConfig.notify ? resolveActionContactMethods(pluginData, actionConfig) : undefined; const contactMethods = actionConfig.notify ? resolveActionContactMethods(pluginData, actionConfig) : undefined;

View file

@ -1,28 +1,27 @@
import * as t from "io-ts";
import { SavedMessage } from "../../../data/entities/SavedMessage"; import { SavedMessage } from "../../../data/entities/SavedMessage";
import { humanizeDurationShort } from "../../../humanizeDurationShort"; import { humanizeDurationShort } from "../../../humanizeDurationShort";
import { getBaseUrl } from "../../../pluginUtils"; import { getBaseUrl } from "../../../pluginUtils";
import { convertDelayStringToMS, sorter, tDelayString, tNullable } from "../../../utils"; import { convertDelayStringToMS, sorter, zDelayString } from "../../../utils";
import { RecentActionType } from "../constants"; import { RecentActionType } from "../constants";
import { automodTrigger } from "../helpers"; import { automodTrigger } from "../helpers";
import { findRecentSpam } from "./findRecentSpam"; import { findRecentSpam } from "./findRecentSpam";
import { getMatchingMessageRecentActions } from "./getMatchingMessageRecentActions"; import { getMatchingMessageRecentActions } from "./getMatchingMessageRecentActions";
import { getMessageSpamIdentifier } from "./getSpamIdentifier"; import { getMessageSpamIdentifier } from "./getSpamIdentifier";
import z from "zod";
const MessageSpamTriggerConfig = t.type({
amount: t.number,
within: tDelayString,
per_channel: tNullable(t.boolean),
});
interface TMessageSpamMatchResultType { interface TMessageSpamMatchResultType {
archiveId: string; archiveId: string;
} }
const configSchema = z.strictObject({
amount: z.number().int(),
within: zDelayString,
per_channel: z.boolean().optional(),
});
export function createMessageSpamTrigger(spamType: RecentActionType, prettyName: string) { export function createMessageSpamTrigger(spamType: RecentActionType, prettyName: string) {
return automodTrigger<TMessageSpamMatchResultType>()({ return automodTrigger<TMessageSpamMatchResultType>()({
configType: MessageSpamTriggerConfig, configSchema,
defaultConfig: {},
async match({ pluginData, context, triggerConfig }) { async match({ pluginData, context, triggerConfig }) {
if (!context.message) { if (!context.message) {

View file

@ -1,7 +1,7 @@
import * as t from "io-ts";
import { GuildPluginData } from "knub"; import { GuildPluginData } from "knub";
import { Awaitable } from "../../utils/typeUtils"; import { Awaitable } from "../../utils/typeUtils";
import { AutomodContext, AutomodPluginType } from "./types"; import { AutomodContext, AutomodPluginType } from "./types";
import z, { ZodTypeAny } from "zod";
interface BaseAutomodTriggerMatchResult { interface BaseAutomodTriggerMatchResult {
extraContexts?: AutomodContext[]; extraContexts?: AutomodContext[];
@ -31,21 +31,19 @@ type AutomodTriggerRenderMatchInformationFn<TConfigType, TMatchResultExtra> = (m
matchResult: AutomodTriggerMatchResult<TMatchResultExtra>; matchResult: AutomodTriggerMatchResult<TMatchResultExtra>;
}) => Awaitable<string>; }) => Awaitable<string>;
export interface AutomodTriggerBlueprint<TConfigType extends t.Any, TMatchResultExtra> { export interface AutomodTriggerBlueprint<TConfigSchema extends ZodTypeAny, TMatchResultExtra> {
configType: TConfigType; configSchema: TConfigSchema;
defaultConfig: Partial<t.TypeOf<TConfigType>>; match: AutomodTriggerMatchFn<z.output<TConfigSchema>, TMatchResultExtra>;
renderMatchInformation: AutomodTriggerRenderMatchInformationFn<z.output<TConfigSchema>, TMatchResultExtra>;
match: AutomodTriggerMatchFn<t.TypeOf<TConfigType>, TMatchResultExtra>;
renderMatchInformation: AutomodTriggerRenderMatchInformationFn<t.TypeOf<TConfigType>, TMatchResultExtra>;
} }
export function automodTrigger<TMatchResultExtra>(): <TConfigType extends t.Any>( export function automodTrigger<TMatchResultExtra>(): <TConfigSchema extends ZodTypeAny>(
blueprint: AutomodTriggerBlueprint<TConfigType, TMatchResultExtra>, blueprint: AutomodTriggerBlueprint<TConfigSchema, TMatchResultExtra>,
) => AutomodTriggerBlueprint<TConfigType, TMatchResultExtra>; ) => AutomodTriggerBlueprint<TConfigSchema, TMatchResultExtra>;
export function automodTrigger<TConfigType extends t.Any>( export function automodTrigger<TConfigSchema extends ZodTypeAny>(
blueprint: AutomodTriggerBlueprint<TConfigType, unknown>, blueprint: AutomodTriggerBlueprint<TConfigSchema, unknown>,
): AutomodTriggerBlueprint<TConfigType, unknown>; ): AutomodTriggerBlueprint<TConfigSchema, unknown>;
export function automodTrigger(...args) { export function automodTrigger(...args) {
if (args.length) { if (args.length) {
@ -63,15 +61,13 @@ type AutomodActionApplyFn<TConfigType> = (meta: {
matchResult: AutomodTriggerMatchResult; matchResult: AutomodTriggerMatchResult;
}) => Awaitable<void>; }) => Awaitable<void>;
export interface AutomodActionBlueprint<TConfigType extends t.Any> { export interface AutomodActionBlueprint<TConfigSchema extends ZodTypeAny> {
configType: TConfigType; configSchema: TConfigSchema;
defaultConfig: Partial<t.TypeOf<TConfigType>>; apply: AutomodActionApplyFn<z.output<TConfigSchema>>;
apply: AutomodActionApplyFn<t.TypeOf<TConfigType>>;
} }
export function automodAction<TConfigType extends t.Any>( export function automodAction<TConfigSchema extends ZodTypeAny>(
blueprint: AutomodActionBlueprint<TConfigType>, blueprint: AutomodActionBlueprint<TConfigSchema>,
): AutomodActionBlueprint<TConfigType> { ): AutomodActionBlueprint<TConfigSchema> {
return blueprint; return blueprint;
} }

View file

@ -1,6 +1,6 @@
import { trimPluginDescription } from "../../utils"; import { trimPluginDescription } from "../../utils";
import { ZeppelinGuildPluginBlueprint } from "../ZeppelinPluginBlueprint"; import { ZeppelinGuildPluginBlueprint } from "../ZeppelinPluginBlueprint";
import { ConfigSchema } from "./types"; import { zAutomodConfig } from "./types";
export const pluginInfo: ZeppelinGuildPluginBlueprint["info"] = { export const pluginInfo: ZeppelinGuildPluginBlueprint["info"] = {
prettyName: "Automod", prettyName: "Automod",
@ -100,5 +100,5 @@ export const pluginInfo: ZeppelinGuildPluginBlueprint["info"] = {
{matchSummary} {matchSummary}
~~~ ~~~
`), `),
configSchema: ConfigSchema, configSchema: zAutomodConfig,
}; };

View file

@ -1,15 +1,14 @@
import * as t from "io-ts";
import { tNullable } from "../../../utils";
import { automodTrigger } from "../helpers"; import { automodTrigger } from "../helpers";
import z from "zod";
interface AntiraidLevelTriggerResult {} interface AntiraidLevelTriggerResult {}
export const AntiraidLevelTrigger = automodTrigger<AntiraidLevelTriggerResult>()({ const configSchema = z.strictObject({
configType: t.type({ level: z.nullable(z.string().max(100)),
level: tNullable(t.string), });
}),
defaultConfig: {}, export const AntiraidLevelTrigger = automodTrigger<AntiraidLevelTriggerResult>()({
configSchema,
async match({ triggerConfig, context }) { async match({ triggerConfig, context }) {
if (!context.antiraid) { if (!context.antiraid) {

View file

@ -1,14 +1,14 @@
import { Snowflake } from "discord.js"; import { Snowflake } from "discord.js";
import * as t from "io-ts";
import { verboseChannelMention } from "../../../utils"; import { verboseChannelMention } from "../../../utils";
import { automodTrigger } from "../helpers"; import { automodTrigger } from "../helpers";
import z from "zod";
interface AnyMessageResultType {} interface AnyMessageResultType {}
export const AnyMessageTrigger = automodTrigger<AnyMessageResultType>()({ const configSchema = z.strictObject({});
configType: t.type({}),
defaultConfig: {}, export const AnyMessageTrigger = automodTrigger<AnyMessageResultType>()({
configSchema,
async match({ context }) { async match({ context }) {
if (!context.message) { if (!context.message) {

View file

@ -1,4 +1,3 @@
import * as t from "io-ts";
import { AutomodTriggerBlueprint } from "../helpers"; import { AutomodTriggerBlueprint } from "../helpers";
import { AntiraidLevelTrigger } from "./antiraidLevel"; import { AntiraidLevelTrigger } from "./antiraidLevel";
import { AnyMessageTrigger } from "./anyMessage"; import { AnyMessageTrigger } from "./anyMessage";
@ -45,6 +44,7 @@ export const availableTriggers: Record<string, AutomodTriggerBlueprint<any, any>
match_attachment_type: MatchAttachmentTypeTrigger, match_attachment_type: MatchAttachmentTypeTrigger,
match_mime_type: MatchMimeTypeTrigger, match_mime_type: MatchMimeTypeTrigger,
member_join: MemberJoinTrigger, member_join: MemberJoinTrigger,
member_leave: MemberLeaveTrigger,
role_added: RoleAddedTrigger, role_added: RoleAddedTrigger,
role_removed: RoleRemovedTrigger, role_removed: RoleRemovedTrigger,
@ -76,46 +76,3 @@ export const availableTriggers: Record<string, AutomodTriggerBlueprint<any, any>
thread_archive: ThreadArchiveTrigger, thread_archive: ThreadArchiveTrigger,
thread_unarchive: ThreadUnarchiveTrigger, 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,
});

View file

@ -1,19 +1,16 @@
import * as t from "io-ts"; import z from "zod";
import { automodTrigger } from "../helpers"; import { automodTrigger } from "../helpers";
// tslint:disable-next-line:no-empty-interface // tslint:disable-next-line:no-empty-interface
interface BanTriggerResultType {} interface BanTriggerResultType {}
export const BanTrigger = automodTrigger<BanTriggerResultType>()({ const configSchema = z.strictObject({
configType: t.type({ manual: z.boolean().default(true),
manual: t.boolean, automatic: z.boolean().default(true),
automatic: t.boolean, });
}),
defaultConfig: { export const BanTrigger = automodTrigger<BanTriggerResultType>()({
manual: true, configSchema,
automatic: true,
},
async match({ context, triggerConfig }) { async match({ context, triggerConfig }) {
if (context.modAction?.type !== "ban") { if (context.modAction?.type !== "ban") {

View file

@ -1,18 +1,17 @@
import * as t from "io-ts"; import z from "zod";
import { tNullable } from "../../../utils";
import { automodTrigger } from "../helpers"; import { automodTrigger } from "../helpers";
// tslint:disable-next-line // tslint:disable-next-line
interface CounterTriggerResult {} interface CounterTriggerResult {}
export const CounterTrigger = automodTrigger<CounterTriggerResult>()({ const configSchema = z.strictObject({
configType: t.type({ counter: z.string().max(100),
counter: t.string, trigger: z.string().max(100),
trigger: t.string, reverse: z.boolean().optional(),
reverse: tNullable(t.boolean), });
}),
defaultConfig: {}, export const CounterTrigger = automodTrigger<CounterTriggerResult>()({
configSchema,
async match({ triggerConfig, context }) { async match({ triggerConfig, context }) {
if (!context.counterTrigger) { if (!context.counterTrigger) {

View file

@ -1,18 +1,16 @@
import * as t from "io-ts"; import z from "zod";
import { automodTrigger } from "../helpers"; import { automodTrigger } from "../helpers";
interface ExampleMatchResultType { interface ExampleMatchResultType {
isBanana: boolean; isBanana: boolean;
} }
export const ExampleTrigger = automodTrigger<ExampleMatchResultType>()({ const configSchema = z.strictObject({
configType: t.type({ allowedFruits: z.array(z.string().max(100)).max(50).default(["peach", "banana"]),
allowedFruits: t.array(t.string), });
}),
defaultConfig: { export const ExampleTrigger = automodTrigger<ExampleMatchResultType>()({
allowedFruits: ["peach", "banana"], configSchema,
},
async match({ triggerConfig, context }) { async match({ triggerConfig, context }) {
const foundFruit = triggerConfig.allowedFruits.find((fruit) => context.message?.data.content === fruit); const foundFruit = triggerConfig.allowedFruits.find((fruit) => context.message?.data.content === fruit);

View file

@ -1,19 +1,16 @@
import * as t from "io-ts"; import z from "zod";
import { automodTrigger } from "../helpers"; import { automodTrigger } from "../helpers";
// tslint:disable-next-line:no-empty-interface // tslint:disable-next-line:no-empty-interface
interface KickTriggerResultType {} interface KickTriggerResultType {}
export const KickTrigger = automodTrigger<KickTriggerResultType>()({ const configSchema = z.strictObject({
configType: t.type({ manual: z.boolean().default(true),
manual: t.boolean, automatic: z.boolean().default(true),
automatic: t.boolean, });
}),
defaultConfig: { export const KickTrigger = automodTrigger<KickTriggerResultType>()({
manual: true, configSchema,
automatic: true,
},
async match({ context, triggerConfig }) { async match({ context, triggerConfig }) {
if (context.modAction?.type !== "kick") { if (context.modAction?.type !== "kick") {

View file

@ -1,5 +1,5 @@
import { escapeInlineCode, Snowflake } from "discord.js"; import { escapeInlineCode, Snowflake } from "discord.js";
import * as t from "io-ts"; import z from "zod";
import { asSingleLine, messageSummary, verboseChannelMention } from "../../../utils"; import { asSingleLine, messageSummary, verboseChannelMention } from "../../../utils";
import { automodTrigger } from "../helpers"; import { automodTrigger } from "../helpers";
@ -8,20 +8,31 @@ interface MatchResultType {
mode: "blacklist" | "whitelist"; mode: "blacklist" | "whitelist";
} }
export const MatchAttachmentTypeTrigger = automodTrigger<MatchResultType>()({ const configSchema = z.strictObject({
configType: t.type({ filetype_blacklist: z.array(z.string().max(32)).max(255).default([]),
filetype_blacklist: t.array(t.string), blacklist_enabled: z.boolean().default(false),
blacklist_enabled: t.boolean, filetype_whitelist: z.array(z.string().max(32)).max(255).default([]),
filetype_whitelist: t.array(t.string), whitelist_enabled: z.boolean().default(false),
whitelist_enabled: t.boolean, }).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: { export const MatchAttachmentTypeTrigger = automodTrigger<MatchResultType>()({
filetype_blacklist: [], configSchema,
blacklist_enabled: false,
filetype_whitelist: [],
whitelist_enabled: false,
},
async match({ context, triggerConfig: trigger }) { async match({ context, triggerConfig: trigger }) {
if (!context.message) { if (!context.message) {

View file

@ -1,5 +1,5 @@
import * as t from "io-ts"; import z from "zod";
import { getInviteCodesInString, GuildInvite, isGuildInvite, resolveInvite, tNullable } from "../../../utils"; import { getInviteCodesInString, GuildInvite, isGuildInvite, resolveInvite, zSnowflake } from "../../../utils";
import { getTextMatchPartialSummary } from "../functions/getTextMatchPartialSummary"; import { getTextMatchPartialSummary } from "../functions/getTextMatchPartialSummary";
import { MatchableTextType, matchMultipleTextTypesOnMessage } from "../functions/matchMultipleTextTypesOnMessage"; import { MatchableTextType, matchMultipleTextTypesOnMessage } from "../functions/matchMultipleTextTypesOnMessage";
import { automodTrigger } from "../helpers"; import { automodTrigger } from "../helpers";
@ -10,30 +10,22 @@ interface MatchResultType {
invite?: GuildInvite; invite?: GuildInvite;
} }
export const MatchInvitesTrigger = automodTrigger<MatchResultType>()({ const configSchema = z.strictObject({
configType: t.type({ include_guilds: z.array(zSnowflake).max(255).optional(),
include_guilds: tNullable(t.array(t.string)), exclude_guilds: z.array(zSnowflake).max(255).optional(),
exclude_guilds: tNullable(t.array(t.string)), include_invite_codes: z.array(z.string().max(32)).max(255).optional(),
include_invite_codes: tNullable(t.array(t.string)), exclude_invite_codes: z.array(z.string().max(32)).max(255).optional(),
exclude_invite_codes: tNullable(t.array(t.string)), allow_group_dm_invites: z.boolean().default(false),
allow_group_dm_invites: t.boolean, match_messages: z.boolean().default(true),
match_messages: t.boolean, match_embeds: z.boolean().default(false),
match_embeds: t.boolean, match_visible_names: z.boolean().default(false),
match_visible_names: t.boolean, match_usernames: z.boolean().default(false),
match_usernames: t.boolean, match_nicknames: z.boolean().default(false),
match_nicknames: t.boolean, match_custom_status: z.boolean().default(false),
match_custom_status: t.boolean, });
}),
defaultConfig: { export const MatchInvitesTrigger = automodTrigger<MatchResultType>()({
allow_group_dm_invites: false, configSchema,
match_messages: true,
match_embeds: false,
match_visible_names: false,
match_usernames: false,
match_nicknames: false,
match_custom_status: false,
},
async match({ pluginData, context, triggerConfig: trigger }) { async match({ pluginData, context, triggerConfig: trigger }) {
if (!context.message) { if (!context.message) {

View file

@ -1,11 +1,10 @@
import { escapeInlineCode } from "discord.js"; import { escapeInlineCode } from "discord.js";
import * as t from "io-ts"; import z from "zod";
import { allowTimeout } from "../../../RegExpRunner"; import { allowTimeout } from "../../../RegExpRunner";
import { phishermanDomainIsSafe } from "../../../data/Phisherman"; import { phishermanDomainIsSafe } from "../../../data/Phisherman";
import { getUrlsInString, tNullable } from "../../../utils"; import { getUrlsInString, zRegex } from "../../../utils";
import { mergeRegexes } from "../../../utils/mergeRegexes"; import { mergeRegexes } from "../../../utils/mergeRegexes";
import { mergeWordsIntoRegex } from "../../../utils/mergeWordsIntoRegex"; import { mergeWordsIntoRegex } from "../../../utils/mergeWordsIntoRegex";
import { TRegex } from "../../../validatorUtils";
import { PhishermanPlugin } from "../../Phisherman/PhishermanPlugin"; import { PhishermanPlugin } from "../../Phisherman/PhishermanPlugin";
import { getTextMatchPartialSummary } from "../functions/getTextMatchPartialSummary"; import { getTextMatchPartialSummary } from "../functions/getTextMatchPartialSummary";
import { MatchableTextType, matchMultipleTextTypesOnMessage } from "../functions/matchMultipleTextTypesOnMessage"; import { MatchableTextType, matchMultipleTextTypesOnMessage } from "../functions/matchMultipleTextTypesOnMessage";
@ -21,40 +20,29 @@ const regexCache = new WeakMap<any, RegExp[]>();
const quickLinkCheck = /^https?:\/\//i; const quickLinkCheck = /^https?:\/\//i;
export const MatchLinksTrigger = automodTrigger<MatchResultType>()({ const configSchema = z.strictObject({
configType: t.type({ include_domains: z.array(z.string().max(255)).max(255).optional(),
include_domains: tNullable(t.array(t.string)), exclude_domains: z.array(z.string().max(255)).max(255).optional(),
exclude_domains: tNullable(t.array(t.string)), include_subdomains: z.boolean().default(true),
include_subdomains: t.boolean, include_words: z.array(z.string().max(2000)).max(512).optional(),
include_words: tNullable(t.array(t.string)), exclude_words: z.array(z.string().max(2000)).max(512).optional(),
exclude_words: tNullable(t.array(t.string)), include_regex: z.array(zRegex(z.string().max(2000))).max(512).optional(),
include_regex: tNullable(t.array(TRegex)), exclude_regex: z.array(zRegex(z.string().max(2000))).max(512).optional(),
exclude_regex: tNullable(t.array(TRegex)), phisherman: z.strictObject({
phisherman: tNullable( include_suspected: z.boolean().optional(),
t.type({ include_verified: z.boolean().optional(),
include_suspected: tNullable(t.boolean), }).optional(),
include_verified: tNullable(t.boolean), only_real_links: z.boolean(),
}), match_messages: z.boolean().default(true),
), match_embeds: z.boolean().default(true),
only_real_links: t.boolean, match_visible_names: z.boolean().default(false),
match_messages: t.boolean, match_usernames: z.boolean().default(false),
match_embeds: t.boolean, match_nicknames: z.boolean().default(false),
match_visible_names: t.boolean, match_custom_status: z.boolean().default(false),
match_usernames: t.boolean, });
match_nicknames: t.boolean,
match_custom_status: t.boolean,
}),
defaultConfig: { export const MatchLinksTrigger = automodTrigger<MatchResultType>()({
include_subdomains: true, configSchema,
match_messages: true,
match_embeds: false,
match_visible_names: false,
match_usernames: false,
match_nicknames: false,
match_custom_status: false,
only_real_links: true,
},
async match({ pluginData, context, triggerConfig: trigger }) { async match({ pluginData, context, triggerConfig: trigger }) {
if (!context.message) { if (!context.message) {

View file

@ -1,5 +1,5 @@
import { escapeInlineCode } from "discord.js"; import { escapeInlineCode } from "discord.js";
import * as t from "io-ts"; import z from "zod";
import { asSingleLine, messageSummary, verboseChannelMention } from "../../../utils"; import { asSingleLine, messageSummary, verboseChannelMention } from "../../../utils";
import { automodTrigger } from "../helpers"; import { automodTrigger } from "../helpers";
@ -8,20 +8,31 @@ interface MatchResultType {
mode: "blacklist" | "whitelist"; mode: "blacklist" | "whitelist";
} }
export const MatchMimeTypeTrigger = automodTrigger<MatchResultType>()({ const configSchema = z.strictObject({
configType: t.type({ mime_type_blacklist: z.array(z.string().max(255)).max(255).default([]),
mime_type_blacklist: t.array(t.string), blacklist_enabled: z.boolean().default(false),
blacklist_enabled: t.boolean, mime_type_whitelist: z.array(z.string().max(255)).max(255).default([]),
mime_type_whitelist: t.array(t.string), whitelist_enabled: z.boolean().default(false),
whitelist_enabled: t.boolean, }).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: { export const MatchMimeTypeTrigger = automodTrigger<MatchResultType>()({
mime_type_blacklist: [], configSchema,
blacklist_enabled: false,
mime_type_whitelist: [],
whitelist_enabled: false,
},
async match({ context, triggerConfig: trigger }) { async match({ context, triggerConfig: trigger }) {
if (!context.message) return; if (!context.message) return;

View file

@ -1,9 +1,9 @@
import * as t from "io-ts"; import z from "zod";
import { allowTimeout } from "../../../RegExpRunner"; import { allowTimeout } from "../../../RegExpRunner";
import { zRegex } from "../../../utils";
import { mergeRegexes } from "../../../utils/mergeRegexes"; import { mergeRegexes } from "../../../utils/mergeRegexes";
import { normalizeText } from "../../../utils/normalizeText"; import { normalizeText } from "../../../utils/normalizeText";
import { stripMarkdown } from "../../../utils/stripMarkdown"; import { stripMarkdown } from "../../../utils/stripMarkdown";
import { TRegex } from "../../../validatorUtils";
import { getTextMatchPartialSummary } from "../functions/getTextMatchPartialSummary"; import { getTextMatchPartialSummary } from "../functions/getTextMatchPartialSummary";
import { MatchableTextType, matchMultipleTextTypesOnMessage } from "../functions/matchMultipleTextTypesOnMessage"; import { MatchableTextType, matchMultipleTextTypesOnMessage } from "../functions/matchMultipleTextTypesOnMessage";
import { automodTrigger } from "../helpers"; import { automodTrigger } from "../helpers";
@ -13,33 +13,23 @@ interface MatchResultType {
type: MatchableTextType; 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[]>(); const regexCache = new WeakMap<any, RegExp[]>();
export const MatchRegexTrigger = automodTrigger<MatchResultType>()({ export const MatchRegexTrigger = automodTrigger<MatchResultType>()({
configType: t.type({ configSchema,
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,
},
async match({ pluginData, context, triggerConfig: trigger }) { async match({ pluginData, context, triggerConfig: trigger }) {
if (!context.message) { if (!context.message) {

View file

@ -1,5 +1,5 @@
import escapeStringRegexp from "escape-string-regexp"; import escapeStringRegexp from "escape-string-regexp";
import * as t from "io-ts"; import z from "zod";
import { normalizeText } from "../../../utils/normalizeText"; import { normalizeText } from "../../../utils/normalizeText";
import { stripMarkdown } from "../../../utils/stripMarkdown"; import { stripMarkdown } from "../../../utils/stripMarkdown";
import { getTextMatchPartialSummary } from "../functions/getTextMatchPartialSummary"; import { getTextMatchPartialSummary } from "../functions/getTextMatchPartialSummary";
@ -13,37 +13,24 @@ interface MatchResultType {
const regexCache = new WeakMap<any, RegExp[]>(); const regexCache = new WeakMap<any, RegExp[]>();
export const MatchWordsTrigger = automodTrigger<MatchResultType>()({ const configSchema = z.strictObject({
configType: t.type({ words: z.array(z.string().max(2000)).max(512),
words: t.array(t.string), case_sensitive: z.boolean().default(false),
case_sensitive: t.boolean, only_full_words: z.boolean().default(true),
only_full_words: t.boolean, normalize: z.boolean().default(false),
normalize: t.boolean, loose_matching: z.boolean().default(false),
loose_matching: t.boolean, loose_matching_threshold: z.number().int().default(4),
loose_matching_threshold: t.number, strip_markdown: z.boolean().default(false),
strip_markdown: t.boolean, match_messages: z.boolean().default(true),
match_messages: t.boolean, match_embeds: z.boolean().default(false),
match_embeds: t.boolean, match_visible_names: z.boolean().default(false),
match_visible_names: t.boolean, match_usernames: z.boolean().default(false),
match_usernames: t.boolean, match_nicknames: z.boolean().default(false),
match_nicknames: t.boolean, match_custom_status: z.boolean().default(false),
match_custom_status: t.boolean, });
}),
defaultConfig: { export const MatchWordsTrigger = automodTrigger<MatchResultType>()({
case_sensitive: false, configSchema,
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,
},
async match({ pluginData, context, triggerConfig: trigger }) { async match({ pluginData, context, triggerConfig: trigger }) {
if (!context.message) { if (!context.message) {

View file

@ -1,17 +1,14 @@
import * as t from "io-ts"; import z from "zod";
import { convertDelayStringToMS, tDelayString } from "../../../utils"; import { convertDelayStringToMS, zDelayString } from "../../../utils";
import { automodTrigger } from "../helpers"; import { automodTrigger } from "../helpers";
export const MemberJoinTrigger = automodTrigger<unknown>()({ const configSchema = z.strictObject({
configType: t.type({ only_new: z.boolean().default(false),
only_new: t.boolean, new_threshold: zDelayString.default("1h"),
new_threshold: tDelayString, });
}),
defaultConfig: { export const MemberJoinTrigger = automodTrigger<unknown>()({
only_new: false, configSchema,
new_threshold: "1h",
},
async match({ context, triggerConfig }) { async match({ context, triggerConfig }) {
if (!context.joined || !context.member) { if (!context.joined || !context.member) {

View file

@ -1,18 +1,18 @@
import * as t from "io-ts"; import z from "zod";
import { convertDelayStringToMS, tDelayString } from "../../../utils"; import { convertDelayStringToMS, zDelayString } from "../../../utils";
import { RecentActionType } from "../constants"; import { RecentActionType } from "../constants";
import { findRecentSpam } from "../functions/findRecentSpam"; import { findRecentSpam } from "../functions/findRecentSpam";
import { getMatchingRecentActions } from "../functions/getMatchingRecentActions"; import { getMatchingRecentActions } from "../functions/getMatchingRecentActions";
import { sumRecentActionCounts } from "../functions/sumRecentActionCounts"; import { sumRecentActionCounts } from "../functions/sumRecentActionCounts";
import { automodTrigger } from "../helpers"; import { automodTrigger } from "../helpers";
export const MemberJoinSpamTrigger = automodTrigger<unknown>()({ const configSchema = z.strictObject({
configType: t.type({ amount: z.number().int(),
amount: t.number, within: zDelayString,
within: tDelayString, });
}),
defaultConfig: {}, export const MemberJoinSpamTrigger = automodTrigger<unknown>()({
configSchema,
async match({ pluginData, context, triggerConfig }) { async match({ pluginData, context, triggerConfig }) {
if (!context.joined || !context.member) { if (!context.joined || !context.member) {

View file

@ -1,10 +1,10 @@
import * as t from "io-ts"; import z from "zod";
import { automodTrigger } from "../helpers"; import { automodTrigger } from "../helpers";
export const MemberLeaveTrigger = automodTrigger<unknown>()({ const configSchema = z.strictObject({});
configType: t.type({}),
defaultConfig: {}, export const MemberLeaveTrigger = automodTrigger<unknown>()({
configSchema,
async match({ context }) { async match({ context }) {
if (!context.joined || !context.member) { if (!context.joined || !context.member) {

View file

@ -1,19 +1,16 @@
import * as t from "io-ts"; import z from "zod";
import { automodTrigger } from "../helpers"; import { automodTrigger } from "../helpers";
// tslint:disable-next-line:no-empty-interface // tslint:disable-next-line:no-empty-interface
interface MuteTriggerResultType {} interface MuteTriggerResultType {}
export const MuteTrigger = automodTrigger<MuteTriggerResultType>()({ const configSchema = z.strictObject({
configType: t.type({ manual: z.boolean().default(true),
manual: t.boolean, automatic: z.boolean().default(true),
automatic: t.boolean, });
}),
defaultConfig: { export const MuteTrigger = automodTrigger<MuteTriggerResultType>()({
manual: true, configSchema,
automatic: true,
},
async match({ context, triggerConfig }) { async match({ context, triggerConfig }) {
if (context.modAction?.type !== "mute") { if (context.modAction?.type !== "mute") {

View file

@ -1,12 +1,13 @@
import * as t from "io-ts"; import z from "zod";
import { automodTrigger } from "../helpers"; import { automodTrigger } from "../helpers";
// tslint:disable-next-line:no-empty-interface // tslint:disable-next-line:no-empty-interface
interface NoteTriggerResultType {} interface NoteTriggerResultType {}
const configSchema = z.strictObject({});
export const NoteTrigger = automodTrigger<NoteTriggerResultType>()({ export const NoteTrigger = automodTrigger<NoteTriggerResultType>()({
configType: t.type({}), configSchema,
defaultConfig: {},
async match({ context }) { async match({ context }) {
if (context.modAction?.type !== "note") { if (context.modAction?.type !== "note") {

View file

@ -1,6 +1,6 @@
import { Snowflake } from "discord.js"; import { Snowflake } from "discord.js";
import * as t from "io-ts"; import z from "zod";
import { renderUserUsername } from "../../../utils"; import { renderUserUsername, zSnowflake } from "../../../utils";
import { consumeIgnoredRoleChange } from "../functions/ignoredRoleChanges"; import { consumeIgnoredRoleChange } from "../functions/ignoredRoleChanges";
import { automodTrigger } from "../helpers"; import { automodTrigger } from "../helpers";
@ -8,10 +8,13 @@ interface RoleAddedMatchResult {
matchedRoleId: string; matchedRoleId: string;
} }
export const RoleAddedTrigger = automodTrigger<RoleAddedMatchResult>()({ const configSchema = z.union([
configType: t.union([t.string, t.array(t.string)]), zSnowflake,
z.array(zSnowflake).max(255),
]).default([]);
defaultConfig: "", export const RoleAddedTrigger = automodTrigger<RoleAddedMatchResult>()({
configSchema,
async match({ triggerConfig, context, pluginData }) { async match({ triggerConfig, context, pluginData }) {
if (!context.member || !context.rolesChanged || context.rolesChanged.added!.length === 0) { if (!context.member || !context.rolesChanged || context.rolesChanged.added!.length === 0) {

View file

@ -1,6 +1,6 @@
import { Snowflake } from "discord.js"; import { Snowflake } from "discord.js";
import * as t from "io-ts"; import z from "zod";
import { renderUserUsername } from "../../../utils"; import { renderUserUsername, zSnowflake } from "../../../utils";
import { consumeIgnoredRoleChange } from "../functions/ignoredRoleChanges"; import { consumeIgnoredRoleChange } from "../functions/ignoredRoleChanges";
import { automodTrigger } from "../helpers"; import { automodTrigger } from "../helpers";
@ -8,10 +8,13 @@ interface RoleAddedMatchResult {
matchedRoleId: string; matchedRoleId: string;
} }
export const RoleRemovedTrigger = automodTrigger<RoleAddedMatchResult>()({ const configSchema = z.union([
configType: t.union([t.string, t.array(t.string)]), zSnowflake,
z.array(zSnowflake).max(255),
]).default([]);
defaultConfig: "", export const RoleRemovedTrigger = automodTrigger<RoleAddedMatchResult>()({
configSchema,
async match({ triggerConfig, context, pluginData }) { async match({ triggerConfig, context, pluginData }) {
if (!context.member || !context.rolesChanged || context.rolesChanged.removed!.length === 0) { if (!context.member || !context.rolesChanged || context.rolesChanged.removed!.length === 0) {

View file

@ -1,6 +1,5 @@
import { User, escapeBold, type Snowflake } from "discord.js"; import { User, escapeBold, type Snowflake } from "discord.js";
import * as t from "io-ts"; import z from "zod";
import { tNullable } from "../../../utils";
import { automodTrigger } from "../helpers"; import { automodTrigger } from "../helpers";
interface ThreadArchiveResult { interface ThreadArchiveResult {
@ -11,12 +10,12 @@ interface ThreadArchiveResult {
matchedThreadOwner: User | undefined; matchedThreadOwner: User | undefined;
} }
export const ThreadArchiveTrigger = automodTrigger<ThreadArchiveResult>()({ const configSchema = z.strictObject({
configType: t.type({ locked: z.boolean().optional(),
locked: tNullable(t.boolean), });
}),
defaultConfig: {}, export const ThreadArchiveTrigger = automodTrigger<ThreadArchiveResult>()({
configSchema,
async match({ context, triggerConfig }) { async match({ context, triggerConfig }) {
if (!context.threadChange?.archived) { if (!context.threadChange?.archived) {

View file

@ -1,5 +1,5 @@
import { User, escapeBold, type Snowflake } from "discord.js"; import { User, escapeBold, type Snowflake } from "discord.js";
import * as t from "io-ts"; import z from "zod";
import { automodTrigger } from "../helpers"; import { automodTrigger } from "../helpers";
interface ThreadCreateResult { interface ThreadCreateResult {
@ -10,9 +10,10 @@ interface ThreadCreateResult {
matchedThreadOwner: User | undefined; matchedThreadOwner: User | undefined;
} }
const configSchema = z.strictObject({});
export const ThreadCreateTrigger = automodTrigger<ThreadCreateResult>()({ export const ThreadCreateTrigger = automodTrigger<ThreadCreateResult>()({
configType: t.type({}), configSchema,
defaultConfig: {},
async match({ context }) { async match({ context }) {
if (!context.threadChange?.created) { if (!context.threadChange?.created) {

View file

@ -1,18 +1,18 @@
import * as t from "io-ts"; import z from "zod";
import { convertDelayStringToMS, tDelayString } from "../../../utils"; import { convertDelayStringToMS, zDelayString } from "../../../utils";
import { RecentActionType } from "../constants"; import { RecentActionType } from "../constants";
import { findRecentSpam } from "../functions/findRecentSpam"; import { findRecentSpam } from "../functions/findRecentSpam";
import { getMatchingRecentActions } from "../functions/getMatchingRecentActions"; import { getMatchingRecentActions } from "../functions/getMatchingRecentActions";
import { sumRecentActionCounts } from "../functions/sumRecentActionCounts"; import { sumRecentActionCounts } from "../functions/sumRecentActionCounts";
import { automodTrigger } from "../helpers"; import { automodTrigger } from "../helpers";
export const ThreadCreateSpamTrigger = automodTrigger<unknown>()({ const configSchema = z.strictObject({
configType: t.type({ amount: z.number().int(),
amount: t.number, within: zDelayString,
within: tDelayString, });
}),
defaultConfig: {}, export const ThreadCreateSpamTrigger = automodTrigger<unknown>()({
configSchema,
async match({ pluginData, context, triggerConfig }) { async match({ pluginData, context, triggerConfig }) {
if (!context.threadChange?.created) { if (!context.threadChange?.created) {

View file

@ -1,5 +1,5 @@
import { User, escapeBold, type Snowflake } from "discord.js"; import { User, escapeBold, type Snowflake } from "discord.js";
import * as t from "io-ts"; import z from "zod";
import { automodTrigger } from "../helpers"; import { automodTrigger } from "../helpers";
interface ThreadDeleteResult { interface ThreadDeleteResult {
@ -10,9 +10,10 @@ interface ThreadDeleteResult {
matchedThreadOwner: User | undefined; matchedThreadOwner: User | undefined;
} }
const configSchema = z.strictObject({});
export const ThreadDeleteTrigger = automodTrigger<ThreadDeleteResult>()({ export const ThreadDeleteTrigger = automodTrigger<ThreadDeleteResult>()({
configType: t.type({}), configSchema,
defaultConfig: {},
async match({ context }) { async match({ context }) {
if (!context.threadChange?.deleted) { if (!context.threadChange?.deleted) {

View file

@ -1,6 +1,5 @@
import { User, escapeBold, type Snowflake } from "discord.js"; import { User, escapeBold, type Snowflake } from "discord.js";
import * as t from "io-ts"; import z from "zod";
import { tNullable } from "../../../utils";
import { automodTrigger } from "../helpers"; import { automodTrigger } from "../helpers";
interface ThreadUnarchiveResult { interface ThreadUnarchiveResult {
@ -11,12 +10,12 @@ interface ThreadUnarchiveResult {
matchedThreadOwner: User | undefined; matchedThreadOwner: User | undefined;
} }
export const ThreadUnarchiveTrigger = automodTrigger<ThreadUnarchiveResult>()({ const configSchema = z.strictObject({
configType: t.type({ locked: z.boolean().optional(),
locked: tNullable(t.boolean), });
}),
defaultConfig: {}, export const ThreadUnarchiveTrigger = automodTrigger<ThreadUnarchiveResult>()({
configSchema,
async match({ context, triggerConfig }) { async match({ context, triggerConfig }) {
if (!context.threadChange?.unarchived) { if (!context.threadChange?.unarchived) {

View file

@ -1,12 +1,13 @@
import * as t from "io-ts"; import z from "zod";
import { automodTrigger } from "../helpers"; import { automodTrigger } from "../helpers";
// tslint:disable-next-line:no-empty-interface // tslint:disable-next-line:no-empty-interface
interface UnbanTriggerResultType {} interface UnbanTriggerResultType {}
const configSchema = z.strictObject({});
export const UnbanTrigger = automodTrigger<UnbanTriggerResultType>()({ export const UnbanTrigger = automodTrigger<UnbanTriggerResultType>()({
configType: t.type({}), configSchema,
defaultConfig: {},
async match({ context }) { async match({ context }) {
if (context.modAction?.type !== "unban") { if (context.modAction?.type !== "unban") {

View file

@ -1,12 +1,13 @@
import * as t from "io-ts"; import z from "zod";
import { automodTrigger } from "../helpers"; import { automodTrigger } from "../helpers";
// tslint:disable-next-line:no-empty-interface // tslint:disable-next-line:no-empty-interface
interface UnmuteTriggerResultType {} interface UnmuteTriggerResultType {}
const configSchema = z.strictObject({});
export const UnmuteTrigger = automodTrigger<UnmuteTriggerResultType>()({ export const UnmuteTrigger = automodTrigger<UnmuteTriggerResultType>()({
configType: t.type({}), configSchema,
defaultConfig: {},
async match({ context }) { async match({ context }) {
if (context.modAction?.type !== "unmute") { if (context.modAction?.type !== "unmute") {

View file

@ -1,19 +1,16 @@
import * as t from "io-ts"; import z from "zod";
import { automodTrigger } from "../helpers"; import { automodTrigger } from "../helpers";
// tslint:disable-next-line:no-empty-interface // tslint:disable-next-line:no-empty-interface
interface WarnTriggerResultType {} interface WarnTriggerResultType {}
export const WarnTrigger = automodTrigger<WarnTriggerResultType>()({ const configSchema = z.strictObject({
configType: t.type({ manual: z.boolean().default(true),
manual: t.boolean, automatic: z.boolean().default(true),
automatic: t.boolean, });
}),
defaultConfig: { export const WarnTrigger = automodTrigger<WarnTriggerResultType>()({
manual: true, configSchema,
automatic: true,
},
async match({ context, triggerConfig }) { async match({ context, triggerConfig }) {
if (context.modAction?.type !== "warn") { if (context.modAction?.type !== "warn") {

View file

@ -1,6 +1,6 @@
import { GuildMember, GuildTextBasedChannel, PartialGuildMember, ThreadChannel, User } from "discord.js"; import { GuildMember, GuildTextBasedChannel, PartialGuildMember, ThreadChannel, User } from "discord.js";
import * as t from "io-ts";
import { BasePluginType, CooldownManager } from "knub"; import { BasePluginType, CooldownManager } from "knub";
import z from "zod";
import { Queue } from "../../Queue"; import { Queue } from "../../Queue";
import { RegExpRunner } from "../../RegExpRunner"; import { RegExpRunner } from "../../RegExpRunner";
import { GuildAntiraidLevels } from "../../data/GuildAntiraidLevels"; import { GuildAntiraidLevels } from "../../data/GuildAntiraidLevels";
@ -8,39 +8,81 @@ import { GuildArchives } from "../../data/GuildArchives";
import { GuildLogs } from "../../data/GuildLogs"; import { GuildLogs } from "../../data/GuildLogs";
import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildSavedMessages } from "../../data/GuildSavedMessages";
import { SavedMessage } from "../../data/entities/SavedMessage"; import { SavedMessage } from "../../data/entities/SavedMessage";
import { tNullable } from "../../utils"; import { entries, zBoundedRecord, zDelayString } from "../../utils";
import { CounterEvents } from "../Counters/types"; import { CounterEvents } from "../Counters/types";
import { ModActionType, ModActionsEvents } from "../ModActions/types"; import { ModActionType, ModActionsEvents } from "../ModActions/types";
import { MutesEvents } from "../Mutes/types"; import { MutesEvents } from "../Mutes/types";
import { AvailableActions } from "./actions/availableActions"; import { availableActions } from "./actions/availableActions";
import { RecentActionType } from "./constants"; import { RecentActionType } from "./constants";
import { AvailableTriggers } from "./triggers/availableTriggers"; import { availableTriggers } from "./triggers/availableTriggers";
import Timeout = NodeJS.Timeout; import Timeout = NodeJS.Timeout;
export const Rule = t.type({ export type ZTriggersMapHelper = {
enabled: t.boolean, [TriggerName in keyof typeof availableTriggers]: typeof availableTriggers[TriggerName]["configSchema"];
name: t.string, };
presets: tNullable(t.array(t.string)), const zTriggersMap = z.strictObject(entries(availableTriggers).reduce((map, [triggerName, trigger]) => {
affects_bots: t.boolean, map[triggerName] = trigger.configSchema;
affects_self: t.boolean, return map;
triggers: t.array(t.partial(AvailableTriggers.props)), }, {} as ZTriggersMapHelper)).partial();
actions: t.partial(AvailableActions.props),
cooldown: tNullable(t.string),
allow_further_rules: t.boolean,
});
export type TRule = t.TypeOf<typeof Rule>;
export const ConfigSchema = t.type({ type ZActionsMapHelper = {
rules: t.record(t.string, Rule), [ActionName in keyof typeof availableActions]: typeof availableActions[ActionName]["configSchema"];
antiraid_levels: t.array(t.string), };
can_set_antiraid: t.boolean, const zActionsMap = z.strictObject(entries(availableActions).reduce((map, [actionName, action]) => {
can_view_antiraid: t.boolean, // @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 { export interface AutomodPluginType extends BasePluginType {
config: TConfigSchema; config: z.output<typeof zAutomodConfig>;
customOverrideCriteria: { customOverrideCriteria: {
antiraid_level?: string; antiraid_level?: string;

View file

@ -3,7 +3,7 @@ import { AllowedGuilds } from "../../data/AllowedGuilds";
import { ApiPermissionAssignments } from "../../data/ApiPermissionAssignments"; import { ApiPermissionAssignments } from "../../data/ApiPermissionAssignments";
import { Configs } from "../../data/Configs"; import { Configs } from "../../data/Configs";
import { GuildArchives } from "../../data/GuildArchives"; import { GuildArchives } from "../../data/GuildArchives";
import { makeIoTsConfigParser, sendSuccessMessage } from "../../pluginUtils"; import { sendSuccessMessage } from "../../pluginUtils";
import { zeppelinGlobalPlugin } from "../ZeppelinPluginBlueprint"; import { zeppelinGlobalPlugin } from "../ZeppelinPluginBlueprint";
import { getActiveReload, resetActiveReload } from "./activeReload"; import { getActiveReload, resetActiveReload } from "./activeReload";
import { AddDashboardUserCmd } from "./commands/AddDashboardUserCmd"; import { AddDashboardUserCmd } from "./commands/AddDashboardUserCmd";
@ -22,7 +22,7 @@ import { ReloadServerCmd } from "./commands/ReloadServerCmd";
import { RemoveDashboardUserCmd } from "./commands/RemoveDashboardUserCmd"; import { RemoveDashboardUserCmd } from "./commands/RemoveDashboardUserCmd";
import { RestPerformanceCmd } from "./commands/RestPerformanceCmd"; import { RestPerformanceCmd } from "./commands/RestPerformanceCmd";
import { ServersCmd } from "./commands/ServersCmd"; import { ServersCmd } from "./commands/ServersCmd";
import { BotControlPluginType, ConfigSchema } from "./types"; import { BotControlPluginType, zBotControlConfig } from "./types";
const defaultOptions = { const defaultOptions = {
config: { config: {
@ -37,7 +37,7 @@ const defaultOptions = {
export const BotControlPlugin = zeppelinGlobalPlugin<BotControlPluginType>()({ export const BotControlPlugin = zeppelinGlobalPlugin<BotControlPluginType>()({
name: "bot_control", name: "bot_control",
configParser: makeIoTsConfigParser(ConfigSchema), configParser: (input) => zBotControlConfig.parse(input),
defaultOptions, defaultOptions,
// prettier-ignore // prettier-ignore

View file

@ -1,23 +1,22 @@
import * as t from "io-ts";
import { BasePluginType, globalPluginEventListener, globalPluginMessageCommand } from "knub"; import { BasePluginType, globalPluginEventListener, globalPluginMessageCommand } from "knub";
import z from "zod";
import { AllowedGuilds } from "../../data/AllowedGuilds"; import { AllowedGuilds } from "../../data/AllowedGuilds";
import { ApiPermissionAssignments } from "../../data/ApiPermissionAssignments"; import { ApiPermissionAssignments } from "../../data/ApiPermissionAssignments";
import { Configs } from "../../data/Configs"; import { Configs } from "../../data/Configs";
import { GuildArchives } from "../../data/GuildArchives"; import { GuildArchives } from "../../data/GuildArchives";
import { tNullable } from "../../utils"; import { zBoundedCharacters } from "../../utils";
export const ConfigSchema = t.type({ export const zBotControlConfig = z.strictObject({
can_use: t.boolean, can_use: z.boolean(),
can_eligible: t.boolean, can_eligible: z.boolean(),
can_performance: t.boolean, can_performance: z.boolean(),
can_add_server_from_invite: t.boolean, can_add_server_from_invite: z.boolean(),
can_list_dashboard_perms: t.boolean, can_list_dashboard_perms: z.boolean(),
update_cmd: tNullable(t.string), update_cmd: zBoundedCharacters(0, 2000).nullable(),
}); });
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
export interface BotControlPluginType extends BasePluginType { export interface BotControlPluginType extends BasePluginType {
config: TConfigSchema; config: z.output<typeof zBotControlConfig>;
state: { state: {
archives: GuildArchives; archives: GuildArchives;
allowedGuilds: AllowedGuilds; allowedGuilds: AllowedGuilds;

View file

@ -3,7 +3,7 @@ import { Case } from "../../data/entities/Case";
import { GuildArchives } from "../../data/GuildArchives"; import { GuildArchives } from "../../data/GuildArchives";
import { GuildCases } from "../../data/GuildCases"; import { GuildCases } from "../../data/GuildCases";
import { GuildLogs } from "../../data/GuildLogs"; import { GuildLogs } from "../../data/GuildLogs";
import { makeIoTsConfigParser, mapToPublicFn } from "../../pluginUtils"; import { mapToPublicFn } from "../../pluginUtils";
import { trimPluginDescription } from "../../utils"; import { trimPluginDescription } from "../../utils";
import { InternalPosterPlugin } from "../InternalPoster/InternalPosterPlugin"; import { InternalPosterPlugin } from "../InternalPoster/InternalPosterPlugin";
import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin"; import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin";
@ -16,7 +16,7 @@ import { getCaseTypeAmountForUserId } from "./functions/getCaseTypeAmountForUser
import { getRecentCasesByMod } from "./functions/getRecentCasesByMod"; import { getRecentCasesByMod } from "./functions/getRecentCasesByMod";
import { getTotalCasesByMod } from "./functions/getTotalCasesByMod"; import { getTotalCasesByMod } from "./functions/getTotalCasesByMod";
import { postCaseToCaseLogChannel } from "./functions/postToCaseLogChannel"; import { postCaseToCaseLogChannel } from "./functions/postToCaseLogChannel";
import { CaseArgs, CaseNoteArgs, CasesPluginType, ConfigSchema } from "./types"; import { CaseArgs, CaseNoteArgs, CasesPluginType, zCasesConfig } from "./types";
// The `any` cast here is to prevent TypeScript from locking up from the circular dependency // The `any` cast here is to prevent TypeScript from locking up from the circular dependency
function getLogsPlugin(): Promise<any> { function getLogsPlugin(): Promise<any> {
@ -42,11 +42,11 @@ export const CasesPlugin = zeppelinGuildPlugin<CasesPluginType>()({
description: trimPluginDescription(` description: trimPluginDescription(`
This plugin contains basic configuration for cases created by other plugins This plugin contains basic configuration for cases created by other plugins
`), `),
configSchema: ConfigSchema, configSchema: zCasesConfig,
}, },
dependencies: async () => [TimeAndDatePlugin, InternalPosterPlugin, (await getLogsPlugin()).LogsPlugin], dependencies: async () => [TimeAndDatePlugin, InternalPosterPlugin, (await getLogsPlugin()).LogsPlugin],
configParser: makeIoTsConfigParser(ConfigSchema), configParser: (input) => zCasesConfig.parse(input),
defaultOptions, defaultOptions,
public: { public: {

View file

@ -1,24 +1,26 @@
import * as t from "io-ts";
import { BasePluginType } from "knub"; import { BasePluginType } from "knub";
import { U } from "ts-toolbelt";
import z from "zod";
import { CaseNameToType, CaseTypes } from "../../data/CaseTypes"; import { CaseNameToType, CaseTypes } from "../../data/CaseTypes";
import { GuildArchives } from "../../data/GuildArchives"; import { GuildArchives } from "../../data/GuildArchives";
import { GuildCases } from "../../data/GuildCases"; import { GuildCases } from "../../data/GuildCases";
import { GuildLogs } from "../../data/GuildLogs"; import { GuildLogs } from "../../data/GuildLogs";
import { tDelayString, tNullable, tPartialDictionary } from "../../utils"; import { keys, zBoundedCharacters, zDelayString, zSnowflake } from "../../utils";
import { tColor } from "../../utils/tColor"; import { zColor } from "../../utils/zColor";
export const ConfigSchema = t.type({ const caseKeys = keys(CaseNameToType) as U.ListOf<keyof typeof CaseNameToType>;
log_automatic_actions: t.boolean,
case_log_channel: tNullable(t.string), export const zCasesConfig = z.strictObject({
show_relative_times: t.boolean, log_automatic_actions: z.boolean(),
relative_time_cutoff: tDelayString, case_log_channel: zSnowflake.nullable(),
case_colors: tNullable(tPartialDictionary(t.keyof(CaseNameToType), tColor)), show_relative_times: z.boolean(),
case_icons: tNullable(tPartialDictionary(t.keyof(CaseNameToType), t.string)), relative_time_cutoff: zDelayString.default("1w"),
case_colors: z.record(z.enum(caseKeys), zColor).nullable(),
case_icons: z.record(z.enum(caseKeys), zBoundedCharacters(0, 32)).nullable(),
}); });
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
export interface CasesPluginType extends BasePluginType { export interface CasesPluginType extends BasePluginType {
config: TConfigSchema; config: z.infer<typeof zCasesConfig>;
state: { state: {
logs: GuildLogs; logs: GuildLogs;
cases: GuildCases; cases: GuildCases;

View file

@ -1,12 +1,11 @@
import { PluginOptions } from "knub"; import { PluginOptions } from "knub";
import { GuildLogs } from "../../data/GuildLogs"; import { GuildLogs } from "../../data/GuildLogs";
import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildSavedMessages } from "../../data/GuildSavedMessages";
import { makeIoTsConfigParser } from "../../pluginUtils";
import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners"; import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners";
import { trimPluginDescription } from "../../utils"; import { trimPluginDescription } from "../../utils";
import { LogsPlugin } from "../Logs/LogsPlugin"; import { LogsPlugin } from "../Logs/LogsPlugin";
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
import { CensorPluginType, ConfigSchema } from "./types"; import { CensorPluginType, zCensorConfig } from "./types";
import { onMessageCreate } from "./util/onMessageCreate"; import { onMessageCreate } from "./util/onMessageCreate";
import { onMessageUpdate } from "./util/onMessageUpdate"; import { onMessageUpdate } from "./util/onMessageUpdate";
@ -54,11 +53,11 @@ export const CensorPlugin = zeppelinGuildPlugin<CensorPluginType>()({
For more advanced filtering, check out the Automod plugin! For more advanced filtering, check out the Automod plugin!
`), `),
legacy: true, legacy: true,
configSchema: ConfigSchema, configSchema: zCensorConfig,
}, },
dependencies: () => [LogsPlugin], dependencies: () => [LogsPlugin],
configParser: makeIoTsConfigParser(ConfigSchema), configParser: (input) => zCensorConfig.parse(input),
defaultOptions, defaultOptions,
beforeLoad(pluginData) { beforeLoad(pluginData) {

View file

@ -1,30 +1,28 @@
import * as t from "io-ts";
import { BasePluginType } from "knub"; import { BasePluginType } from "knub";
import z from "zod";
import { RegExpRunner } from "../../RegExpRunner"; import { RegExpRunner } from "../../RegExpRunner";
import { GuildLogs } from "../../data/GuildLogs"; import { GuildLogs } from "../../data/GuildLogs";
import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildSavedMessages } from "../../data/GuildSavedMessages";
import { tNullable } from "../../utils"; import { zBoundedCharacters, zRegex, zSnowflake } from "../../utils";
import { TRegex } from "../../validatorUtils";
export const ConfigSchema = t.type({ export const zCensorConfig = z.strictObject({
filter_zalgo: t.boolean, filter_zalgo: z.boolean(),
filter_invites: t.boolean, filter_invites: z.boolean(),
invite_guild_whitelist: tNullable(t.array(t.string)), invite_guild_whitelist: z.array(zSnowflake).nullable(),
invite_guild_blacklist: tNullable(t.array(t.string)), invite_guild_blacklist: z.array(zSnowflake).nullable(),
invite_code_whitelist: tNullable(t.array(t.string)), invite_code_whitelist: z.array(zBoundedCharacters(0, 16)).nullable(),
invite_code_blacklist: tNullable(t.array(t.string)), invite_code_blacklist: z.array(zBoundedCharacters(0, 16)).nullable(),
allow_group_dm_invites: t.boolean, allow_group_dm_invites: z.boolean(),
filter_domains: t.boolean, filter_domains: z.boolean(),
domain_whitelist: tNullable(t.array(t.string)), domain_whitelist: z.array(zBoundedCharacters(0, 255)).nullable(),
domain_blacklist: tNullable(t.array(t.string)), domain_blacklist: z.array(zBoundedCharacters(0, 255)).nullable(),
blocked_tokens: tNullable(t.array(t.string)), blocked_tokens: z.array(zBoundedCharacters(0, 2000)).nullable(),
blocked_words: tNullable(t.array(t.string)), blocked_words: z.array(zBoundedCharacters(0, 2000)).nullable(),
blocked_regex: tNullable(t.array(TRegex)), blocked_regex: z.array(zRegex(z.string().max(1000))).nullable(),
}); });
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
export interface CensorPluginType extends BasePluginType { export interface CensorPluginType extends BasePluginType {
config: TConfigSchema; config: z.infer<typeof zCensorConfig>;
state: { state: {
serverLogs: GuildLogs; serverLogs: GuildLogs;
savedMessages: GuildSavedMessages; savedMessages: GuildSavedMessages;

View file

@ -1,18 +1,15 @@
import * as t from "io-ts"; import z from "zod";
import { makeIoTsConfigParser } from "../../pluginUtils";
import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin"; import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin";
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
import { ArchiveChannelCmd } from "./commands/ArchiveChannelCmd"; import { ArchiveChannelCmd } from "./commands/ArchiveChannelCmd";
import { ChannelArchiverPluginType } from "./types"; import { ChannelArchiverPluginType } from "./types";
const ConfigSchema = t.type({});
export const ChannelArchiverPlugin = zeppelinGuildPlugin<ChannelArchiverPluginType>()({ export const ChannelArchiverPlugin = zeppelinGuildPlugin<ChannelArchiverPluginType>()({
name: "channel_archiver", name: "channel_archiver",
showInDocs: false, showInDocs: false,
dependencies: () => [TimeAndDatePlugin], dependencies: () => [TimeAndDatePlugin],
configParser: makeIoTsConfigParser(ConfigSchema), configParser: (input) => z.strictObject({}).parse(input),
// prettier-ignore // prettier-ignore
messageCommands: [ messageCommands: [

View file

@ -1,11 +1,10 @@
import { CooldownManager } from "knub"; import { CooldownManager } from "knub";
import { GuildLogs } from "../../data/GuildLogs"; import { GuildLogs } from "../../data/GuildLogs";
import { makeIoTsConfigParser } from "../../pluginUtils";
import { trimPluginDescription } from "../../utils"; import { trimPluginDescription } from "../../utils";
import { LogsPlugin } from "../Logs/LogsPlugin"; import { LogsPlugin } from "../Logs/LogsPlugin";
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
import { VoiceStateUpdateEvt } from "./events/VoiceStateUpdateEvt"; import { VoiceStateUpdateEvt } from "./events/VoiceStateUpdateEvt";
import { CompanionChannelsPluginType, ConfigSchema } from "./types"; import { CompanionChannelsPluginType, zCompanionChannelsConfig } from "./types";
const defaultOptions = { const defaultOptions = {
config: { config: {
@ -23,11 +22,11 @@ export const CompanionChannelsPlugin = zeppelinGuildPlugin<CompanionChannelsPlug
Once set up, any time a user joins one of the specified voice channels, Once set up, any time a user joins one of the specified voice channels,
they'll get channel permissions applied to them for the text channels. they'll get channel permissions applied to them for the text channels.
`), `),
configSchema: ConfigSchema, configSchema: zCompanionChannelsConfig,
}, },
dependencies: () => [LogsPlugin], dependencies: () => [LogsPlugin],
configParser: makeIoTsConfigParser(ConfigSchema), configParser: (input) => zCompanionChannelsConfig.parse(input),
defaultOptions, defaultOptions,
events: [VoiceStateUpdateEvt], events: [VoiceStateUpdateEvt],

View file

@ -1,28 +1,23 @@
import * as t from "io-ts";
import { BasePluginType, CooldownManager, guildPluginEventListener } from "knub"; import { BasePluginType, CooldownManager, guildPluginEventListener } from "knub";
import z from "zod";
import { GuildLogs } from "../../data/GuildLogs"; import { GuildLogs } from "../../data/GuildLogs";
import { tNullable } from "../../utils"; import { zBoundedCharacters, zSnowflake } from "../../utils";
// Permissions using these numbers: https://abal.moe/Eris/docs/reference (add all allowed/denied ones up) export const zCompanionChannelOpts = z.strictObject({
export const CompanionChannelOpts = t.type({ voice_channel_ids: z.array(zSnowflake),
voice_channel_ids: t.array(t.string), text_channel_ids: z.array(zSnowflake),
text_channel_ids: t.array(t.string), // See https://discord.com/developers/docs/topics/permissions#permissions-bitwise-permission-flags
permissions: t.number, permissions: z.number(),
enabled: tNullable(t.boolean), enabled: z.boolean().nullable().default(true),
}); });
export type TCompanionChannelOpts = t.TypeOf<typeof CompanionChannelOpts>; export type TCompanionChannelOpts = z.infer<typeof zCompanionChannelOpts>;
export const ConfigSchema = t.type({ export const zCompanionChannelsConfig = z.strictObject({
entries: t.record(t.string, CompanionChannelOpts), entries: z.record(zBoundedCharacters(0, 100), zCompanionChannelOpts),
}); });
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
export interface ICompanionChannelMap {
[channelId: string]: TCompanionChannelOpts;
}
export interface CompanionChannelsPluginType extends BasePluginType { export interface CompanionChannelsPluginType extends BasePluginType {
config: TConfigSchema; config: z.infer<typeof zCompanionChannelsConfig>;
state: { state: {
errorCooldownManager: CooldownManager; errorCooldownManager: CooldownManager;
serverLogs: GuildLogs; serverLogs: GuildLogs;

View file

@ -1,12 +1,11 @@
import { PluginOptions } from "knub"; import { PluginOptions } from "knub";
import { GuildContextMenuLinks } from "../../data/GuildContextMenuLinks"; import { GuildContextMenuLinks } from "../../data/GuildContextMenuLinks";
import { makeIoTsConfigParser } from "../../pluginUtils";
import { LogsPlugin } from "../Logs/LogsPlugin"; import { LogsPlugin } from "../Logs/LogsPlugin";
import { MutesPlugin } from "../Mutes/MutesPlugin"; import { MutesPlugin } from "../Mutes/MutesPlugin";
import { UtilityPlugin } from "../Utility/UtilityPlugin"; import { UtilityPlugin } from "../Utility/UtilityPlugin";
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
import { ContextClickedEvt } from "./events/ContextClickedEvt"; import { ContextClickedEvt } from "./events/ContextClickedEvt";
import { ConfigSchema, ContextMenuPluginType } from "./types"; import { ContextMenuPluginType, zContextMenusConfig } from "./types";
import { loadAllCommands } from "./utils/loadAllCommands"; import { loadAllCommands } from "./utils/loadAllCommands";
const defaultOptions: PluginOptions<ContextMenuPluginType> = { const defaultOptions: PluginOptions<ContextMenuPluginType> = {
@ -37,7 +36,7 @@ export const ContextMenuPlugin = zeppelinGuildPlugin<ContextMenuPluginType>()({
showInDocs: false, showInDocs: false,
dependencies: () => [MutesPlugin, LogsPlugin, UtilityPlugin], dependencies: () => [MutesPlugin, LogsPlugin, UtilityPlugin],
configParser: makeIoTsConfigParser(ConfigSchema), configParser: (input) => zContextMenusConfig.parse(input),
defaultOptions, defaultOptions,
// prettier-ignore // prettier-ignore

View file

@ -45,9 +45,9 @@ export async function muteAction(
try { try {
const result = await mutes.muteUser(userId, durationMs, "Context Menu Action", { caseArgs }); const result = await mutes.muteUser(userId, durationMs, "Context Menu Action", { caseArgs });
const muteMessage = `Muted **${result.case.user_name}** ${ const muteMessage = `Muted **${result.case!.user_name}** ${
durationMs ? `for ${humanizeDuration(durationMs)}` : "indefinitely" durationMs ? `for ${humanizeDuration(durationMs)}` : "indefinitely"
} (Case #${result.case.case_number}) (user notified via ${ } (Case #${result.case!.case_number}) (user notified via ${
result.notifyResult.method ?? "dm" result.notifyResult.method ?? "dm"
})\nPlease update the new case with the \`update\` command`; })\nPlease update the new case with the \`update\` command`;

View file

@ -1,22 +1,20 @@
import * as t from "io-ts";
import { BasePluginType, guildPluginEventListener } from "knub"; import { BasePluginType, guildPluginEventListener } from "knub";
import z from "zod";
import { GuildContextMenuLinks } from "../../data/GuildContextMenuLinks"; import { GuildContextMenuLinks } from "../../data/GuildContextMenuLinks";
export const ConfigSchema = t.type({ export const zContextMenusConfig = z.strictObject({
can_use: t.boolean, can_use: z.boolean(),
user_muteindef: z.boolean(),
user_muteindef: t.boolean, user_mute1d: z.boolean(),
user_mute1d: t.boolean, user_mute1h: z.boolean(),
user_mute1h: t.boolean, user_info: z.boolean(),
user_info: t.boolean, message_clean10: z.boolean(),
message_clean10: t.boolean, message_clean25: z.boolean(),
message_clean25: t.boolean, message_clean50: z.boolean(),
message_clean50: t.boolean,
}); });
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
export interface ContextMenuPluginType extends BasePluginType { export interface ContextMenuPluginType extends BasePluginType {
config: TConfigSchema; config: z.infer<typeof zContextMenusConfig>;
state: { state: {
contextMenuLinks: GuildContextMenuLinks; contextMenuLinks: GuildContextMenuLinks;
}; };

View file

@ -1,15 +1,12 @@
import { EventEmitter } from "events"; import { EventEmitter } from "events";
import { PluginOptions } from "knub"; import { PluginOptions } from "knub";
import {
buildCounterConditionString,
CounterTrigger,
getReverseCounterComparisonOp,
parseCounterConditionString,
} from "../../data/entities/CounterTrigger";
import { GuildCounters } from "../../data/GuildCounters"; import { GuildCounters } from "../../data/GuildCounters";
import {
CounterTrigger,
parseCounterConditionString
} from "../../data/entities/CounterTrigger";
import { mapToPublicFn } from "../../pluginUtils"; import { mapToPublicFn } from "../../pluginUtils";
import { convertDelayStringToMS, MINUTES } from "../../utils"; import { MINUTES, convertDelayStringToMS, values } from "../../utils";
import { parseIoTsSchema, StrictValidationError } from "../../validatorUtils";
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
import { AddCounterCmd } from "./commands/AddCounterCmd"; import { AddCounterCmd } from "./commands/AddCounterCmd";
import { CountersListCmd } from "./commands/CountersListCmd"; import { CountersListCmd } from "./commands/CountersListCmd";
@ -25,10 +22,8 @@ import { getPrettyNameForCounterTrigger } from "./functions/getPrettyNameForCoun
import { offCounterEvent } from "./functions/offCounterEvent"; import { offCounterEvent } from "./functions/offCounterEvent";
import { onCounterEvent } from "./functions/onCounterEvent"; import { onCounterEvent } from "./functions/onCounterEvent";
import { setCounterValue } from "./functions/setCounterValue"; import { setCounterValue } from "./functions/setCounterValue";
import { ConfigSchema, CountersPluginType, TTrigger } from "./types"; import { CountersPluginType, zCountersConfig } from "./types";
const MAX_COUNTERS = 5;
const MAX_TRIGGERS_PER_COUNTER = 5;
const DECAY_APPLY_INTERVAL = 5 * MINUTES; const DECAY_APPLY_INTERVAL = 5 * MINUTES;
const defaultOptions: PluginOptions<CountersPluginType> = { const defaultOptions: PluginOptions<CountersPluginType> = {
@ -72,50 +67,12 @@ export const CountersPlugin = zeppelinGuildPlugin<CountersPluginType>()({
description: description:
"Keep track of per-user, per-channel, or global numbers and trigger specific actions based on this number", "Keep track of per-user, per-channel, or global numbers and trigger specific actions based on this number",
configurationGuide: "See <a href='/docs/setup-guides/counters'>Counters setup guide</a>", configurationGuide: "See <a href='/docs/setup-guides/counters'>Counters setup guide</a>",
configSchema: ConfigSchema, configSchema: zCountersConfig,
}, },
defaultOptions, defaultOptions,
// TODO: Separate input and output types // TODO: Separate input and output types
configParser: (input) => { configParser: (input) => zCountersConfig.parse(input),
for (const [counterName, counter] of Object.entries<any>((input as any).counters || {})) {
counter.name = counterName;
counter.per_user = counter.per_user ?? false;
counter.per_channel = counter.per_channel ?? false;
counter.initial_value = counter.initial_value ?? 0;
counter.triggers = counter.triggers || {};
if (Object.values(counter.triggers).length > MAX_TRIGGERS_PER_COUNTER) {
throw new StrictValidationError([`You can only have at most ${MAX_TRIGGERS_PER_COUNTER} triggers per counter`]);
}
// Normalize triggers
for (const [triggerName, trigger] of Object.entries(counter.triggers)) {
const triggerObj = (typeof trigger === "string" ? { condition: trigger } : trigger) as Partial<TTrigger>;
triggerObj.name = triggerName;
const parsedCondition = parseCounterConditionString(triggerObj.condition || "");
if (!parsedCondition) {
throw new StrictValidationError([
`Invalid comparison in counter trigger ${counterName}/${triggerName}: "${triggerObj.condition}"`,
]);
}
triggerObj.condition = buildCounterConditionString(parsedCondition[0], parsedCondition[1]);
triggerObj.reverse_condition =
triggerObj.reverse_condition ||
buildCounterConditionString(getReverseCounterComparisonOp(parsedCondition[0]), parsedCondition[1]);
counter.triggers[triggerName] = triggerObj as TTrigger;
}
}
if (Object.values((input as any).counters || {}).length > MAX_COUNTERS) {
throw new StrictValidationError([`You can only have at most ${MAX_COUNTERS} counters`]);
}
return parseIoTsSchema(ConfigSchema, input);
},
public: { public: {
counterExists: mapToPublicFn(counterExists), counterExists: mapToPublicFn(counterExists),
@ -163,13 +120,12 @@ export const CountersPlugin = zeppelinGuildPlugin<CountersPluginType>()({
state.counterTriggersByCounterId.set(dbCounter.id, thisCounterTriggers); state.counterTriggersByCounterId.set(dbCounter.id, thisCounterTriggers);
// Initialize triggers // Initialize triggers
for (const trigger of Object.values(counter.triggers)) { for (const trigger of values(counter.triggers)) {
const theTrigger = trigger as TTrigger; const parsedCondition = parseCounterConditionString(trigger.condition)!;
const parsedCondition = parseCounterConditionString(theTrigger.condition)!; const parsedReverseCondition = parseCounterConditionString(trigger.reverse_condition)!;
const parsedReverseCondition = parseCounterConditionString(theTrigger.reverse_condition)!;
const counterTrigger = await state.counters.initCounterTrigger( const counterTrigger = await state.counters.initCounterTrigger(
dbCounter.id, dbCounter.id,
theTrigger.name, trigger.name,
parsedCondition[0], parsedCondition[0],
parsedCondition[1], parsedCondition[1],
parsedReverseCondition[0], parsedReverseCondition[0],

View file

@ -1,5 +1,5 @@
import { GuildPluginData } from "knub"; import { GuildPluginData } from "knub";
import { CountersPluginType, TTrigger } from "../types"; import { CountersPluginType } from "../types";
export function getPrettyNameForCounterTrigger( export function getPrettyNameForCounterTrigger(
pluginData: GuildPluginData<CountersPluginType>, pluginData: GuildPluginData<CountersPluginType>,
@ -12,6 +12,6 @@ export function getPrettyNameForCounterTrigger(
return "Unknown Counter Trigger"; return "Unknown Counter Trigger";
} }
const trigger = counter.triggers[triggerName] as TTrigger | undefined; const trigger = counter.triggers[triggerName];
return trigger ? trigger.pretty_name || trigger.name : "Unknown Counter Trigger"; return trigger ? trigger.pretty_name || trigger.name : "Unknown Counter Trigger";
} }

View file

@ -1,45 +1,98 @@
import { EventEmitter } from "events"; import { EventEmitter } from "events";
import * as t from "io-ts";
import { BasePluginType } from "knub"; import { BasePluginType } from "knub";
import z from "zod";
import { GuildCounters } from "../../data/GuildCounters"; import { GuildCounters } from "../../data/GuildCounters";
import { CounterTrigger } from "../../data/entities/CounterTrigger"; import { CounterTrigger, buildCounterConditionString, getReverseCounterComparisonOp, parseCounterConditionString } from "../../data/entities/CounterTrigger";
import { tDelayString, tNullable } from "../../utils"; import { zBoundedCharacters, zBoundedRecord, zDelayString } from "../../utils";
import Timeout = NodeJS.Timeout; import Timeout = NodeJS.Timeout;
export const Trigger = t.type({ const MAX_COUNTERS = 5;
name: t.string, const MAX_TRIGGERS_PER_COUNTER = 5;
pretty_name: tNullable(t.string),
condition: t.string,
reverse_condition: t.string,
});
export type TTrigger = t.TypeOf<typeof Trigger>;
export const Counter = t.type({ export const zTrigger = z.strictObject({
name: t.string, // Dummy type because name gets replaced by the property key in zTriggerInput
pretty_name: tNullable(t.string), name: z.never().optional().transform(() => ""),
per_channel: t.boolean, pretty_name: zBoundedCharacters(0, 100).nullable().default(null),
per_user: t.boolean, condition: zBoundedCharacters(1, 64).refine(
initial_value: t.number, (str) => parseCounterConditionString(str) !== null,
triggers: t.record(t.string, t.union([t.string, Trigger])), { message: "Invalid counter trigger condition" },
decay: tNullable( ),
t.type({ reverse_condition: zBoundedCharacters(1, 64).refine(
amount: t.number, (str) => parseCounterConditionString(str) !== null,
every: tDelayString, { message: "Invalid counter trigger reverse condition" },
}),
), ),
can_view: tNullable(t.boolean),
can_edit: tNullable(t.boolean),
can_reset_all: tNullable(t.boolean),
}); });
export type TCounter = t.TypeOf<typeof Counter>;
export const ConfigSchema = t.type({ const zTriggerInput = z.union([zBoundedCharacters(0, 100), zTrigger])
counters: t.record(t.string, Counter), .transform((val, ctx) => {
can_view: t.boolean, const ruleName = String(ctx.path[ctx.path.length - 2]).trim();
can_edit: t.boolean, if (typeof val === "string") {
can_reset_all: t.boolean, const parsedCondition = parseCounterConditionString(val);
if (!parsedCondition) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Invalid counter trigger condition",
});
return z.NEVER;
}
return {
name: ruleName,
pretty_name: null,
condition: buildCounterConditionString(parsedCondition[0], parsedCondition[1]),
reverse_condition: buildCounterConditionString(getReverseCounterComparisonOp(parsedCondition[0]), parsedCondition[1]),
};
}
return {
...val,
name: ruleName,
};
});
export const zCounter = z.strictObject({
// 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: "Counters must have names",
});
return z.NEVER;
}
return ruleName;
}),
pretty_name: zBoundedCharacters(0, 100).nullable().default(null),
per_channel: z.boolean().default(false),
per_user: z.boolean().default(false),
initial_value: z.number().default(0),
triggers: zBoundedRecord(
z.record(
zBoundedCharacters(0, 100),
zTriggerInput,
),
1,
MAX_TRIGGERS_PER_COUNTER,
),
decay: z.strictObject({
amount: z.number(),
every: zDelayString,
}).nullable().default(null),
can_view: z.boolean(),
can_edit: z.boolean(),
can_reset_all: z.boolean(),
});
export const zCountersConfig = z.strictObject({
counters: zBoundedRecord(
z.record(zBoundedCharacters(0, 100), zCounter),
0,
MAX_COUNTERS,
),
can_view: z.boolean(),
can_edit: z.boolean(),
can_reset_all: z.boolean(),
}); });
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
export interface CounterEvents { export interface CounterEvents {
trigger: (counterName: string, triggerName: string, channelId: string | null, userId: string | null) => void; trigger: (counterName: string, triggerName: string, channelId: string | null, userId: string | null) => void;
@ -52,7 +105,7 @@ export interface CounterEventEmitter extends EventEmitter {
} }
export interface CountersPluginType extends BasePluginType { export interface CountersPluginType extends BasePluginType {
config: TConfigSchema; config: z.infer<typeof zCountersConfig>;
state: { state: {
counters: GuildCounters; counters: GuildCounters;
counterIds: Record<string, number>; counterIds: Record<string, number>;

View file

@ -2,7 +2,6 @@ import { GuildChannel, GuildMember, User } from "discord.js";
import { guildPluginMessageCommand, parseSignature } from "knub"; import { guildPluginMessageCommand, parseSignature } from "knub";
import { TSignature } from "knub-command-manager"; import { TSignature } from "knub-command-manager";
import { commandTypes } from "../../commandTypes"; import { commandTypes } from "../../commandTypes";
import { makeIoTsConfigParser } from "../../pluginUtils";
import { TemplateSafeValueContainer, createTypedTemplateSafeValueContainer } from "../../templateFormatter"; import { TemplateSafeValueContainer, createTypedTemplateSafeValueContainer } from "../../templateFormatter";
import { UnknownUser } from "../../utils"; import { UnknownUser } from "../../utils";
import { isScalar } from "../../utils/isScalar"; import { isScalar } from "../../utils/isScalar";
@ -14,7 +13,7 @@ import {
} from "../../utils/templateSafeObjects"; } from "../../utils/templateSafeObjects";
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
import { runEvent } from "./functions/runEvent"; import { runEvent } from "./functions/runEvent";
import { ConfigSchema, CustomEventsPluginType } from "./types"; import { CustomEventsPluginType, zCustomEventsConfig } from "./types";
const defaultOptions = { const defaultOptions = {
config: { config: {
@ -26,7 +25,7 @@ export const CustomEventsPlugin = zeppelinGuildPlugin<CustomEventsPluginType>()(
name: "custom_events", name: "custom_events",
showInDocs: false, showInDocs: false,
configParser: makeIoTsConfigParser(ConfigSchema), configParser: (input) => zCustomEventsConfig.parse(input),
defaultOptions, defaultOptions,
afterLoad(pluginData) { afterLoad(pluginData) {

View file

@ -1,17 +1,17 @@
import * as t from "io-ts";
import { GuildPluginData } from "knub"; import { GuildPluginData } from "knub";
import z from "zod";
import { canActOn } from "../../../pluginUtils"; import { canActOn } from "../../../pluginUtils";
import { renderTemplate, TemplateSafeValueContainer } from "../../../templateFormatter"; import { renderTemplate, TemplateSafeValueContainer } from "../../../templateFormatter";
import { resolveMember } from "../../../utils"; import { resolveMember, zSnowflake } from "../../../utils";
import { ActionError } from "../ActionError"; import { ActionError } from "../ActionError";
import { CustomEventsPluginType, TCustomEvent } from "../types"; import { CustomEventsPluginType, TCustomEvent } from "../types";
export const AddRoleAction = t.type({ export const zAddRoleAction = z.strictObject({
type: t.literal("add_role"), type: z.literal("add_role"),
target: t.string, target: zSnowflake,
role: t.union([t.string, t.array(t.string)]), role: z.union([zSnowflake, z.array(zSnowflake)]),
}); });
export type TAddRoleAction = t.TypeOf<typeof AddRoleAction>; export type TAddRoleAction = z.infer<typeof zAddRoleAction>;
export async function addRoleAction( export async function addRoleAction(
pluginData: GuildPluginData<CustomEventsPluginType>, pluginData: GuildPluginData<CustomEventsPluginType>,

View file

@ -1,19 +1,20 @@
import * as t from "io-ts";
import { GuildPluginData } from "knub"; import { GuildPluginData } from "knub";
import z from "zod";
import { CaseTypes } from "../../../data/CaseTypes"; import { CaseTypes } from "../../../data/CaseTypes";
import { renderTemplate, TemplateSafeValueContainer } from "../../../templateFormatter"; import { renderTemplate, TemplateSafeValueContainer } from "../../../templateFormatter";
import { zBoundedCharacters, zSnowflake } from "../../../utils";
import { CasesPlugin } from "../../Cases/CasesPlugin"; import { CasesPlugin } from "../../Cases/CasesPlugin";
import { ActionError } from "../ActionError"; import { ActionError } from "../ActionError";
import { CustomEventsPluginType, TCustomEvent } from "../types"; import { CustomEventsPluginType, TCustomEvent } from "../types";
export const CreateCaseAction = t.type({ export const zCreateCaseAction = z.strictObject({
type: t.literal("create_case"), type: z.literal("create_case"),
case_type: t.string, case_type: zBoundedCharacters(0, 32),
mod: t.string, mod: zSnowflake,
target: t.string, target: zSnowflake,
reason: t.string, reason: zBoundedCharacters(0, 4000),
}); });
export type TCreateCaseAction = t.TypeOf<typeof CreateCaseAction>; export type TCreateCaseAction = z.infer<typeof zCreateCaseAction>;
export async function createCaseAction( export async function createCaseAction(
pluginData: GuildPluginData<CustomEventsPluginType>, pluginData: GuildPluginData<CustomEventsPluginType>,
@ -32,7 +33,7 @@ export async function createCaseAction(
} }
const casesPlugin = pluginData.getPlugin(CasesPlugin); const casesPlugin = pluginData.getPlugin(CasesPlugin);
await casesPlugin.createCase({ await casesPlugin!.createCase({
userId: targetId, userId: targetId,
modId, modId,
type: CaseTypes[action.case_type], type: CaseTypes[action.case_type],

View file

@ -1,17 +1,17 @@
import { Snowflake } from "discord.js"; import { Snowflake } from "discord.js";
import * as t from "io-ts";
import { GuildPluginData } from "knub"; import { GuildPluginData } from "knub";
import z from "zod";
import { TemplateSafeValueContainer } from "../../../templateFormatter"; import { TemplateSafeValueContainer } from "../../../templateFormatter";
import { convertDelayStringToMS, noop, tDelayString } from "../../../utils"; import { convertDelayStringToMS, noop, zDelayString, zSnowflake } from "../../../utils";
import { ActionError } from "../ActionError"; import { ActionError } from "../ActionError";
import { CustomEventsPluginType, TCustomEvent } from "../types"; import { CustomEventsPluginType, TCustomEvent } from "../types";
export const MakeRoleMentionableAction = t.type({ export const zMakeRoleMentionableAction = z.strictObject({
type: t.literal("make_role_mentionable"), type: z.literal("make_role_mentionable"),
role: t.string, role: zSnowflake,
timeout: tDelayString, timeout: zDelayString,
}); });
export type TMakeRoleMentionableAction = t.TypeOf<typeof MakeRoleMentionableAction>; export type TMakeRoleMentionableAction = z.infer<typeof zMakeRoleMentionableAction>;
export async function makeRoleMentionableAction( export async function makeRoleMentionableAction(
pluginData: GuildPluginData<CustomEventsPluginType>, pluginData: GuildPluginData<CustomEventsPluginType>,

View file

@ -1,15 +1,16 @@
import { Snowflake } from "discord.js"; import { Snowflake } from "discord.js";
import * as t from "io-ts";
import { GuildPluginData } from "knub"; import { GuildPluginData } from "knub";
import z from "zod";
import { TemplateSafeValueContainer } from "../../../templateFormatter"; import { TemplateSafeValueContainer } from "../../../templateFormatter";
import { zSnowflake } from "../../../utils";
import { ActionError } from "../ActionError"; import { ActionError } from "../ActionError";
import { CustomEventsPluginType, TCustomEvent } from "../types"; import { CustomEventsPluginType, TCustomEvent } from "../types";
export const MakeRoleUnmentionableAction = t.type({ export const zMakeRoleUnmentionableAction = z.strictObject({
type: t.literal("make_role_unmentionable"), type: z.literal("make_role_unmentionable"),
role: t.string, role: zSnowflake,
}); });
export type TMakeRoleUnmentionableAction = t.TypeOf<typeof MakeRoleUnmentionableAction>; export type TMakeRoleUnmentionableAction = z.infer<typeof zMakeRoleUnmentionableAction>;
export async function makeRoleUnmentionableAction( export async function makeRoleUnmentionableAction(
pluginData: GuildPluginData<CustomEventsPluginType>, pluginData: GuildPluginData<CustomEventsPluginType>,

View file

@ -1,16 +1,17 @@
import { Snowflake, TextChannel } from "discord.js"; import { Snowflake, TextChannel } from "discord.js";
import * as t from "io-ts";
import { GuildPluginData } from "knub"; import { GuildPluginData } from "knub";
import z from "zod";
import { TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter"; import { TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter";
import { zBoundedCharacters, zSnowflake } from "../../../utils";
import { ActionError } from "../ActionError"; import { ActionError } from "../ActionError";
import { CustomEventsPluginType } from "../types"; import { CustomEventsPluginType } from "../types";
export const MessageAction = t.type({ export const zMessageAction = z.strictObject({
type: t.literal("message"), type: z.literal("message"),
channel: t.string, channel: zSnowflake,
content: t.string, content: zBoundedCharacters(0, 4000),
}); });
export type TMessageAction = t.TypeOf<typeof MessageAction>; export type TMessageAction = z.infer<typeof zMessageAction>;
export async function messageAction( export async function messageAction(
pluginData: GuildPluginData<CustomEventsPluginType>, pluginData: GuildPluginData<CustomEventsPluginType>,

View file

@ -1,18 +1,18 @@
import { Snowflake, VoiceChannel } from "discord.js"; import { Snowflake, VoiceChannel } from "discord.js";
import * as t from "io-ts";
import { GuildPluginData } from "knub"; import { GuildPluginData } from "knub";
import z from "zod";
import { canActOn } from "../../../pluginUtils"; import { canActOn } from "../../../pluginUtils";
import { TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter"; import { TemplateSafeValueContainer, renderTemplate } from "../../../templateFormatter";
import { resolveMember } from "../../../utils"; import { resolveMember, zSnowflake } from "../../../utils";
import { ActionError } from "../ActionError"; import { ActionError } from "../ActionError";
import { CustomEventsPluginType, TCustomEvent } from "../types"; import { CustomEventsPluginType, TCustomEvent } from "../types";
export const MoveToVoiceChannelAction = t.type({ export const zMoveToVoiceChannelAction = z.strictObject({
type: t.literal("move_to_vc"), type: z.literal("move_to_vc"),
target: t.string, target: zSnowflake,
channel: t.string, channel: zSnowflake,
}); });
export type TMoveToVoiceChannelAction = t.TypeOf<typeof MoveToVoiceChannelAction>; export type TMoveToVoiceChannelAction = z.infer<typeof zMoveToVoiceChannelAction>;
export async function moveToVoiceChannelAction( export async function moveToVoiceChannelAction(
pluginData: GuildPluginData<CustomEventsPluginType>, pluginData: GuildPluginData<CustomEventsPluginType>,

View file

@ -1,23 +1,24 @@
import { PermissionsBitField, PermissionsString, Snowflake } from "discord.js"; import { PermissionsBitField, PermissionsString, Snowflake } from "discord.js";
import * as t from "io-ts";
import { GuildPluginData } from "knub"; import { GuildPluginData } from "knub";
import z from "zod";
import { TemplateSafeValueContainer } from "../../../templateFormatter"; import { TemplateSafeValueContainer } from "../../../templateFormatter";
import { zSnowflake } from "../../../utils";
import { ActionError } from "../ActionError"; import { ActionError } from "../ActionError";
import { CustomEventsPluginType, TCustomEvent } from "../types"; import { CustomEventsPluginType, TCustomEvent } from "../types";
export const SetChannelPermissionOverridesAction = t.type({ export const zSetChannelPermissionOverridesAction = z.strictObject({
type: t.literal("set_channel_permission_overrides"), type: z.literal("set_channel_permission_overrides"),
channel: t.string, channel: zSnowflake,
overrides: t.array( overrides: z.array(
t.type({ z.strictObject({
type: t.union([t.literal("member"), t.literal("role")]), type: z.union([z.literal("member"), z.literal("role")]),
id: t.string, id: zSnowflake,
allow: t.number, allow: z.number(),
deny: t.number, deny: z.number(),
}), }),
), ).max(15),
}); });
export type TSetChannelPermissionOverridesAction = t.TypeOf<typeof SetChannelPermissionOverridesAction>; export type TSetChannelPermissionOverridesAction = z.infer<typeof zSetChannelPermissionOverridesAction>;
export async function setChannelPermissionOverridesAction( export async function setChannelPermissionOverridesAction(
pluginData: GuildPluginData<CustomEventsPluginType>, pluginData: GuildPluginData<CustomEventsPluginType>,

View file

@ -1,47 +1,50 @@
import * as t from "io-ts";
import { BasePluginType } from "knub"; import { BasePluginType } from "knub";
import { AddRoleAction } from "./actions/addRoleAction"; import z from "zod";
import { CreateCaseAction } from "./actions/createCaseAction"; import { zBoundedCharacters, zBoundedRecord } from "../../utils";
import { MakeRoleMentionableAction } from "./actions/makeRoleMentionableAction"; import { zAddRoleAction } from "./actions/addRoleAction";
import { MakeRoleUnmentionableAction } from "./actions/makeRoleUnmentionableAction"; import { zCreateCaseAction } from "./actions/createCaseAction";
import { MessageAction } from "./actions/messageAction"; import { zMakeRoleMentionableAction } from "./actions/makeRoleMentionableAction";
import { MoveToVoiceChannelAction } from "./actions/moveToVoiceChannelAction"; import { zMakeRoleUnmentionableAction } from "./actions/makeRoleUnmentionableAction";
import { SetChannelPermissionOverridesAction } from "./actions/setChannelPermissionOverrides"; import { zMessageAction } from "./actions/messageAction";
import { zMoveToVoiceChannelAction } from "./actions/moveToVoiceChannelAction";
import { zSetChannelPermissionOverridesAction } from "./actions/setChannelPermissionOverrides";
// Triggers const zCommandTrigger = z.strictObject({
const CommandTrigger = t.type({ type: z.literal("command"),
type: t.literal("command"), name: zBoundedCharacters(0, 100),
name: t.string, params: zBoundedCharacters(0, 255),
params: t.string, can_use: z.boolean(),
can_use: t.boolean,
}); });
const AnyTrigger = CommandTrigger; // TODO: Make into a union once we have more triggers const zAnyTrigger = zCommandTrigger; // TODO: Make into a union once we have more triggers
const AnyAction = t.union([ const zAnyAction = z.union([
AddRoleAction, zAddRoleAction,
CreateCaseAction, zCreateCaseAction,
MoveToVoiceChannelAction, zMoveToVoiceChannelAction,
MessageAction, zMessageAction,
MakeRoleMentionableAction, zMakeRoleMentionableAction,
MakeRoleUnmentionableAction, zMakeRoleUnmentionableAction,
SetChannelPermissionOverridesAction, zSetChannelPermissionOverridesAction,
]); ]);
export const CustomEvent = t.type({ export const zCustomEvent = z.strictObject({
name: t.string, name: zBoundedCharacters(0, 100),
trigger: AnyTrigger, trigger: zAnyTrigger,
actions: t.array(AnyAction), actions: z.array(zAnyAction).max(10),
}); });
export type TCustomEvent = t.TypeOf<typeof CustomEvent>; export type TCustomEvent = z.infer<typeof zCustomEvent>;
export const ConfigSchema = t.type({ export const zCustomEventsConfig = z.strictObject({
events: t.record(t.string, CustomEvent), events: zBoundedRecord(
z.record(zBoundedCharacters(0, 100), zCustomEvent),
0,
100,
),
}); });
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
export interface CustomEventsPluginType extends BasePluginType { export interface CustomEventsPluginType extends BasePluginType {
config: TConfigSchema; config: z.infer<typeof zCustomEventsConfig>;
state: { state: {
clearTriggers: () => void; clearTriggers: () => void;
}; };

View file

@ -1,11 +1,10 @@
import { Guild } from "discord.js"; import { Guild } from "discord.js";
import * as t from "io-ts";
import { BasePluginType, GlobalPluginData, globalPluginEventListener } from "knub"; import { BasePluginType, GlobalPluginData, globalPluginEventListener } from "knub";
import { AllowedGuilds } from "../../data/AllowedGuilds"; import { AllowedGuilds } from "../../data/AllowedGuilds";
import { Configs } from "../../data/Configs"; import { Configs } from "../../data/Configs";
import { env } from "../../env"; import { env } from "../../env";
import { makeIoTsConfigParser } from "../../pluginUtils";
import { zeppelinGlobalPlugin } from "../ZeppelinPluginBlueprint"; import { zeppelinGlobalPlugin } from "../ZeppelinPluginBlueprint";
import z from "zod";
interface GuildAccessMonitorPluginType extends BasePluginType { interface GuildAccessMonitorPluginType extends BasePluginType {
state: { state: {
@ -26,7 +25,7 @@ async function checkGuild(pluginData: GlobalPluginData<GuildAccessMonitorPluginT
*/ */
export const GuildAccessMonitorPlugin = zeppelinGlobalPlugin<GuildAccessMonitorPluginType>()({ export const GuildAccessMonitorPlugin = zeppelinGlobalPlugin<GuildAccessMonitorPluginType>()({
name: "guild_access_monitor", name: "guild_access_monitor",
configParser: makeIoTsConfigParser(t.type({})), configParser: (input) => z.strictObject({}).parse(input),
events: [ events: [
globalPluginEventListener<GuildAccessMonitorPluginType>()({ globalPluginEventListener<GuildAccessMonitorPluginType>()({

View file

@ -1,6 +1,5 @@
import * as t from "io-ts"; import z from "zod";
import { Configs } from "../../data/Configs"; import { Configs } from "../../data/Configs";
import { makeIoTsConfigParser } from "../../pluginUtils";
import { zeppelinGlobalPlugin } from "../ZeppelinPluginBlueprint"; import { zeppelinGlobalPlugin } from "../ZeppelinPluginBlueprint";
import { reloadChangedGuilds } from "./functions/reloadChangedGuilds"; import { reloadChangedGuilds } from "./functions/reloadChangedGuilds";
import { GuildConfigReloaderPluginType } from "./types"; import { GuildConfigReloaderPluginType } from "./types";
@ -9,7 +8,7 @@ export const GuildConfigReloaderPlugin = zeppelinGlobalPlugin<GuildConfigReloade
name: "guild_config_reloader", name: "guild_config_reloader",
showInDocs: false, showInDocs: false,
configParser: makeIoTsConfigParser(t.type({})), configParser: (input) => z.strictObject({}).parse(input),
async beforeLoad(pluginData) { async beforeLoad(pluginData) {
const { state } = pluginData; const { state } = pluginData;

View file

@ -1,18 +1,17 @@
import { Guild } from "discord.js"; import { Guild } from "discord.js";
import * as t from "io-ts";
import { guildPluginEventListener } from "knub"; import { guildPluginEventListener } from "knub";
import { AllowedGuilds } from "../../data/AllowedGuilds"; import { AllowedGuilds } from "../../data/AllowedGuilds";
import { ApiPermissionAssignments } from "../../data/ApiPermissionAssignments"; import { ApiPermissionAssignments } from "../../data/ApiPermissionAssignments";
import { makeIoTsConfigParser } from "../../pluginUtils";
import { MINUTES } from "../../utils"; import { MINUTES } from "../../utils";
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
import { GuildInfoSaverPluginType } from "./types"; import { GuildInfoSaverPluginType } from "./types";
import z from "zod";
export const GuildInfoSaverPlugin = zeppelinGuildPlugin<GuildInfoSaverPluginType>()({ export const GuildInfoSaverPlugin = zeppelinGuildPlugin<GuildInfoSaverPluginType>()({
name: "guild_info_saver", name: "guild_info_saver",
showInDocs: false, showInDocs: false,
configParser: makeIoTsConfigParser(t.type({})), configParser: (input) => z.strictObject({}).parse(input),
events: [ events: [
guildPluginEventListener({ guildPluginEventListener({

View file

@ -1,6 +1,6 @@
import * as t from "io-ts"; import z from "zod";
import { GuildMemberCache } from "../../data/GuildMemberCache"; import { GuildMemberCache } from "../../data/GuildMemberCache";
import { makeIoTsConfigParser, mapToPublicFn } from "../../pluginUtils"; import { mapToPublicFn } from "../../pluginUtils";
import { SECONDS } from "../../utils"; import { SECONDS } from "../../utils";
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
import { cancelDeletionOnMemberJoin } from "./events/cancelDeletionOnMemberJoin"; import { cancelDeletionOnMemberJoin } from "./events/cancelDeletionOnMemberJoin";
@ -18,7 +18,7 @@ export const GuildMemberCachePlugin = zeppelinGuildPlugin<GuildMemberCachePlugin
name: "guild_member_cache", name: "guild_member_cache",
showInDocs: false, showInDocs: false,
configParser: makeIoTsConfigParser(t.type({})), configParser: (input) => z.strictObject({}).parse(input),
events: [ events: [
updateMemberCacheOnMemberUpdate, updateMemberCacheOnMemberUpdate,

View file

@ -1,11 +1,12 @@
import { PluginOptions } from "knub"; import { PluginOptions } from "knub";
import z from "zod";
import { Queue } from "../../Queue"; import { Queue } from "../../Queue";
import { Webhooks } from "../../data/Webhooks"; import { Webhooks } from "../../data/Webhooks";
import { makeIoTsConfigParser, mapToPublicFn } from "../../pluginUtils"; import { mapToPublicFn } from "../../pluginUtils";
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
import { editMessage } from "./functions/editMessage"; import { editMessage } from "./functions/editMessage";
import { sendMessage } from "./functions/sendMessage"; import { sendMessage } from "./functions/sendMessage";
import { ConfigSchema, InternalPosterPluginType } from "./types"; import { InternalPosterPluginType } from "./types";
const defaultOptions: PluginOptions<InternalPosterPluginType> = { const defaultOptions: PluginOptions<InternalPosterPluginType> = {
config: {}, config: {},
@ -16,7 +17,7 @@ export const InternalPosterPlugin = zeppelinGuildPlugin<InternalPosterPluginType
name: "internal_poster", name: "internal_poster",
showInDocs: false, showInDocs: false,
configParser: makeIoTsConfigParser(ConfigSchema), configParser: (input) => z.strictObject({}).parse(input),
defaultOptions, defaultOptions,
// prettier-ignore // prettier-ignore

View file

@ -1,15 +1,9 @@
import { WebhookClient } from "discord.js"; import { WebhookClient } from "discord.js";
import * as t from "io-ts";
import { BasePluginType } from "knub"; import { BasePluginType } from "knub";
import { Queue } from "../../Queue"; import { Queue } from "../../Queue";
import { Webhooks } from "../../data/Webhooks"; import { Webhooks } from "../../data/Webhooks";
export const ConfigSchema = t.type({});
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
export interface InternalPosterPluginType extends BasePluginType { export interface InternalPosterPluginType extends BasePluginType {
config: TConfigSchema;
state: { state: {
queue: Queue; queue: Queue;
webhooks: Webhooks; webhooks: Webhooks;

View file

@ -1,7 +1,6 @@
import { PluginOptions } from "knub"; import { PluginOptions } from "knub";
import { onGuildEvent } from "../../data/GuildEvents"; import { onGuildEvent } from "../../data/GuildEvents";
import { GuildVCAlerts } from "../../data/GuildVCAlerts"; import { GuildVCAlerts } from "../../data/GuildVCAlerts";
import { makeIoTsConfigParser } from "../../pluginUtils";
import { trimPluginDescription } from "../../utils"; import { trimPluginDescription } from "../../utils";
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
import { FollowCmd } from "./commands/FollowCmd"; import { FollowCmd } from "./commands/FollowCmd";
@ -9,7 +8,7 @@ import { DeleteFollowCmd, ListFollowCmd } from "./commands/ListFollowCmd";
import { WhereCmd } from "./commands/WhereCmd"; import { WhereCmd } from "./commands/WhereCmd";
import { GuildBanRemoveAlertsEvt } from "./events/BanRemoveAlertsEvt"; import { GuildBanRemoveAlertsEvt } from "./events/BanRemoveAlertsEvt";
import { VoiceStateUpdateAlertEvt } from "./events/SendAlertsEvts"; import { VoiceStateUpdateAlertEvt } from "./events/SendAlertsEvts";
import { ConfigSchema, LocateUserPluginType } from "./types"; import { LocateUserPluginType, zLocateUserConfig } from "./types";
import { clearExpiredAlert } from "./utils/clearExpiredAlert"; import { clearExpiredAlert } from "./utils/clearExpiredAlert";
import { fillActiveAlertsList } from "./utils/fillAlertsList"; import { fillActiveAlertsList } from "./utils/fillAlertsList";
@ -39,10 +38,10 @@ export const LocateUserPlugin = zeppelinGuildPlugin<LocateUserPluginType>()({
* Instantly receive an invite to the voice channel of a user * Instantly receive an invite to the voice channel of a user
* Be notified as soon as a user switches or joins a voice channel * Be notified as soon as a user switches or joins a voice channel
`), `),
configSchema: ConfigSchema, configSchema: zLocateUserConfig,
}, },
configParser: makeIoTsConfigParser(ConfigSchema), configParser: (input) => zLocateUserConfig.parse(input),
defaultOptions, defaultOptions,
// prettier-ignore // prettier-ignore

View file

@ -1,15 +1,14 @@
import * as t from "io-ts";
import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand } from "knub"; import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand } from "knub";
import z from "zod";
import { GuildVCAlerts } from "../../data/GuildVCAlerts"; import { GuildVCAlerts } from "../../data/GuildVCAlerts";
export const ConfigSchema = t.type({ export const zLocateUserConfig = z.strictObject({
can_where: t.boolean, can_where: z.boolean(),
can_alert: t.boolean, can_alert: z.boolean(),
}); });
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
export interface LocateUserPluginType extends BasePluginType { export interface LocateUserPluginType extends BasePluginType {
config: TConfigSchema; config: z.infer<typeof zLocateUserConfig>;
state: { state: {
alerts: GuildVCAlerts; alerts: GuildVCAlerts;
usersWithAlerts: string[]; usersWithAlerts: string[];

View file

@ -6,7 +6,7 @@ import { GuildLogs } from "../../data/GuildLogs";
import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildSavedMessages } from "../../data/GuildSavedMessages";
import { LogType } from "../../data/LogType"; import { LogType } from "../../data/LogType";
import { logger } from "../../logger"; import { logger } from "../../logger";
import { makeIoTsConfigParser, mapToPublicFn } from "../../pluginUtils"; import { mapToPublicFn } from "../../pluginUtils";
import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners"; import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners";
import { TypedTemplateSafeValueContainer, createTypedTemplateSafeValueContainer } from "../../templateFormatter"; import { TypedTemplateSafeValueContainer, createTypedTemplateSafeValueContainer } from "../../templateFormatter";
import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin"; import { TimeAndDatePlugin } from "../TimeAndDate/TimeAndDatePlugin";
@ -31,7 +31,7 @@ import {
import { LogsThreadCreateEvt, LogsThreadDeleteEvt, LogsThreadUpdateEvt } from "./events/LogsThreadModifyEvts"; import { LogsThreadCreateEvt, LogsThreadDeleteEvt, LogsThreadUpdateEvt } from "./events/LogsThreadModifyEvts";
import { LogsGuildMemberUpdateEvt } from "./events/LogsUserUpdateEvts"; import { LogsGuildMemberUpdateEvt } from "./events/LogsUserUpdateEvts";
import { LogsVoiceStateUpdateEvt } from "./events/LogsVoiceChannelEvts"; import { LogsVoiceStateUpdateEvt } from "./events/LogsVoiceChannelEvts";
import { ConfigSchema, FORMAT_NO_TIMESTAMP, ILogTypeData, LogsPluginType, TLogChannel } from "./types"; import { FORMAT_NO_TIMESTAMP, ILogTypeData, LogsPluginType, TLogChannel, zLogsConfig } from "./types";
import { getLogMessage } from "./util/getLogMessage"; import { getLogMessage } from "./util/getLogMessage";
import { log } from "./util/log"; import { log } from "./util/log";
import { onMessageDelete } from "./util/onMessageDelete"; import { onMessageDelete } from "./util/onMessageDelete";
@ -110,7 +110,6 @@ import { logVoiceChannelForceMove } from "./logFunctions/logVoiceChannelForceMov
import { logVoiceChannelJoin } from "./logFunctions/logVoiceChannelJoin"; import { logVoiceChannelJoin } from "./logFunctions/logVoiceChannelJoin";
import { logVoiceChannelLeave } from "./logFunctions/logVoiceChannelLeave"; import { logVoiceChannelLeave } from "./logFunctions/logVoiceChannelLeave";
import { logVoiceChannelMove } from "./logFunctions/logVoiceChannelMove"; import { logVoiceChannelMove } from "./logFunctions/logVoiceChannelMove";
import { asBoundedString } from "../../utils/iotsUtils";
// The `any` cast here is to prevent TypeScript from locking up from the circular dependency // The `any` cast here is to prevent TypeScript from locking up from the circular dependency
function getCasesPlugin(): Promise<any> { function getCasesPlugin(): Promise<any> {
@ -121,12 +120,12 @@ const defaultOptions: PluginOptions<LogsPluginType> = {
config: { config: {
channels: {}, channels: {},
format: { format: {
timestamp: asBoundedString(FORMAT_NO_TIMESTAMP), // Legacy/deprecated, use timestamp_format below instead timestamp: FORMAT_NO_TIMESTAMP,
...DefaultLogMessages, ...DefaultLogMessages,
}, },
ping_user: true, // Legacy/deprecated, if below is false mentions wont actually ping. In case you really want the old behavior, set below to true ping_user: true,
allow_user_mentions: false, allow_user_mentions: false,
timestamp_format: asBoundedString("[<t:]X[>]"), timestamp_format: "[<t:]X[>]",
include_embed_timestamp: true, include_embed_timestamp: true,
}, },
@ -134,7 +133,8 @@ const defaultOptions: PluginOptions<LogsPluginType> = {
{ {
level: ">=50", level: ">=50",
config: { config: {
ping_user: false, // Legacy/deprecated, read comment on global ping_user option // Legacy/deprecated, read comment on global ping_user option
ping_user: false,
}, },
}, },
], ],
@ -145,11 +145,11 @@ export const LogsPlugin = zeppelinGuildPlugin<LogsPluginType>()({
showInDocs: true, showInDocs: true,
info: { info: {
prettyName: "Logs", prettyName: "Logs",
configSchema: ConfigSchema, configSchema: zLogsConfig,
}, },
dependencies: async () => [TimeAndDatePlugin, InternalPosterPlugin, (await getCasesPlugin()).CasesPlugin], dependencies: async () => [TimeAndDatePlugin, InternalPosterPlugin, (await getCasesPlugin()).CasesPlugin],
configParser: makeIoTsConfigParser(ConfigSchema), configParser: (input) => zLogsConfig.parse(input),
defaultOptions, defaultOptions,
events: [ events: [

View file

@ -32,7 +32,7 @@ export function logMessageDelete(pluginData: GuildPluginData<LogsPluginType>, da
// See comment on FORMAT_NO_TIMESTAMP in types.ts // See comment on FORMAT_NO_TIMESTAMP in types.ts
const config = pluginData.config.get(); const config = pluginData.config.get();
const timestampFormat = const timestampFormat =
(config.format.timestamp !== FORMAT_NO_TIMESTAMP ? config.format.timestamp : null) ?? config.timestamp_format; (config.format.timestamp !== FORMAT_NO_TIMESTAMP ? config.format.timestamp : null) ?? config.timestamp_format ?? undefined;
return log( return log(
pluginData, pluginData,

View file

@ -1,4 +1,3 @@
import * as t from "io-ts";
import { BasePluginType, CooldownManager, guildPluginEventListener } from "knub"; import { BasePluginType, CooldownManager, guildPluginEventListener } from "knub";
import { z } from "zod"; import { z } from "zod";
import { RegExpRunner } from "../../RegExpRunner"; import { RegExpRunner } from "../../RegExpRunner";
@ -7,7 +6,7 @@ import { GuildCases } from "../../data/GuildCases";
import { GuildLogs } from "../../data/GuildLogs"; import { GuildLogs } from "../../data/GuildLogs";
import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildSavedMessages } from "../../data/GuildSavedMessages";
import { LogType } from "../../data/LogType"; import { LogType } from "../../data/LogType";
import { tMessageContent, tNullable } from "../../utils"; import { zBoundedCharacters, zMessageContent, zRegex, zSnowflake } from "../../utils";
import { MessageBuffer } from "../../utils/MessageBuffer"; import { MessageBuffer } from "../../utils/MessageBuffer";
import { import {
TemplateSafeCase, TemplateSafeCase,
@ -22,55 +21,56 @@ import {
TemplateSafeUnknownUser, TemplateSafeUnknownUser,
TemplateSafeUser, TemplateSafeUser,
} from "../../utils/templateSafeObjects"; } from "../../utils/templateSafeObjects";
import { TRegex } from "../../validatorUtils";
import { tBoundedString } from "../../utils/iotsUtils";
export const tLogFormats = t.record(t.string, t.union([t.string, tMessageContent])); const DEFAULT_BATCH_TIME = 1000;
export type TLogFormats = t.TypeOf<typeof tLogFormats>; const MIN_BATCH_TIME = 250;
const MAX_BATCH_TIME = 5000;
const LogChannel = t.partial({ export const zLogFormats = z.record(
include: t.array(t.string), zBoundedCharacters(1, 255),
exclude: t.array(t.string), zMessageContent,
batched: t.boolean, );
batch_time: t.number, export type TLogFormats = z.infer<typeof zLogFormats>;
excluded_users: t.array(t.string),
excluded_message_regexes: t.array(TRegex), const zLogChannel = z.strictObject({
excluded_channels: t.array(t.string), include: z.array(zBoundedCharacters(1, 255)).default([]),
excluded_categories: t.array(t.string), exclude: z.array(zBoundedCharacters(1, 255)).default([]),
excluded_threads: t.array(t.string), batched: z.boolean().default(true),
exclude_bots: t.boolean, batch_time: z.number().min(MIN_BATCH_TIME).max(MAX_BATCH_TIME).default(DEFAULT_BATCH_TIME),
excluded_roles: t.array(t.string), excluded_users: z.array(zSnowflake).nullable().default(null),
format: tNullable(tLogFormats), excluded_message_regexes: z.array(zRegex(z.string())).nullable().default(null),
timestamp_format: t.string, excluded_channels: z.array(zSnowflake).nullable().default(null),
include_embed_timestamp: t.boolean, excluded_categories: z.array(zSnowflake).nullable().default(null),
excluded_threads: z.array(zSnowflake).nullable().default(null),
exclude_bots: z.boolean().default(false),
excluded_roles: z.array(zSnowflake).nullable().default(null),
format: zLogFormats.default({}),
timestamp_format: z.string().nullable().default(null),
include_embed_timestamp: z.boolean().nullable().default(null),
}); });
export type TLogChannel = t.TypeOf<typeof LogChannel>; export type TLogChannel = z.infer<typeof zLogChannel>;
const LogChannelMap = t.record(t.string, LogChannel); const zLogChannelMap = z.record(zSnowflake, zLogChannel);
export type TLogChannelMap = t.TypeOf<typeof LogChannelMap>; export type TLogChannelMap = z.infer<typeof zLogChannelMap>;
export const ConfigSchema = t.type({ export const zLogsConfig = z.strictObject({
channels: LogChannelMap, channels: zLogChannelMap,
format: t.intersection([ format: z.intersection(zLogFormats, z.strictObject({
tLogFormats, // Legacy/deprecated, use timestamp_format below instead
t.type({ timestamp: zBoundedCharacters(0, 64).nullable(),
timestamp: tBoundedString(0, 64), // Legacy/deprecated })),
}), // Legacy/deprecated, if below is false mentions wont actually ping. In case you really want the old behavior, set below to true
]), ping_user: z.boolean(),
ping_user: t.boolean, // Legacy/deprecated, if below is false mentions wont actually ping allow_user_mentions: z.boolean(),
allow_user_mentions: t.boolean, timestamp_format: z.string().nullable(),
timestamp_format: tBoundedString(0, 64), include_embed_timestamp: z.boolean(),
include_embed_timestamp: t.boolean,
}); });
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
// Hacky way of allowing a """null""" default value for config.format.timestamp // Hacky way of allowing a """null""" default value for config.format.timestamp due to legacy io-ts reasons
// The type cannot be made nullable properly because io-ts's intersection type still considers
// that it has to match the record type of tLogFormats, which includes string.
export const FORMAT_NO_TIMESTAMP = "__NO_TIMESTAMP__"; export const FORMAT_NO_TIMESTAMP = "__NO_TIMESTAMP__";
export interface LogsPluginType extends BasePluginType { export interface LogsPluginType extends BasePluginType {
config: TConfigSchema; config: z.infer<typeof zLogsConfig>;
state: { state: {
guildLogs: GuildLogs; guildLogs: GuildLogs;
savedMessages: GuildSavedMessages; savedMessages: GuildSavedMessages;

View file

@ -1,11 +1,10 @@
import { PluginOptions } from "knub"; import { PluginOptions } from "knub";
import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildSavedMessages } from "../../data/GuildSavedMessages";
import { makeIoTsConfigParser } from "../../pluginUtils";
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
import { SaveMessagesToDBCmd } from "./commands/SaveMessagesToDB"; import { SaveMessagesToDBCmd } from "./commands/SaveMessagesToDB";
import { SavePinsToDBCmd } from "./commands/SavePinsToDB"; import { SavePinsToDBCmd } from "./commands/SavePinsToDB";
import { MessageCreateEvt, MessageDeleteBulkEvt, MessageDeleteEvt, MessageUpdateEvt } from "./events/SaveMessagesEvts"; import { MessageCreateEvt, MessageDeleteBulkEvt, MessageDeleteEvt, MessageUpdateEvt } from "./events/SaveMessagesEvts";
import { ConfigSchema, MessageSaverPluginType } from "./types"; import { MessageSaverPluginType, zMessageSaverConfig } from "./types";
const defaultOptions: PluginOptions<MessageSaverPluginType> = { const defaultOptions: PluginOptions<MessageSaverPluginType> = {
config: { config: {
@ -25,7 +24,7 @@ export const MessageSaverPlugin = zeppelinGuildPlugin<MessageSaverPluginType>()(
name: "message_saver", name: "message_saver",
showInDocs: false, showInDocs: false,
configParser: makeIoTsConfigParser(ConfigSchema), configParser: (input) => zMessageSaverConfig.parse(input),
defaultOptions, defaultOptions,
// prettier-ignore // prettier-ignore

View file

@ -1,14 +1,13 @@
import * as t from "io-ts";
import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand } from "knub"; import { BasePluginType, guildPluginEventListener, guildPluginMessageCommand } from "knub";
import z from "zod";
import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildSavedMessages } from "../../data/GuildSavedMessages";
export const ConfigSchema = t.type({ export const zMessageSaverConfig = z.strictObject({
can_manage: t.boolean, can_manage: z.boolean(),
}); });
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
export interface MessageSaverPluginType extends BasePluginType { export interface MessageSaverPluginType extends BasePluginType {
config: TConfigSchema; config: z.infer<typeof zMessageSaverConfig>;
state: { state: {
savedMessages: GuildSavedMessages; savedMessages: GuildSavedMessages;
}; };

View file

@ -6,7 +6,7 @@ import { onGuildEvent } from "../../data/GuildEvents";
import { GuildLogs } from "../../data/GuildLogs"; import { GuildLogs } from "../../data/GuildLogs";
import { GuildMutes } from "../../data/GuildMutes"; import { GuildMutes } from "../../data/GuildMutes";
import { GuildTempbans } from "../../data/GuildTempbans"; import { GuildTempbans } from "../../data/GuildTempbans";
import { makeIoTsConfigParser, mapToPublicFn } from "../../pluginUtils"; import { mapToPublicFn } from "../../pluginUtils";
import { MINUTES, trimPluginDescription } from "../../utils"; import { MINUTES, trimPluginDescription } from "../../utils";
import { CasesPlugin } from "../Cases/CasesPlugin"; import { CasesPlugin } from "../Cases/CasesPlugin";
import { LogsPlugin } from "../Logs/LogsPlugin"; import { LogsPlugin } from "../Logs/LogsPlugin";
@ -47,7 +47,7 @@ import { offModActionsEvent } from "./functions/offModActionsEvent";
import { onModActionsEvent } from "./functions/onModActionsEvent"; import { onModActionsEvent } from "./functions/onModActionsEvent";
import { updateCase } from "./functions/updateCase"; import { updateCase } from "./functions/updateCase";
import { warnMember } from "./functions/warnMember"; import { warnMember } from "./functions/warnMember";
import { BanOptions, ConfigSchema, KickOptions, ModActionsPluginType, WarnOptions } from "./types"; import { BanOptions, KickOptions, ModActionsPluginType, WarnOptions, zModActionsConfig } from "./types";
const defaultOptions = { const defaultOptions = {
config: { config: {
@ -121,11 +121,11 @@ export const ModActionsPlugin = zeppelinGuildPlugin<ModActionsPluginType>()({
description: trimPluginDescription(` description: trimPluginDescription(`
This plugin contains the 'typical' mod actions such as warning, muting, kicking, banning, etc. This plugin contains the 'typical' mod actions such as warning, muting, kicking, banning, etc.
`), `),
configSchema: ConfigSchema, configSchema: zModActionsConfig,
}, },
dependencies: () => [TimeAndDatePlugin, CasesPlugin, MutesPlugin, LogsPlugin], dependencies: () => [TimeAndDatePlugin, CasesPlugin, MutesPlugin, LogsPlugin],
configParser: makeIoTsConfigParser(ConfigSchema), configParser: (input) => zModActionsConfig.parse(input),
defaultOptions, defaultOptions,
events: [CreateBanCaseOnManualBanEvt, CreateUnbanCaseOnManualUnbanEvt, PostAlertOnMemberJoinEvt, AuditLogEvents], events: [CreateBanCaseOnManualBanEvt, CreateUnbanCaseOnManualUnbanEvt, PostAlertOnMemberJoinEvt, AuditLogEvents],

Some files were not shown because too many files have changed in this diff Show more