3
0
Fork 0
mirror of https://github.com/ZeppelinBot/Zeppelin.git synced 2025-03-14 21:31:50 +00:00

chore: run prettier

This commit is contained in:
Dragory 2024-01-27 14:23:13 +02:00
parent 77ab2718e7
commit 873bf7eb99
No known key found for this signature in database
54 changed files with 462 additions and 416 deletions

View file

@ -4,12 +4,12 @@ import { Repository } from "typeorm";
import { TemplateSafeValueContainer, renderTemplate } from "../templateFormatter"; import { TemplateSafeValueContainer, renderTemplate } from "../templateFormatter";
import { renderUsername, trimLines } from "../utils"; import { renderUsername, trimLines } from "../utils";
import { decrypt, encrypt } from "../utils/crypt"; import { decrypt, encrypt } from "../utils/crypt";
import { isDefaultSticker } from "../utils/isDefaultSticker";
import { channelToTemplateSafeChannel, guildToTemplateSafeGuild } from "../utils/templateSafeObjects"; import { channelToTemplateSafeChannel, guildToTemplateSafeGuild } from "../utils/templateSafeObjects";
import { BaseGuildRepository } from "./BaseGuildRepository"; import { BaseGuildRepository } from "./BaseGuildRepository";
import { dataSource } from "./dataSource"; import { dataSource } from "./dataSource";
import { ArchiveEntry } from "./entities/ArchiveEntry"; import { ArchiveEntry } from "./entities/ArchiveEntry";
import { SavedMessage } from "./entities/SavedMessage"; import { SavedMessage } from "./entities/SavedMessage";
import { isDefaultSticker } from "../utils/isDefaultSticker";
const DEFAULT_EXPIRY_DAYS = 30; const DEFAULT_EXPIRY_DAYS = 30;

View file

@ -1,21 +1,21 @@
import { z } from "zod"; import { z } from "zod";
import { guildPlugins } from "./plugins/availablePlugins";
import zodToJsonSchema from "zod-to-json-schema"; import zodToJsonSchema from "zod-to-json-schema";
import { guildPlugins } from "./plugins/availablePlugins";
import { zZeppelinGuildConfig } from "./types"; import { zZeppelinGuildConfig } from "./types";
const pluginSchemaMap = guildPlugins.reduce((map, plugin) => { const pluginSchemaMap = guildPlugins.reduce((map, plugin) => {
if (! plugin.info) { if (!plugin.info) {
return map; return map;
} }
map[plugin.name] = plugin.info.configSchema; map[plugin.name] = plugin.info.configSchema;
return map; return map;
}, {}); }, {});
const fullSchema = zZeppelinGuildConfig const fullSchema = zZeppelinGuildConfig.omit({ plugins: true }).merge(
.omit({ plugins: true }) z.strictObject({
.merge(z.strictObject({
plugins: z.strictObject(pluginSchemaMap).partial(), plugins: z.strictObject(pluginSchemaMap).partial(),
})); }),
);
const jsonSchema = zodToJsonSchema(fullSchema); const jsonSchema = zodToJsonSchema(fullSchema);

View file

@ -10,13 +10,7 @@ import {
PermissionsBitField, PermissionsBitField,
TextBasedChannel, TextBasedChannel,
} from "discord.js"; } from "discord.js";
import { import { AnyPluginData, CommandContext, ExtendedMatchParams, GuildPluginData, helpers } from "knub";
AnyPluginData,
CommandContext,
ExtendedMatchParams,
GuildPluginData,
helpers
} 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";

View file

@ -1,10 +1,10 @@
import { BasePluginType } from "knub"; import { BasePluginType } from "knub";
import z from "zod";
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, zDelayString } 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;

View file

@ -1,9 +1,9 @@
import { CooldownManager } from "knub"; import { CooldownManager } from "knub";
import { Queue } from "../../Queue";
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";
import { GuildSavedMessages } from "../../data/GuildSavedMessages"; import { GuildSavedMessages } from "../../data/GuildSavedMessages";
import { Queue } from "../../Queue";
import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners"; 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";
@ -19,9 +19,9 @@ import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
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";
import { runAutomodOnCounterTrigger } from "./events/runAutomodOnCounterTrigger";
import { RunAutomodOnJoinEvt, RunAutomodOnLeaveEvt } from "./events/RunAutomodOnJoinLeaveEvt"; import { RunAutomodOnJoinEvt, RunAutomodOnLeaveEvt } from "./events/RunAutomodOnJoinLeaveEvt";
import { RunAutomodOnMemberUpdate } from "./events/RunAutomodOnMemberUpdate"; import { RunAutomodOnMemberUpdate } from "./events/RunAutomodOnMemberUpdate";
import { runAutomodOnCounterTrigger } from "./events/runAutomodOnCounterTrigger";
import { runAutomodOnMessage } from "./events/runAutomodOnMessage"; import { runAutomodOnMessage } from "./events/runAutomodOnMessage";
import { runAutomodOnModAction } from "./events/runAutomodOnModAction"; import { runAutomodOnModAction } from "./events/runAutomodOnModAction";
import { import {

View file

@ -16,7 +16,7 @@ import {
zAllowedMentions, zAllowedMentions,
zBoundedCharacters, zBoundedCharacters,
zNullishToUndefined, zNullishToUndefined,
zSnowflake zSnowflake,
} from "../../../utils"; } from "../../../utils";
import { erisAllowedMentionsToDjsMentionOptions } from "../../../utils/erisAllowedMentionsToDjsMentionOptions"; import { erisAllowedMentionsToDjsMentionOptions } from "../../../utils/erisAllowedMentionsToDjsMentionOptions";
import { messageIsEmpty } from "../../../utils/messageIsEmpty"; import { messageIsEmpty } from "../../../utils/messageIsEmpty";

View file

@ -1,10 +1,17 @@
import z from "zod"; import z from "zod";
import { convertDelayStringToMS, nonNullish, unique, zBoundedCharacters, zDelayString, zSnowflake } 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 { zNotify } from "../constants";
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods"; import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
import { automodAction } from "../helpers"; import { automodAction } from "../helpers";
import { zNotify } from "../constants";
const configSchema = z.strictObject({ const configSchema = z.strictObject({
reason: zBoundedCharacters(0, 4000).nullable().default(null), reason: zBoundedCharacters(0, 4000).nullable().default(null),

View file

@ -68,10 +68,7 @@ export const ChangePermsAction = automodAction({
configSchema: z.strictObject({ configSchema: z.strictObject({
target: zBoundedCharacters(1, 2000), target: zBoundedCharacters(1, 2000),
channel: zBoundedCharacters(1, 2000).nullable().default(null), channel: zBoundedCharacters(1, 2000).nullable().default(null),
perms: z.record( perms: z.record(z.enum(allPermissionNames), z.boolean().nullable()),
z.enum(allPermissionNames),
z.boolean().nullable(),
),
}), }),
async apply({ pluginData, contexts, actionConfig }) { async apply({ pluginData, contexts, actionConfig }) {

View file

@ -2,9 +2,9 @@ import z from "zod";
import { asyncMap, nonNullish, resolveMember, unique, zBoundedCharacters, zSnowflake } 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 { zNotify } from "../constants";
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods"; import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
import { automodAction } from "../helpers"; import { automodAction } from "../helpers";
import { zNotify } from "../constants";
export const KickAction = automodAction({ export const KickAction = automodAction({
configSchema: z.strictObject({ configSchema: z.strictObject({

View file

@ -1,12 +1,19 @@
import z from "zod"; import z from "zod";
import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError"; import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError";
import { convertDelayStringToMS, nonNullish, unique, zBoundedCharacters, zDelayString, zSnowflake } 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 { zNotify } from "../constants";
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods"; import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
import { automodAction } from "../helpers"; import { automodAction } from "../helpers";
import { zNotify } from "../constants";
export const MuteAction = automodAction({ export const MuteAction = automodAction({
configSchema: z.strictObject({ configSchema: z.strictObject({
@ -14,8 +21,14 @@ export const MuteAction = automodAction({
duration: zDelayString.nullable().default(null), duration: zDelayString.nullable().default(null),
notify: zNotify.nullable().default(null), notify: zNotify.nullable().default(null),
notifyChannel: zSnowflake.nullable().default(null), notifyChannel: zSnowflake.nullable().default(null),
remove_roles_on_mute: z.union([z.boolean(), z.array(zSnowflake)]).nullable().default(null), remove_roles_on_mute: z
restore_roles_on_mute: z.union([z.boolean(), z.array(zSnowflake)]).nullable().default(null), .union([z.boolean(), z.array(zSnowflake)])
.nullable()
.default(null),
restore_roles_on_mute: z
.union([z.boolean(), z.array(zSnowflake)])
.nullable()
.default(null),
postInCaseLog: z.boolean().nullable().default(null), postInCaseLog: z.boolean().nullable().default(null),
hide_case: z.boolean().nullable().default(false), hide_case: z.boolean().nullable().default(false),
}), }),

View file

@ -10,7 +10,7 @@ import {
verboseChannelMention, verboseChannelMention,
zBoundedCharacters, zBoundedCharacters,
zDelayString, zDelayString,
zMessageContent 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";

View file

@ -2,9 +2,9 @@ import z from "zod";
import { asyncMap, nonNullish, resolveMember, unique, zBoundedCharacters, zSnowflake } 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 { zNotify } from "../constants";
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods"; import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
import { automodAction } from "../helpers"; import { automodAction } from "../helpers";
import { zNotify } from "../constants";
export const WarnAction = automodAction({ export const WarnAction = automodAction({
configSchema: z.strictObject({ configSchema: z.strictObject({

View file

@ -20,7 +20,4 @@ export enum RecentActionType {
ThreadCreate, ThreadCreate,
} }
export const zNotify = z.union([ export const zNotify = z.union([z.literal("dm"), z.literal("channel")]);
z.literal("dm"),
z.literal("channel"),
]);

View file

@ -1,3 +1,4 @@
import z from "zod";
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";
@ -7,7 +8,6 @@ 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";
interface TMessageSpamMatchResultType { interface TMessageSpamMatchResultType {
archiveId: string; archiveId: string;

View file

@ -1,7 +1,7 @@
import { GuildPluginData } from "knub"; import { GuildPluginData } from "knub";
import z, { ZodTypeAny } from "zod";
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[];

View file

@ -1,5 +1,5 @@
import { automodTrigger } from "../helpers";
import z from "zod"; import z from "zod";
import { automodTrigger } from "../helpers";
interface AntiraidLevelTriggerResult {} interface AntiraidLevelTriggerResult {}

View file

@ -1,7 +1,7 @@
import { Snowflake } from "discord.js"; import { Snowflake } from "discord.js";
import z from "zod";
import { verboseChannelMention } from "../../../utils"; import { verboseChannelMention } from "../../../utils";
import { automodTrigger } from "../helpers"; import { automodTrigger } from "../helpers";
import z from "zod";
interface AnyMessageResultType {} interface AnyMessageResultType {}

View file

@ -1,6 +1,6 @@
import { escapeInlineCode, Snowflake } from "discord.js"; import { escapeInlineCode, Snowflake } from "discord.js";
import z from "zod";
import { extname } from "path"; import { extname } from "path";
import z from "zod";
import { asSingleLine, messageSummary, verboseChannelMention } from "../../../utils"; import { asSingleLine, messageSummary, verboseChannelMention } from "../../../utils";
import { automodTrigger } from "../helpers"; import { automodTrigger } from "../helpers";
@ -9,28 +9,30 @@ interface MatchResultType {
mode: "blacklist" | "whitelist"; mode: "blacklist" | "whitelist";
} }
const configSchema = z.strictObject({ const configSchema = z
filetype_blacklist: z.array(z.string().max(32)).max(255).default([]), .strictObject({
blacklist_enabled: z.boolean().default(false), filetype_blacklist: z.array(z.string().max(32)).max(255).default([]),
filetype_whitelist: z.array(z.string().max(32)).max(255).default([]), blacklist_enabled: z.boolean().default(false),
whitelist_enabled: z.boolean().default(false), filetype_whitelist: z.array(z.string().max(32)).max(255).default([]),
}).transform((parsed, ctx) => { whitelist_enabled: z.boolean().default(false),
if (parsed.blacklist_enabled && parsed.whitelist_enabled) { })
ctx.addIssue({ .transform((parsed, ctx) => {
code: z.ZodIssueCode.custom, if (parsed.blacklist_enabled && parsed.whitelist_enabled) {
message: "Cannot have both blacklist and whitelist enabled", ctx.addIssue({
}); code: z.ZodIssueCode.custom,
return z.NEVER; message: "Cannot have both blacklist and whitelist enabled",
} });
if (! parsed.blacklist_enabled && ! parsed.whitelist_enabled) { return z.NEVER;
ctx.addIssue({ }
code: z.ZodIssueCode.custom, if (!parsed.blacklist_enabled && !parsed.whitelist_enabled) {
message: "Must have either blacklist or whitelist enabled", ctx.addIssue({
}); code: z.ZodIssueCode.custom,
return z.NEVER; message: "Must have either blacklist or whitelist enabled",
} });
return parsed; return z.NEVER;
}); }
return parsed;
});
export const MatchAttachmentTypeTrigger = automodTrigger<MatchResultType>()({ export const MatchAttachmentTypeTrigger = automodTrigger<MatchResultType>()({
configSchema, configSchema,

View file

@ -26,12 +26,20 @@ const configSchema = z.strictObject({
include_subdomains: z.boolean().default(true), include_subdomains: z.boolean().default(true),
include_words: z.array(z.string().max(2000)).max(700).optional(), include_words: z.array(z.string().max(2000)).max(700).optional(),
exclude_words: z.array(z.string().max(2000)).max(700).optional(), exclude_words: z.array(z.string().max(2000)).max(700).optional(),
include_regex: z.array(zRegex(z.string().max(2000))).max(512).optional(), include_regex: z
exclude_regex: z.array(zRegex(z.string().max(2000))).max(512).optional(), .array(zRegex(z.string().max(2000)))
phisherman: z.strictObject({ .max(512)
include_suspected: z.boolean().optional(), .optional(),
include_verified: z.boolean().optional(), exclude_regex: z
}).optional(), .array(zRegex(z.string().max(2000)))
.max(512)
.optional(),
phisherman: z
.strictObject({
include_suspected: z.boolean().optional(),
include_verified: z.boolean().optional(),
})
.optional(),
only_real_links: z.boolean().default(true), only_real_links: z.boolean().default(true),
match_messages: z.boolean().default(true), match_messages: z.boolean().default(true),
match_embeds: z.boolean().default(true), match_embeds: z.boolean().default(true),

View file

@ -8,28 +8,30 @@ interface MatchResultType {
mode: "blacklist" | "whitelist"; mode: "blacklist" | "whitelist";
} }
const configSchema = z.strictObject({ const configSchema = z
mime_type_blacklist: z.array(z.string().max(255)).max(255).default([]), .strictObject({
blacklist_enabled: z.boolean().default(false), mime_type_blacklist: z.array(z.string().max(255)).max(255).default([]),
mime_type_whitelist: z.array(z.string().max(255)).max(255).default([]), blacklist_enabled: z.boolean().default(false),
whitelist_enabled: z.boolean().default(false), mime_type_whitelist: z.array(z.string().max(255)).max(255).default([]),
}).transform((parsed, ctx) => { whitelist_enabled: z.boolean().default(false),
if (parsed.blacklist_enabled && parsed.whitelist_enabled) { })
ctx.addIssue({ .transform((parsed, ctx) => {
code: z.ZodIssueCode.custom, if (parsed.blacklist_enabled && parsed.whitelist_enabled) {
message: "Cannot have both blacklist and whitelist enabled", ctx.addIssue({
}); code: z.ZodIssueCode.custom,
return z.NEVER; message: "Cannot have both blacklist and whitelist enabled",
} });
if (! parsed.blacklist_enabled && ! parsed.whitelist_enabled) { return z.NEVER;
ctx.addIssue({ }
code: z.ZodIssueCode.custom, if (!parsed.blacklist_enabled && !parsed.whitelist_enabled) {
message: "Must have either blacklist or whitelist enabled", ctx.addIssue({
}); code: z.ZodIssueCode.custom,
return z.NEVER; message: "Must have either blacklist or whitelist enabled",
} });
return parsed; return z.NEVER;
}); }
return parsed;
});
export const MatchMimeTypeTrigger = automodTrigger<MatchResultType>()({ export const MatchMimeTypeTrigger = automodTrigger<MatchResultType>()({
configSchema, configSchema,

View file

@ -8,10 +8,7 @@ interface RoleAddedMatchResult {
matchedRoleId: string; matchedRoleId: string;
} }
const configSchema = z.union([ const configSchema = z.union([zSnowflake, z.array(zSnowflake).max(255)]).default([]);
zSnowflake,
z.array(zSnowflake).max(255),
]).default([]);
export const RoleAddedTrigger = automodTrigger<RoleAddedMatchResult>()({ export const RoleAddedTrigger = automodTrigger<RoleAddedMatchResult>()({
configSchema, configSchema,

View file

@ -8,10 +8,7 @@ interface RoleAddedMatchResult {
matchedRoleId: string; matchedRoleId: string;
} }
const configSchema = z.union([ const configSchema = z.union([zSnowflake, z.array(zSnowflake).max(255)]).default([]);
zSnowflake,
z.array(zSnowflake).max(255),
]).default([]);
export const RoleRemovedTrigger = automodTrigger<RoleAddedMatchResult>()({ export const RoleRemovedTrigger = automodTrigger<RoleAddedMatchResult>()({
configSchema, configSchema,

View file

@ -19,58 +19,62 @@ import { availableTriggers } from "./triggers/availableTriggers";
import Timeout = NodeJS.Timeout; import Timeout = NodeJS.Timeout;
export type ZTriggersMapHelper = { export type ZTriggersMapHelper = {
[TriggerName in keyof typeof availableTriggers]: typeof availableTriggers[TriggerName]["configSchema"]; [TriggerName in keyof typeof availableTriggers]: (typeof availableTriggers)[TriggerName]["configSchema"];
}; };
const zTriggersMap = z.strictObject(entries(availableTriggers).reduce((map, [triggerName, trigger]) => { const zTriggersMap = z
map[triggerName] = trigger.configSchema; .strictObject(
return map; entries(availableTriggers).reduce((map, [triggerName, trigger]) => {
}, {} as ZTriggersMapHelper)).partial(); map[triggerName] = trigger.configSchema;
return map;
}, {} as ZTriggersMapHelper),
)
.partial();
type ZActionsMapHelper = { type ZActionsMapHelper = {
[ActionName in keyof typeof availableActions]: typeof availableActions[ActionName]["configSchema"]; [ActionName in keyof typeof availableActions]: (typeof availableActions)[ActionName]["configSchema"];
}; };
const zActionsMap = z.strictObject(entries(availableActions).reduce((map, [actionName, action]) => { const zActionsMap = z
// @ts-expect-error TS can't infer this properly but it works fine thanks to our helper .strictObject(
map[actionName] = action.configSchema; entries(availableActions).reduce((map, [actionName, action]) => {
return map; // @ts-expect-error TS can't infer this properly but it works fine thanks to our helper
}, {} as ZActionsMapHelper)).partial(); map[actionName] = action.configSchema;
return map;
}, {} as ZActionsMapHelper),
)
.partial();
const zRule = z.strictObject({ const zRule = z.strictObject({
enabled: z.boolean().default(true), enabled: z.boolean().default(true),
// Typed as "never" because you are not expected to supply this directly. // 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. // The transform instead picks it up from the property key and the output type is a string.
name: z.never().optional().transform((_, ctx) => { name: z
const ruleName = String(ctx.path[ctx.path.length - 2]).trim(); .never()
if (! ruleName) { .optional()
ctx.addIssue({ .transform((_, ctx) => {
code: z.ZodIssueCode.custom, const ruleName = String(ctx.path[ctx.path.length - 2]).trim();
message: "Automod rules must have names", if (!ruleName) {
}); ctx.addIssue({
return z.NEVER; code: z.ZodIssueCode.custom,
} message: "Automod rules must have names",
return ruleName; });
}), return z.NEVER;
}
return ruleName;
}),
presets: z.array(z.string().max(100)).max(25).default([]), presets: z.array(z.string().max(100)).max(25).default([]),
affects_bots: z.boolean().default(false), affects_bots: z.boolean().default(false),
affects_self: z.boolean().default(false), affects_self: z.boolean().default(false),
cooldown: zDelayString.nullable().default(null), cooldown: zDelayString.nullable().default(null),
allow_further_rules: z.boolean().default(false), allow_further_rules: z.boolean().default(false),
triggers: z.array(zTriggersMap), triggers: z.array(zTriggersMap),
actions: zActionsMap.refine( actions: zActionsMap.refine((v) => !(v.clean && v.start_thread), {
(v) => ! (v.clean && v.start_thread), message: "Cannot have both clean and start_thread active at the same time",
{ }),
message: "Cannot have both clean and start_thread active at the same time",
}
),
}); });
export type TRule = z.infer<typeof zRule>; export type TRule = z.infer<typeof zRule>;
export const zAutomodConfig = z.strictObject({ export const zAutomodConfig = z.strictObject({
rules: zBoundedRecord( rules: zBoundedRecord(z.record(z.string().max(100), zRule), 0, 255),
z.record(z.string().max(100), zRule),
0,
255,
),
antiraid_levels: z.array(z.string().max(100)).max(10), antiraid_levels: z.array(z.string().max(100)).max(10),
can_set_antiraid: z.boolean(), can_set_antiraid: z.boolean(),
can_view_antiraid: z.boolean(), can_view_antiraid: z.boolean(),

View file

@ -2,13 +2,13 @@ import { ContextMenuCommandInteraction } from "discord.js";
import humanizeDuration from "humanize-duration"; import humanizeDuration from "humanize-duration";
import { GuildPluginData } from "knub"; import { GuildPluginData } from "knub";
import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError"; import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError";
import { canActOn } from "../../../pluginUtils";
import { convertDelayStringToMS } from "../../../utils"; import { convertDelayStringToMS } from "../../../utils";
import { CaseArgs } from "../../Cases/types"; import { CaseArgs } from "../../Cases/types";
import { LogsPlugin } from "../../Logs/LogsPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin";
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
import { MutesPlugin } from "../../Mutes/MutesPlugin"; import { MutesPlugin } from "../../Mutes/MutesPlugin";
import { ContextMenuPluginType } from "../types"; import { ContextMenuPluginType } from "../types";
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
import { canActOn } from "../../../pluginUtils";
export async function muteAction( export async function muteAction(
pluginData: GuildPluginData<ContextMenuPluginType>, pluginData: GuildPluginData<ContextMenuPluginType>,

View file

@ -1,10 +1,7 @@
import { EventEmitter } from "events"; import { EventEmitter } from "events";
import { PluginOptions } from "knub"; import { PluginOptions } from "knub";
import { GuildCounters } from "../../data/GuildCounters"; import { GuildCounters } from "../../data/GuildCounters";
import { import { CounterTrigger, parseCounterConditionString } from "../../data/entities/CounterTrigger";
CounterTrigger,
parseCounterConditionString
} from "../../data/entities/CounterTrigger";
import { mapToPublicFn } from "../../pluginUtils"; import { mapToPublicFn } from "../../pluginUtils";
import { MINUTES, convertDelayStringToMS, values } from "../../utils"; import { MINUTES, convertDelayStringToMS, values } from "../../utils";
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint"; import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";

View file

@ -2,33 +2,45 @@ import { EventEmitter } from "events";
import { BasePluginType } from "knub"; import { BasePluginType } from "knub";
import z from "zod"; import z from "zod";
import { GuildCounters } from "../../data/GuildCounters"; import { GuildCounters } from "../../data/GuildCounters";
import { CounterTrigger, buildCounterConditionString, getReverseCounterComparisonOp, parseCounterConditionString } from "../../data/entities/CounterTrigger"; import {
CounterTrigger,
buildCounterConditionString,
getReverseCounterComparisonOp,
parseCounterConditionString,
} from "../../data/entities/CounterTrigger";
import { zBoundedCharacters, zBoundedRecord, zDelayString } from "../../utils"; import { zBoundedCharacters, zBoundedRecord, zDelayString } from "../../utils";
import Timeout = NodeJS.Timeout; import Timeout = NodeJS.Timeout;
const MAX_COUNTERS = 5; const MAX_COUNTERS = 5;
const MAX_TRIGGERS_PER_COUNTER = 5; const MAX_TRIGGERS_PER_COUNTER = 5;
export const zTrigger = z.strictObject({ export const zTrigger = z
// Dummy type because name gets replaced by the property key in transform() .strictObject({
name: z.never().optional().transform(() => ""), // Dummy type because name gets replaced by the property key in transform()
pretty_name: zBoundedCharacters(0, 100).nullable().default(null), name: z
condition: zBoundedCharacters(1, 64).refine( .never()
(str) => parseCounterConditionString(str) !== null, .optional()
{ message: "Invalid counter trigger condition" }, .transform(() => ""),
), pretty_name: zBoundedCharacters(0, 100).nullable().default(null),
reverse_condition: zBoundedCharacters(1, 64).refine( condition: zBoundedCharacters(1, 64).refine((str) => parseCounterConditionString(str) !== null, {
(str) => parseCounterConditionString(str) !== null, message: "Invalid counter trigger condition",
{ message: "Invalid counter trigger reverse condition" }, }),
).optional(), reverse_condition: zBoundedCharacters(1, 64)
}) .refine((str) => parseCounterConditionString(str) !== null, {
message: "Invalid counter trigger reverse condition",
})
.optional(),
})
.transform((val, ctx) => { .transform((val, ctx) => {
const ruleName = String(ctx.path[ctx.path.length - 2]).trim(); const ruleName = String(ctx.path[ctx.path.length - 2]).trim();
let reverseCondition = val.reverse_condition; let reverseCondition = val.reverse_condition;
if (! reverseCondition) { if (!reverseCondition) {
const parsedCondition = parseCounterConditionString(val.condition)!; const parsedCondition = parseCounterConditionString(val.condition)!;
reverseCondition = buildCounterConditionString(getReverseCounterComparisonOp(parsedCondition[0]), parsedCondition[1]); reverseCondition = buildCounterConditionString(
getReverseCounterComparisonOp(parsedCondition[0]),
parsedCondition[1],
);
} }
return { return {
@ -38,68 +50,65 @@ export const zTrigger = z.strictObject({
}; };
}); });
const zTriggerFromString = zBoundedCharacters(0, 100) const zTriggerFromString = zBoundedCharacters(0, 100).transform((val, ctx) => {
.transform((val, ctx) => { const ruleName = String(ctx.path[ctx.path.length - 2]).trim();
const ruleName = String(ctx.path[ctx.path.length - 2]).trim(); const parsedCondition = parseCounterConditionString(val);
const parsedCondition = parseCounterConditionString(val); if (!parsedCondition) {
if (!parsedCondition) { ctx.addIssue({
ctx.addIssue({ code: z.ZodIssueCode.custom,
code: z.ZodIssueCode.custom, message: "Invalid counter trigger condition",
message: "Invalid counter trigger condition", });
}); return z.NEVER;
return z.NEVER; }
} return {
return { name: ruleName,
name: ruleName, pretty_name: null,
pretty_name: null, condition: buildCounterConditionString(parsedCondition[0], parsedCondition[1]),
condition: buildCounterConditionString(parsedCondition[0], parsedCondition[1]), reverse_condition: buildCounterConditionString(
reverse_condition: buildCounterConditionString(getReverseCounterComparisonOp(parsedCondition[0]), parsedCondition[1]), getReverseCounterComparisonOp(parsedCondition[0]),
}; parsedCondition[1],
}); ),
};
});
const zTriggerInput = z.union([zTrigger, zTriggerFromString]); const zTriggerInput = z.union([zTrigger, zTriggerFromString]);
export const zCounter = z.strictObject({ export const zCounter = z.strictObject({
// Typed as "never" because you are not expected to supply this directly. // 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. // The transform instead picks it up from the property key and the output type is a string.
name: z.never().optional().transform((_, ctx) => { name: z
const ruleName = String(ctx.path[ctx.path.length - 2]).trim(); .never()
if (! ruleName) { .optional()
ctx.addIssue({ .transform((_, ctx) => {
code: z.ZodIssueCode.custom, const ruleName = String(ctx.path[ctx.path.length - 2]).trim();
message: "Counters must have names", if (!ruleName) {
}); ctx.addIssue({
return z.NEVER; code: z.ZodIssueCode.custom,
} message: "Counters must have names",
return ruleName; });
}), return z.NEVER;
}
return ruleName;
}),
pretty_name: zBoundedCharacters(0, 100).nullable().default(null), pretty_name: zBoundedCharacters(0, 100).nullable().default(null),
per_channel: z.boolean().default(false), per_channel: z.boolean().default(false),
per_user: z.boolean().default(false), per_user: z.boolean().default(false),
initial_value: z.number().default(0), initial_value: z.number().default(0),
triggers: zBoundedRecord( triggers: zBoundedRecord(z.record(zBoundedCharacters(0, 100), zTriggerInput), 1, MAX_TRIGGERS_PER_COUNTER),
z.record( decay: z
zBoundedCharacters(0, 100), .strictObject({
zTriggerInput, amount: z.number(),
), every: zDelayString,
1, })
MAX_TRIGGERS_PER_COUNTER, .nullable()
), .default(null),
decay: z.strictObject({
amount: z.number(),
every: zDelayString,
}).nullable().default(null),
can_view: z.boolean().default(false), can_view: z.boolean().default(false),
can_edit: z.boolean().default(false), can_edit: z.boolean().default(false),
can_reset_all: z.boolean().default(false), can_reset_all: z.boolean().default(false),
}); });
export const zCountersConfig = z.strictObject({ export const zCountersConfig = z.strictObject({
counters: zBoundedRecord( counters: zBoundedRecord(z.record(zBoundedCharacters(0, 100), zCounter), 0, MAX_COUNTERS),
z.record(zBoundedCharacters(0, 100), zCounter),
0,
MAX_COUNTERS,
),
can_view: z.boolean(), can_view: z.boolean(),
can_edit: z.boolean(), can_edit: z.boolean(),
can_reset_all: z.boolean(), can_reset_all: z.boolean(),

View file

@ -9,14 +9,16 @@ import { CustomEventsPluginType, TCustomEvent } from "../types";
export const zSetChannelPermissionOverridesAction = z.strictObject({ export const zSetChannelPermissionOverridesAction = z.strictObject({
type: z.literal("set_channel_permission_overrides"), type: z.literal("set_channel_permission_overrides"),
channel: zSnowflake, channel: zSnowflake,
overrides: z.array( overrides: z
z.strictObject({ .array(
type: z.union([z.literal("member"), z.literal("role")]), z.strictObject({
id: zSnowflake, type: z.union([z.literal("member"), z.literal("role")]),
allow: z.number(), id: zSnowflake,
deny: z.number(), allow: z.number(),
}), deny: z.number(),
).max(15), }),
)
.max(15),
}); });
export type TSetChannelPermissionOverridesAction = z.infer<typeof zSetChannelPermissionOverridesAction>; export type TSetChannelPermissionOverridesAction = z.infer<typeof zSetChannelPermissionOverridesAction>;

View file

@ -36,11 +36,7 @@ export const zCustomEvent = z.strictObject({
export type TCustomEvent = z.infer<typeof zCustomEvent>; export type TCustomEvent = z.infer<typeof zCustomEvent>;
export const zCustomEventsConfig = z.strictObject({ export const zCustomEventsConfig = z.strictObject({
events: zBoundedRecord( events: zBoundedRecord(z.record(zBoundedCharacters(0, 100), zCustomEvent), 0, 100),
z.record(zBoundedCharacters(0, 100), zCustomEvent),
0,
100,
),
}); });
export interface CustomEventsPluginType extends BasePluginType { export interface CustomEventsPluginType extends BasePluginType {

View file

@ -1,10 +1,10 @@
import { Guild } from "discord.js"; import { Guild } from "discord.js";
import { BasePluginType, GlobalPluginData, globalPluginEventListener } from "knub"; import { BasePluginType, GlobalPluginData, globalPluginEventListener } from "knub";
import z from "zod";
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 { zeppelinGlobalPlugin } from "../ZeppelinPluginBlueprint"; import { zeppelinGlobalPlugin } from "../ZeppelinPluginBlueprint";
import z from "zod";
interface GuildAccessMonitorPluginType extends BasePluginType { interface GuildAccessMonitorPluginType extends BasePluginType {
state: { state: {

View file

@ -1,11 +1,11 @@
import { Guild } from "discord.js"; import { Guild } from "discord.js";
import { guildPluginEventListener } from "knub"; import { guildPluginEventListener } 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 { 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",

View file

@ -123,7 +123,7 @@ const defaultOptions: PluginOptions<LogsPluginType> = {
timestamp: FORMAT_NO_TIMESTAMP, timestamp: FORMAT_NO_TIMESTAMP,
...DefaultLogMessages, ...DefaultLogMessages,
}, },
ping_user: true, ping_user: true,
allow_user_mentions: false, allow_user_mentions: false,
timestamp_format: "[<t:]X[>]", timestamp_format: "[<t:]X[>]",
include_embed_timestamp: true, include_embed_timestamp: true,

View file

@ -32,7 +32,9 @@ 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 ?? undefined; (config.format.timestamp !== FORMAT_NO_TIMESTAMP ? config.format.timestamp : null) ??
config.timestamp_format ??
undefined;
return log( return log(
pluginData, pluginData,

View file

@ -29,10 +29,12 @@ const MAX_BATCH_TIME = 5000;
type ZLogFormatsHelper = { type ZLogFormatsHelper = {
-readonly [K in keyof typeof LogType]: typeof zMessageContent; -readonly [K in keyof typeof LogType]: typeof zMessageContent;
}; };
export const zLogFormats = z.strictObject(keys(LogType).reduce((map, logType) => { export const zLogFormats = z.strictObject(
map[logType] = zMessageContent; keys(LogType).reduce((map, logType) => {
return map; map[logType] = zMessageContent;
}, {} as ZLogFormatsHelper)); return map;
}, {} as ZLogFormatsHelper),
);
export type TLogFormats = z.infer<typeof zLogFormats>; export type TLogFormats = z.infer<typeof zLogFormats>;
const zLogChannel = z.strictObject({ const zLogChannel = z.strictObject({
@ -58,10 +60,12 @@ export type TLogChannelMap = z.infer<typeof zLogChannelMap>;
export const zLogsConfig = z.strictObject({ export const zLogsConfig = z.strictObject({
channels: zLogChannelMap, channels: zLogChannelMap,
format: zLogFormats.merge(z.strictObject({ format: zLogFormats.merge(
// Legacy/deprecated, use timestamp_format below instead z.strictObject({
timestamp: zBoundedCharacters(0, 64).nullable(), // Legacy/deprecated, use timestamp_format below instead
})), timestamp: zBoundedCharacters(0, 64).nullable(),
}),
),
// Legacy/deprecated, if below is false mentions wont actually ping. In case you really want the old behavior, set below to 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: z.boolean(), ping_user: z.boolean(),
allow_user_mentions: z.boolean(), allow_user_mentions: z.boolean(),

View file

@ -2,6 +2,10 @@ import { GuildPluginData } from "knub";
import { LogType } from "../../../data/LogType"; import { LogType } from "../../../data/LogType";
import { LogsPluginType } from "../types"; import { LogsPluginType } from "../types";
export function isLogIgnored(pluginData: GuildPluginData<LogsPluginType>, type: keyof typeof LogType, ignoreId: string) { export function isLogIgnored(
pluginData: GuildPluginData<LogsPluginType>,
type: keyof typeof LogType,
ignoreId: string,
) {
return pluginData.state.guildLogs.isLogIgnored(type, ignoreId); return pluginData.state.guildLogs.isLogIgnored(type, ignoreId);
} }

View file

@ -3,7 +3,9 @@ import humanizeDuration from "humanize-duration";
import { GuildPluginData } from "knub"; import { GuildPluginData } from "knub";
import moment from "moment-timezone"; import moment from "moment-timezone";
import { CaseTypes } from "../../../data/CaseTypes"; import { CaseTypes } from "../../../data/CaseTypes";
import { LogType } from "../../../data/LogType";
import { Tempban } from "../../../data/entities/Tempban"; import { Tempban } from "../../../data/entities/Tempban";
import { logger } from "../../../logger";
import { resolveUser } from "../../../utils"; import { resolveUser } from "../../../utils";
import { CasesPlugin } from "../../Cases/CasesPlugin"; import { CasesPlugin } from "../../Cases/CasesPlugin";
import { LogsPlugin } from "../../Logs/LogsPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin";
@ -11,8 +13,6 @@ import { IgnoredEventType, ModActionsPluginType } from "../types";
import { formatReasonWithAttachments } from "./formatReasonWithAttachments"; import { formatReasonWithAttachments } from "./formatReasonWithAttachments";
import { ignoreEvent } from "./ignoreEvent"; import { ignoreEvent } from "./ignoreEvent";
import { isBanned } from "./isBanned"; import { isBanned } from "./isBanned";
import { LogType } from "../../../data/LogType";
import { logger } from "../../../logger";
export async function clearTempban(pluginData: GuildPluginData<ModActionsPluginType>, tempban: Tempban) { export async function clearTempban(pluginData: GuildPluginData<ModActionsPluginType>, tempban: Tempban) {
if (!(await isBanned(pluginData, tempban.user_id))) { if (!(await isBanned(pluginData, tempban.user_id))) {

View file

@ -22,10 +22,7 @@ export type PendingMemberRoleChanges = {
}>; }>;
}; };
const zReactionRolePair = z.union([ const zReactionRolePair = z.union([z.tuple([z.string(), z.string(), z.string()]), z.tuple([z.string(), z.string()])]);
z.tuple([z.string(), z.string(), z.string()]),
z.tuple([z.string(), z.string()]),
]);
export type TReactionRolePair = z.infer<typeof zReactionRolePair>; export type TReactionRolePair = z.infer<typeof zReactionRolePair>;
export interface ReactionRolesPluginType extends BasePluginType { export interface ReactionRolesPluginType extends BasePluginType {

View file

@ -11,89 +11,99 @@ const zRoleButtonOption = z.strictObject({
label: z.string().nullable().default(null), label: z.string().nullable().default(null),
emoji: z.string().nullable().default(null), emoji: z.string().nullable().default(null),
// https://discord.js.org/#/docs/discord.js/v13/typedef/MessageButtonStyle // https://discord.js.org/#/docs/discord.js/v13/typedef/MessageButtonStyle
style: z.union([ style: z
z.literal(ButtonStyle.Primary), .union([
z.literal(ButtonStyle.Secondary), z.literal(ButtonStyle.Primary),
z.literal(ButtonStyle.Success), z.literal(ButtonStyle.Secondary),
z.literal(ButtonStyle.Danger), z.literal(ButtonStyle.Success),
z.literal(ButtonStyle.Danger),
// The following are deprecated // The following are deprecated
z.literal("PRIMARY"), z.literal("PRIMARY"),
z.literal("SECONDARY"), z.literal("SECONDARY"),
z.literal("SUCCESS"), z.literal("SUCCESS"),
z.literal("DANGER"), z.literal("DANGER"),
// z.literal("LINK"), // Role buttons don't use link buttons, but adding this here so it's documented why it's not available // z.literal("LINK"), // Role buttons don't use link buttons, but adding this here so it's documented why it's not available
]).nullable().default(null), ])
.nullable()
.default(null),
start_new_row: z.boolean().default(false), start_new_row: z.boolean().default(false),
}); });
export type TRoleButtonOption = z.infer<typeof zRoleButtonOption>; export type TRoleButtonOption = z.infer<typeof zRoleButtonOption>;
const zRoleButtonsConfigItem = z.strictObject({ const zRoleButtonsConfigItem = z
// Typed as "never" because you are not expected to supply this directly. .strictObject({
// The transform instead picks it up from the property key and the output type is a string. // Typed as "never" because you are not expected to supply this directly.
name: z.never().optional().transform((_, ctx) => { // The transform instead picks it up from the property key and the output type is a string.
const ruleName = String(ctx.path[ctx.path.length - 2]).trim(); name: z
if (! ruleName) { .never()
ctx.addIssue({ .optional()
code: z.ZodIssueCode.custom, .transform((_, ctx) => {
message: "Role buttons must have names", const ruleName = String(ctx.path[ctx.path.length - 2]).trim();
}); if (!ruleName) {
return z.NEVER; ctx.addIssue({
} code: z.ZodIssueCode.custom,
return ruleName; message: "Role buttons must have names",
}), });
message: z.union([ return z.NEVER;
z.strictObject({ }
channel_id: zSnowflake, return ruleName;
message_id: zSnowflake, }),
}), message: z.union([
z.strictObject({ z.strictObject({
channel_id: zSnowflake, channel_id: zSnowflake,
content: zMessageContent, message_id: zSnowflake,
}), }),
]), z.strictObject({
options: z.array(zRoleButtonOption).max(25), channel_id: zSnowflake,
exclusive: z.boolean().default(false), content: zMessageContent,
}) }),
.refine((parsed) => { ]),
try { options: z.array(zRoleButtonOption).max(25),
createButtonComponents(parsed); exclusive: z.boolean().default(false),
} catch (err) { })
if (err instanceof TooManyComponentsError) { .refine(
return false; (parsed) => {
try {
createButtonComponents(parsed);
} catch (err) {
if (err instanceof TooManyComponentsError) {
return false;
}
throw err;
} }
throw err; return true;
} },
return true; {
}, { message: "Too many options; can only have max 5 buttons per row on max 5 rows.",
message: "Too many options; can only have max 5 buttons per row on max 5 rows." },
}); );
export type TRoleButtonsConfigItem = z.infer<typeof zRoleButtonsConfigItem>; export type TRoleButtonsConfigItem = z.infer<typeof zRoleButtonsConfigItem>;
export const zRoleButtonsConfig = z.strictObject({ export const zRoleButtonsConfig = z
buttons: zBoundedRecord( .strictObject({
z.record(zBoundedCharacters(1, 16), zRoleButtonsConfigItem), buttons: zBoundedRecord(z.record(zBoundedCharacters(1, 16), zRoleButtonsConfigItem), 0, 100),
0, can_reset: z.boolean(),
100, })
), .refine(
can_reset: z.boolean(), (parsed) => {
}) const seenMessages = new Set();
.refine((parsed) => { for (const button of Object.values(parsed.buttons)) {
const seenMessages = new Set(); if (button.message) {
for (const button of Object.values(parsed.buttons)) { if ("message_id" in button.message) {
if (button.message) { if (seenMessages.has(button.message.message_id)) {
if ("message_id" in button.message) { return false;
if (seenMessages.has(button.message.message_id)) { }
return false; seenMessages.add(button.message.message_id);
} }
seenMessages.add(button.message.message_id);
} }
} }
} return true;
return true; },
}, { {
message: "Can't target the same message with two sets of role buttons", message: "Can't target the same message with two sets of role buttons",
}); },
);
export interface RoleButtonsPluginType extends BasePluginType { export interface RoleButtonsPluginType extends BasePluginType {
config: z.infer<typeof zRoleButtonsConfig>; config: z.infer<typeof zRoleButtonsConfig>;

View file

@ -4,9 +4,10 @@ import { zBoundedCharacters, zBoundedRecord } from "../../utils";
const zRoleMap = z.record( const zRoleMap = z.record(
zBoundedCharacters(1, 100), zBoundedCharacters(1, 100),
z.array(zBoundedCharacters(1, 2000)) z
.array(zBoundedCharacters(1, 2000))
.max(100) .max(100)
.transform((parsed) => parsed.map(v => v.toLowerCase())), .transform((parsed) => parsed.map((v) => v.toLowerCase())),
); );
const zSelfGrantableRoleEntry = z.strictObject({ const zSelfGrantableRoleEntry = z.strictObject({

View file

@ -7,7 +7,7 @@ import { SlowmodeChannel } from "../../data/entities/SlowmodeChannel";
export const zSlowmodeConfig = z.strictObject({ export const zSlowmodeConfig = z.strictObject({
use_native_slowmode: z.boolean(), use_native_slowmode: z.boolean(),
can_manage: z.boolean(), can_manage: z.boolean(),
is_affected: z.boolean(), is_affected: z.boolean(),
}); });

View file

@ -11,14 +11,8 @@ const zBaseSingleSpamConfig = z.strictObject({
count: z.number(), count: z.number(),
mute: z.boolean().default(false), mute: z.boolean().default(false),
mute_time: z.number().nullable().default(null), mute_time: z.number().nullable().default(null),
remove_roles_on_mute: z.union([ remove_roles_on_mute: z.union([z.boolean(), z.array(zSnowflake)]).default(false),
z.boolean(), restore_roles_on_mute: z.union([z.boolean(), z.array(zSnowflake)]).default(false),
z.array(zSnowflake),
]).default(false),
restore_roles_on_mute: z.union([
z.boolean(),
z.array(zSnowflake),
]).default(false),
clean: z.boolean().default(false), clean: z.boolean().default(false),
}); });
export type TBaseSingleSpamConfig = z.infer<typeof zBaseSingleSpamConfig>; export type TBaseSingleSpamConfig = z.infer<typeof zBaseSingleSpamConfig>;

View file

@ -18,11 +18,7 @@ const zStarboardOpts = z.strictObject({
export type TStarboardOpts = z.infer<typeof zStarboardOpts>; export type TStarboardOpts = z.infer<typeof zStarboardOpts>;
export const zStarboardConfig = z.strictObject({ export const zStarboardConfig = z.strictObject({
boards: zBoundedRecord( boards: zBoundedRecord(z.record(z.string(), zStarboardOpts), 0, 100),
z.record(z.string(), zStarboardOpts),
0,
100,
),
can_migrate: z.boolean(), can_migrate: z.boolean(),
}); });

View file

@ -1,11 +1,11 @@
import { MessageCreateOptions } from "discord.js"; import { MessageCreateOptions } from "discord.js";
import { commandTypeHelpers as ct } from "../../../commandTypes"; import { commandTypeHelpers as ct } from "../../../commandTypes";
import { logger } from "../../../logger";
import { sendErrorMessage } from "../../../pluginUtils"; import { sendErrorMessage } from "../../../pluginUtils";
import { TemplateParseError } from "../../../templateFormatter"; import { TemplateParseError } from "../../../templateFormatter";
import { memberToTemplateSafeMember, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { memberToTemplateSafeMember, userToTemplateSafeUser } from "../../../utils/templateSafeObjects";
import { tagsCmd } from "../types"; import { tagsCmd } from "../types";
import { renderTagBody } from "../util/renderTagBody"; import { renderTagBody } from "../util/renderTagBody";
import { logger } from "../../../logger";
export const TagEvalCmd = tagsCmd({ export const TagEvalCmd = tagsCmd({
trigger: "tag eval", trigger: "tag eval",
@ -35,13 +35,11 @@ export const TagEvalCmd = tagsCmd({
msg.channel.send(rendered); msg.channel.send(rendered);
} catch (e) { } catch (e) {
const errorMessage = e instanceof TemplateParseError const errorMessage = e instanceof TemplateParseError ? e.message : "Internal error";
? e.message
: "Internal error";
sendErrorMessage(pluginData, msg.channel, `Failed to render tag: ${errorMessage}`); sendErrorMessage(pluginData, msg.channel, `Failed to render tag: ${errorMessage}`);
if (! (e instanceof TemplateParseError)) { if (!(e instanceof TemplateParseError)) {
logger.warn(`Internal error evaluating tag in ${pluginData.guild.id}: ${e}`); logger.warn(`Internal error evaluating tag in ${pluginData.guild.id}: ${e}`);
} }

View file

@ -9,52 +9,48 @@ import { zEmbedInput } from "../../utils";
export const zTag = z.union([z.string(), zEmbedInput]); export const zTag = z.union([z.string(), zEmbedInput]);
export type TTag = z.infer<typeof zTag>; export type TTag = z.infer<typeof zTag>;
export const zTagCategory = z.strictObject({ export const zTagCategory = z
prefix: z.string().nullable().default(null), .strictObject({
delete_with_command: z.boolean().default(false), prefix: z.string().nullable().default(null),
delete_with_command: z.boolean().default(false),
user_tag_cooldown: z.union([z.string(), z.number()]).nullable().default(null), // Per user, per tag user_tag_cooldown: z.union([z.string(), z.number()]).nullable().default(null), // Per user, per tag
user_category_cooldown: z.union([z.string(), z.number()]).nullable().default(null), // Per user, per tag category user_category_cooldown: z.union([z.string(), z.number()]).nullable().default(null), // Per user, per tag category
global_tag_cooldown: z.union([z.string(), z.number()]).nullable().default(null), // Any user, per tag global_tag_cooldown: z.union([z.string(), z.number()]).nullable().default(null), // Any user, per tag
allow_mentions: z.boolean().nullable().default(null), allow_mentions: z.boolean().nullable().default(null),
global_category_cooldown: z.union([z.string(), z.number()]).nullable().default(null), // Any user, per category global_category_cooldown: z.union([z.string(), z.number()]).nullable().default(null), // Any user, per category
auto_delete_command: z.boolean().nullable().default(null), auto_delete_command: z.boolean().nullable().default(null),
tags: z.record(z.string(), zTag), tags: z.record(z.string(), zTag),
can_use: z.boolean().nullable().default(null), can_use: z.boolean().nullable().default(null),
}) })
.refine( .refine((parsed) => !(parsed.auto_delete_command && parsed.delete_with_command), {
(parsed) => ! (parsed.auto_delete_command && parsed.delete_with_command), message: "Cannot have both (category specific) delete_with_command and auto_delete_command enabled",
{ });
message: "Cannot have both (category specific) delete_with_command and auto_delete_command enabled",
},
);
export type TTagCategory = z.infer<typeof zTagCategory>; export type TTagCategory = z.infer<typeof zTagCategory>;
export const zTagsConfig = z.strictObject({ export const zTagsConfig = z
prefix: z.string(), .strictObject({
delete_with_command: z.boolean(), prefix: z.string(),
delete_with_command: z.boolean(),
user_tag_cooldown: z.union([z.string(), z.number()]).nullable(), // Per user, per tag user_tag_cooldown: z.union([z.string(), z.number()]).nullable(), // Per user, per tag
global_tag_cooldown: z.union([z.string(), z.number()]).nullable(), // Any user, per tag global_tag_cooldown: z.union([z.string(), z.number()]).nullable(), // Any user, per tag
user_cooldown: z.union([z.string(), z.number()]).nullable(), // Per user user_cooldown: z.union([z.string(), z.number()]).nullable(), // Per user
allow_mentions: z.boolean(), // Per user allow_mentions: z.boolean(), // Per user
global_cooldown: z.union([z.string(), z.number()]).nullable(), // Any tag use global_cooldown: z.union([z.string(), z.number()]).nullable(), // Any tag use
auto_delete_command: z.boolean(), // Any tag auto_delete_command: z.boolean(), // Any tag
categories: z.record(z.string(), zTagCategory), categories: z.record(z.string(), zTagCategory),
can_create: z.boolean(), can_create: z.boolean(),
can_use: z.boolean(), can_use: z.boolean(),
can_list: z.boolean(), can_list: z.boolean(),
}) })
.refine( .refine((parsed) => !(parsed.auto_delete_command && parsed.delete_with_command), {
(parsed) => ! (parsed.auto_delete_command && parsed.delete_with_command),
{
message: "Cannot have both (category specific) delete_with_command and auto_delete_command enabled", message: "Cannot have both (category specific) delete_with_command and auto_delete_command enabled",
}, });
);
export interface TagsPluginType extends BasePluginType { export interface TagsPluginType extends BasePluginType {
config: z.infer<typeof zTagsConfig>; config: z.infer<typeof zTagsConfig>;

View file

@ -2,11 +2,11 @@ import { Snowflake, TextChannel } from "discord.js";
import { GuildPluginData } from "knub"; import { GuildPluginData } from "knub";
import { SavedMessage } from "../../../data/entities/SavedMessage"; import { SavedMessage } from "../../../data/entities/SavedMessage";
import { convertDelayStringToMS, resolveMember, zStrictMessageContent } from "../../../utils"; import { convertDelayStringToMS, resolveMember, zStrictMessageContent } from "../../../utils";
import { erisAllowedMentionsToDjsMentionOptions } from "../../../utils/erisAllowedMentionsToDjsMentionOptions";
import { messageIsEmpty } from "../../../utils/messageIsEmpty"; import { messageIsEmpty } from "../../../utils/messageIsEmpty";
import { LogsPlugin } from "../../Logs/LogsPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin";
import { TagsPluginType } from "../types"; import { TagsPluginType } from "../types";
import { matchAndRenderTagFromString } from "./matchAndRenderTagFromString"; import { matchAndRenderTagFromString } from "./matchAndRenderTagFromString";
import { erisAllowedMentionsToDjsMentionOptions } from "../../../utils/erisAllowedMentionsToDjsMentionOptions";
export async function onMessageCreate(pluginData: GuildPluginData<TagsPluginType>, msg: SavedMessage) { export async function onMessageCreate(pluginData: GuildPluginData<TagsPluginType>, msg: SavedMessage) {
if (msg.is_bot) return; if (msg.is_bot) return;
@ -85,7 +85,7 @@ export async function onMessageCreate(pluginData: GuildPluginData<TagsPluginType
} }
const validated = zStrictMessageContent.safeParse(tagResult.renderedContent); const validated = zStrictMessageContent.safeParse(tagResult.renderedContent);
if (! validated.success) { if (!validated.success) {
pluginData.getPlugin(LogsPlugin).logBotAlert({ pluginData.getPlugin(LogsPlugin).logBotAlert({
body: `Rendering tag ${tagResult.tagName} resulted in an invalid message: ${validated.error.message}`, body: `Rendering tag ${tagResult.tagName} resulted in an invalid message: ${validated.error.message}`,
}); });

View file

@ -1,13 +1,13 @@
import { GuildMember } from "discord.js"; import { GuildMember } from "discord.js";
import { GuildPluginData } from "knub"; import { GuildPluginData } from "knub";
import { parseArguments } from "knub-command-manager"; import { parseArguments } from "knub-command-manager";
import { logger } from "../../../logger";
import { TemplateParseError } from "../../../templateFormatter"; import { TemplateParseError } from "../../../templateFormatter";
import { StrictMessageContent, validateAndParseMessageContent } from "../../../utils"; import { StrictMessageContent, validateAndParseMessageContent } from "../../../utils";
import { memberToTemplateSafeMember, userToTemplateSafeUser } from "../../../utils/templateSafeObjects"; import { memberToTemplateSafeMember, userToTemplateSafeUser } from "../../../utils/templateSafeObjects";
import { LogsPlugin } from "../../Logs/LogsPlugin"; import { LogsPlugin } from "../../Logs/LogsPlugin";
import { TTag, TagsPluginType } from "../types"; import { TTag, TagsPluginType } from "../types";
import { renderTagBody } from "./renderTagBody"; import { renderTagBody } from "./renderTagBody";
import { logger } from "../../../logger";
export async function renderTagFromString( export async function renderTagFromString(
pluginData: GuildPluginData<TagsPluginType>, pluginData: GuildPluginData<TagsPluginType>,
@ -36,14 +36,12 @@ export async function renderTagFromString(
return validateAndParseMessageContent(rendered); return validateAndParseMessageContent(rendered);
} catch (e) { } catch (e) {
const logs = pluginData.getPlugin(LogsPlugin); const logs = pluginData.getPlugin(LogsPlugin);
const errorMessage = e instanceof TemplateParseError const errorMessage = e instanceof TemplateParseError ? e.message : "Internal error";
? e.message
: "Internal error";
logs.logBotAlert({ logs.logBotAlert({
body: `Failed to render tag \`${prefix}${tagName}\`: ${errorMessage}`, body: `Failed to render tag \`${prefix}${tagName}\`: ${errorMessage}`,
}); });
if (! (e instanceof TemplateParseError)) { if (!(e instanceof TemplateParseError)) {
logger.warn(`Internal error rendering tag ${tagName} in ${pluginData.guild.id}: ${e}`); logger.warn(`Internal error rendering tag ${tagName} in ${pluginData.guild.id}: ${e}`);
} }

View file

@ -79,11 +79,7 @@ export const InfoCmd = utilityCmd({
const messageTarget = await resolveMessageTarget(pluginData, value); const messageTarget = await resolveMessageTarget(pluginData, value);
if (messageTarget) { if (messageTarget) {
if (canReadChannel(messageTarget.channel, message.member)) { if (canReadChannel(messageTarget.channel, message.member)) {
const embed = await getMessageInfoEmbed( const embed = await getMessageInfoEmbed(pluginData, messageTarget.channel.id, messageTarget.messageId);
pluginData,
messageTarget.channel.id,
messageTarget.messageId,
);
if (embed) { if (embed) {
message.channel.send({ embeds: [embed] }); message.channel.send({ embeds: [embed] });
return; return;

View file

@ -20,11 +20,7 @@ export const MessageInfoCmd = utilityCmd({
return; return;
} }
const embed = await getMessageInfoEmbed( const embed = await getMessageInfoEmbed(pluginData, args.message.channel.id, args.message.messageId);
pluginData,
args.message.channel.id,
args.message.messageId,
);
if (!embed) { if (!embed) {
sendErrorMessage(pluginData, message.channel, "Unknown message"); sendErrorMessage(pluginData, message.channel, "Unknown message");
return; return;

View file

@ -6,10 +6,7 @@ import { UtilityPluginType } from "../types";
const MENTION_ICON = "https://cdn.discordapp.com/attachments/705009450855039042/839284872152481792/mention.png"; const MENTION_ICON = "https://cdn.discordapp.com/attachments/705009450855039042/839284872152481792/mention.png";
export async function getRoleInfoEmbed( export async function getRoleInfoEmbed(pluginData: GuildPluginData<UtilityPluginType>, role: Role): Promise<APIEmbed> {
pluginData: GuildPluginData<UtilityPluginType>,
role: Role,
): Promise<APIEmbed> {
const embed: EmbedWith<"fields" | "author" | "color"> = { const embed: EmbedWith<"fields" | "author" | "color"> = {
fields: [], fields: [],
author: { author: {

View file

@ -4,10 +4,7 @@ import { snowflakeToTimestamp } from "../../../utils/snowflakeToTimestamp";
const SNOWFLAKE_ICON = "https://cdn.discordapp.com/attachments/740650744830623756/742020790471491668/snowflake.png"; const SNOWFLAKE_ICON = "https://cdn.discordapp.com/attachments/740650744830623756/742020790471491668/snowflake.png";
export async function getSnowflakeInfoEmbed( export async function getSnowflakeInfoEmbed(snowflake: string, showUnknownWarning = false): Promise<APIEmbed> {
snowflake: string,
showUnknownWarning = false,
): Promise<APIEmbed> {
const embed: EmbedWith<"fields" | "author"> = { const embed: EmbedWith<"fields" | "author"> = {
fields: [], fields: [],
author: { author: {

View file

@ -14,7 +14,15 @@ import { ArgsFromSignatureOrArray, GuildPluginData } from "knub";
import moment from "moment-timezone"; import moment from "moment-timezone";
import { RegExpRunner, allowTimeout } from "../../RegExpRunner"; import { RegExpRunner, allowTimeout } from "../../RegExpRunner";
import { getBaseUrl, sendErrorMessage } from "../../pluginUtils"; import { getBaseUrl, sendErrorMessage } from "../../pluginUtils";
import { InvalidRegexError, MINUTES, inputPatternToRegExp, multiSorter, renderUserUsername, sorter, trimLines } from "../../utils"; import {
InvalidRegexError,
MINUTES,
inputPatternToRegExp,
multiSorter,
renderUserUsername,
sorter,
trimLines,
} from "../../utils";
import { asyncFilter } from "../../utils/async"; import { asyncFilter } from "../../utils/async";
import { hasDiscordPermissions } from "../../utils/hasDiscordPermissions"; import { hasDiscordPermissions } from "../../utils/hasDiscordPermissions";
import { banSearchSignature } from "./commands/BanSearchCmd"; import { banSearchSignature } from "./commands/BanSearchCmd";

View file

@ -7,8 +7,8 @@ import {
GuildPluginBlueprint, GuildPluginBlueprint,
GuildPluginData, GuildPluginData,
} from "knub"; } from "knub";
import { TMarkdown } from "../types";
import { ZodTypeAny } from "zod"; import { ZodTypeAny } from "zod";
import { TMarkdown } from "../types";
/** /**
* GUILD PLUGINS * GUILD PLUGINS

View file

@ -87,8 +87,10 @@ export function isDiscordAPIError(err: Error | string): err is DiscordAPIError {
} }
// null | undefined -> undefined // null | undefined -> undefined
export function zNullishToUndefined<T extends z.ZodTypeAny>(type: T): ZodEffects<T, NonNullable<z.output<T>> | undefined> { export function zNullishToUndefined<T extends z.ZodTypeAny>(
return type.transform(v => v ?? undefined); type: T,
): ZodEffects<T, NonNullable<z.output<T>> | undefined> {
return type.transform((v) => v ?? undefined);
} }
export function getScalarDifference<T extends object>( export function getScalarDifference<T extends object>(
@ -151,15 +153,18 @@ export type GroupDMInvite = Invite & {
}; };
export function zBoundedCharacters(min: number, max: number) { export function zBoundedCharacters(min: number, max: number) {
return z.string().refine(str => { return z.string().refine(
const len = [...str].length; // Unicode aware character split (str) => {
return (len >= min && len <= max); const len = [...str].length; // Unicode aware character split
}, { return len >= min && len <= max;
message: `String must be between ${min} and ${max} characters long`, },
}); {
message: `String must be between ${min} and ${max} characters long`,
},
);
} }
export const zSnowflake = z.string().refine(str => isSnowflake(str), { export const zSnowflake = z.string().refine((str) => isSnowflake(str), {
message: "Invalid snowflake ID", message: "Invalid snowflake ID",
}); });
@ -188,7 +193,7 @@ export function zRegex<T extends ZodString>(zStr: T) {
if (err instanceof InvalidRegexError) { if (err instanceof InvalidRegexError) {
ctx.addIssue({ ctx.addIssue({
code: z.ZodIssueCode.custom, code: z.ZodIssueCode.custom,
message: "Invalid regex" message: "Invalid regex",
}); });
return z.NEVER; return z.NEVER;
} }
@ -343,8 +348,18 @@ function dropNullValuesRecursively(obj: any) {
*/ */
export const zAllowedMentions = z.strictObject({ export const zAllowedMentions = z.strictObject({
everyone: zNullishToUndefined(z.boolean().nullable().optional()), everyone: zNullishToUndefined(z.boolean().nullable().optional()),
users: zNullishToUndefined(z.union([z.boolean(), z.array(z.string())]).nullable().optional()), users: zNullishToUndefined(
roles: zNullishToUndefined(z.union([z.boolean(), z.array(z.string())]).nullable().optional()), z
.union([z.boolean(), z.array(z.string())])
.nullable()
.optional(),
),
roles: zNullishToUndefined(
z
.union([z.boolean(), z.array(z.string())])
.nullable()
.optional(),
),
replied_user: zNullishToUndefined(z.boolean().nullable().optional()), replied_user: zNullishToUndefined(z.boolean().nullable().optional()),
}); });
@ -359,18 +374,28 @@ export function dropPropertiesByName(obj, propName) {
} }
} }
export function zBoundedRecord<TRecord extends ZodRecord<any, any>>(record: TRecord, minKeys: number, maxKeys: number): ZodEffects<TRecord> { export function zBoundedRecord<TRecord extends ZodRecord<any, any>>(
return record.refine(data => { record: TRecord,
const len = Object.keys(data).length; minKeys: number,
return (len >= minKeys && len <= maxKeys); maxKeys: number,
}, { ): ZodEffects<TRecord> {
message: `Object must have ${minKeys}-${maxKeys} keys`, return record.refine(
}); (data) => {
const len = Object.keys(data).length;
return len >= minKeys && len <= maxKeys;
},
{
message: `Object must have ${minKeys}-${maxKeys} keys`,
},
);
} }
export const zDelayString = z.string().max(32).refine(str => convertDelayStringToMS(str) !== null, { export const zDelayString = z
message: "Invalid delay string", .string()
}); .max(32)
.refine((str) => convertDelayStringToMS(str) !== null, {
message: "Invalid delay string",
});
// To avoid running into issues with the JS max date vaLue, we cap maximum delay strings *far* below that. // To avoid running into issues with the JS max date vaLue, we cap maximum delay strings *far* below that.
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#The_ECMAScript_epoch_and_timestamps // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#The_ECMAScript_epoch_and_timestamps
@ -1487,9 +1512,11 @@ export function renderUserUsername(user: User | UnknownUser): string {
return renderUsername(user.username, user.discriminator); return renderUsername(user.username, user.discriminator);
} }
type Entries<T> = Array<{ type Entries<T> = Array<
[Key in keyof T]-?: [Key, T[Key]]; {
}[keyof T]>; [Key in keyof T]-?: [Key, T[Key]];
}[keyof T]
>;
export function entries<T extends object>(object: T) { export function entries<T extends object>(object: T) {
return Object.entries(object) as Entries<T>; return Object.entries(object) as Entries<T>;

View file

@ -1,6 +1,6 @@
import { ZodIssue } from "zod"; import { ZodIssue } from "zod";
export function formatZodIssue(issue: ZodIssue): string { export function formatZodIssue(issue: ZodIssue): string {
const path = issue.path.join("/"); const path = issue.path.join("/");
return `${path}: ${issue.message}`; return `${path}: ${issue.message}`;
} }

View file

@ -6,7 +6,10 @@ import { loadYamlSafely } from "./utils/loadYamlSafely";
import { ObjectAliasError } from "./utils/validateNoObjectAliases"; import { ObjectAliasError } from "./utils/validateNoObjectAliases";
function writeError(key: string, error: string) { function writeError(key: string, error: string) {
const indented = error.split("\n").map(s => " ".repeat(64) + s).join("\n"); const indented = error
.split("\n")
.map((s) => " ".repeat(64) + s)
.join("\n");
const prefix = `Invalid config ${key}:`; const prefix = `Invalid config ${key}:`;
const prefixed = prefix + indented.slice(prefix.length); const prefixed = prefix + indented.slice(prefix.length);
console.log(prefixed + "\n\n"); console.log(prefixed + "\n\n");