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 { renderUsername, trimLines } from "../utils";
import { decrypt, encrypt } from "../utils/crypt";
import { isDefaultSticker } from "../utils/isDefaultSticker";
import { channelToTemplateSafeChannel, guildToTemplateSafeGuild } from "../utils/templateSafeObjects";
import { BaseGuildRepository } from "./BaseGuildRepository";
import { dataSource } from "./dataSource";
import { ArchiveEntry } from "./entities/ArchiveEntry";
import { SavedMessage } from "./entities/SavedMessage";
import { isDefaultSticker } from "../utils/isDefaultSticker";
const DEFAULT_EXPIRY_DAYS = 30;

View file

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

View file

@ -10,13 +10,7 @@ import {
PermissionsBitField,
TextBasedChannel,
} from "discord.js";
import {
AnyPluginData,
CommandContext,
ExtendedMatchParams,
GuildPluginData,
helpers
} from "knub";
import { AnyPluginData, CommandContext, ExtendedMatchParams, GuildPluginData, helpers } from "knub";
import { logger } from "./logger";
import { isStaff } from "./staff";
import { TZeppelinKnub } from "./types";

View file

@ -1,10 +1,10 @@
import { BasePluginType } from "knub";
import z from "zod";
import { GuildLogs } from "../../data/GuildLogs";
import { GuildSavedMessages } from "../../data/GuildSavedMessages";
import { SavedMessage } from "../../data/entities/SavedMessage";
import { MINUTES, zDelayString } from "../../utils";
import Timeout = NodeJS.Timeout;
import z from "zod";
export const MAX_DELAY = 5 * MINUTES;

View file

@ -1,9 +1,9 @@
import { CooldownManager } from "knub";
import { Queue } from "../../Queue";
import { GuildAntiraidLevels } from "../../data/GuildAntiraidLevels";
import { GuildArchives } from "../../data/GuildArchives";
import { GuildLogs } from "../../data/GuildLogs";
import { GuildSavedMessages } from "../../data/GuildSavedMessages";
import { Queue } from "../../Queue";
import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners";
import { MINUTES, SECONDS } from "../../utils";
import { registerEventListenersFromMap } from "../../utils/registerEventListenersFromMap";
@ -19,9 +19,9 @@ import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
import { AntiraidClearCmd } from "./commands/AntiraidClearCmd";
import { SetAntiraidCmd } from "./commands/SetAntiraidCmd";
import { ViewAntiraidCmd } from "./commands/ViewAntiraidCmd";
import { runAutomodOnCounterTrigger } from "./events/runAutomodOnCounterTrigger";
import { RunAutomodOnJoinEvt, RunAutomodOnLeaveEvt } from "./events/RunAutomodOnJoinLeaveEvt";
import { RunAutomodOnMemberUpdate } from "./events/RunAutomodOnMemberUpdate";
import { runAutomodOnCounterTrigger } from "./events/runAutomodOnCounterTrigger";
import { runAutomodOnMessage } from "./events/runAutomodOnMessage";
import { runAutomodOnModAction } from "./events/runAutomodOnModAction";
import {

View file

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

View file

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

View file

@ -68,10 +68,7 @@ export const ChangePermsAction = automodAction({
configSchema: z.strictObject({
target: zBoundedCharacters(1, 2000),
channel: zBoundedCharacters(1, 2000).nullable().default(null),
perms: z.record(
z.enum(allPermissionNames),
z.boolean().nullable(),
),
perms: z.record(z.enum(allPermissionNames), z.boolean().nullable()),
}),
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 { CaseArgs } from "../../Cases/types";
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
import { zNotify } from "../constants";
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
import { automodAction } from "../helpers";
import { zNotify } from "../constants";
export const KickAction = automodAction({
configSchema: z.strictObject({

View file

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

View file

@ -10,7 +10,7 @@ import {
verboseChannelMention,
zBoundedCharacters,
zDelayString,
zMessageContent
zMessageContent,
} from "../../../utils";
import { hasDiscordPermissions } from "../../../utils/hasDiscordPermissions";
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 { CaseArgs } from "../../Cases/types";
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
import { zNotify } from "../constants";
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
import { automodAction } from "../helpers";
import { zNotify } from "../constants";
export const WarnAction = automodAction({
configSchema: z.strictObject({

View file

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

View file

@ -1,3 +1,4 @@
import z from "zod";
import { SavedMessage } from "../../../data/entities/SavedMessage";
import { humanizeDurationShort } from "../../../humanizeDurationShort";
import { getBaseUrl } from "../../../pluginUtils";
@ -7,7 +8,6 @@ import { automodTrigger } from "../helpers";
import { findRecentSpam } from "./findRecentSpam";
import { getMatchingMessageRecentActions } from "./getMatchingMessageRecentActions";
import { getMessageSpamIdentifier } from "./getSpamIdentifier";
import z from "zod";
interface TMessageSpamMatchResultType {
archiveId: string;

View file

@ -1,7 +1,7 @@
import { GuildPluginData } from "knub";
import z, { ZodTypeAny } from "zod";
import { Awaitable } from "../../utils/typeUtils";
import { AutomodContext, AutomodPluginType } from "./types";
import z, { ZodTypeAny } from "zod";
interface BaseAutomodTriggerMatchResult {
extraContexts?: AutomodContext[];

View file

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

View file

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

View file

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

View file

@ -26,12 +26,20 @@ const configSchema = z.strictObject({
include_subdomains: z.boolean().default(true),
include_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(),
exclude_regex: z.array(zRegex(z.string().max(2000))).max(512).optional(),
phisherman: z.strictObject({
include_regex: z
.array(zRegex(z.string().max(2000)))
.max(512)
.optional(),
exclude_regex: z
.array(zRegex(z.string().max(2000)))
.max(512)
.optional(),
phisherman: z
.strictObject({
include_suspected: z.boolean().optional(),
include_verified: z.boolean().optional(),
}).optional(),
})
.optional(),
only_real_links: z.boolean().default(true),
match_messages: z.boolean().default(true),
match_embeds: z.boolean().default(true),

View file

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

View file

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

View file

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

View file

@ -19,29 +19,40 @@ import { availableTriggers } from "./triggers/availableTriggers";
import Timeout = NodeJS.Timeout;
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
.strictObject(
entries(availableTriggers).reduce((map, [triggerName, trigger]) => {
map[triggerName] = trigger.configSchema;
return map;
}, {} as ZTriggersMapHelper)).partial();
}, {} as ZTriggersMapHelper),
)
.partial();
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
.strictObject(
entries(availableActions).reduce((map, [actionName, action]) => {
// @ts-expect-error TS can't infer this properly but it works fine thanks to our helper
map[actionName] = action.configSchema;
return map;
}, {} as ZActionsMapHelper)).partial();
}, {} 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) => {
name: z
.never()
.optional()
.transform((_, ctx) => {
const ruleName = String(ctx.path[ctx.path.length - 2]).trim();
if (! ruleName) {
if (!ruleName) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Automod rules must have names",
@ -56,21 +67,14 @@ const zRule = z.strictObject({
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),
{
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 zAutomodConfig = z.strictObject({
rules: zBoundedRecord(
z.record(z.string().max(100), zRule),
0,
255,
),
rules: zBoundedRecord(z.record(z.string().max(100), zRule), 0, 255),
antiraid_levels: z.array(z.string().max(100)).max(10),
can_set_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 { GuildPluginData } from "knub";
import { ERRORS, RecoverablePluginError } from "../../../RecoverablePluginError";
import { canActOn } from "../../../pluginUtils";
import { convertDelayStringToMS } from "../../../utils";
import { CaseArgs } from "../../Cases/types";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
import { MutesPlugin } from "../../Mutes/MutesPlugin";
import { ContextMenuPluginType } from "../types";
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
import { canActOn } from "../../../pluginUtils";
export async function muteAction(
pluginData: GuildPluginData<ContextMenuPluginType>,

View file

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

View file

@ -2,33 +2,45 @@ import { EventEmitter } from "events";
import { BasePluginType } from "knub";
import z from "zod";
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 Timeout = NodeJS.Timeout;
const MAX_COUNTERS = 5;
const MAX_TRIGGERS_PER_COUNTER = 5;
export const zTrigger = z.strictObject({
export const zTrigger = z
.strictObject({
// Dummy type because name gets replaced by the property key in transform()
name: z.never().optional().transform(() => ""),
name: z
.never()
.optional()
.transform(() => ""),
pretty_name: zBoundedCharacters(0, 100).nullable().default(null),
condition: zBoundedCharacters(1, 64).refine(
(str) => parseCounterConditionString(str) !== null,
{ message: "Invalid counter trigger condition" },
),
reverse_condition: zBoundedCharacters(1, 64).refine(
(str) => parseCounterConditionString(str) !== null,
{ message: "Invalid counter trigger reverse condition" },
).optional(),
})
condition: zBoundedCharacters(1, 64).refine((str) => parseCounterConditionString(str) !== null, {
message: "Invalid counter trigger condition",
}),
reverse_condition: zBoundedCharacters(1, 64)
.refine((str) => parseCounterConditionString(str) !== null, {
message: "Invalid counter trigger reverse condition",
})
.optional(),
})
.transform((val, ctx) => {
const ruleName = String(ctx.path[ctx.path.length - 2]).trim();
let reverseCondition = val.reverse_condition;
if (! reverseCondition) {
if (!reverseCondition) {
const parsedCondition = parseCounterConditionString(val.condition)!;
reverseCondition = buildCounterConditionString(getReverseCounterComparisonOp(parsedCondition[0]), parsedCondition[1]);
reverseCondition = buildCounterConditionString(
getReverseCounterComparisonOp(parsedCondition[0]),
parsedCondition[1],
);
}
return {
@ -38,8 +50,7 @@ export const zTrigger = z.strictObject({
};
});
const zTriggerFromString = zBoundedCharacters(0, 100)
.transform((val, ctx) => {
const zTriggerFromString = zBoundedCharacters(0, 100).transform((val, ctx) => {
const ruleName = String(ctx.path[ctx.path.length - 2]).trim();
const parsedCondition = parseCounterConditionString(val);
if (!parsedCondition) {
@ -53,18 +64,24 @@ const zTriggerFromString = zBoundedCharacters(0, 100)
name: ruleName,
pretty_name: null,
condition: buildCounterConditionString(parsedCondition[0], parsedCondition[1]),
reverse_condition: buildCounterConditionString(getReverseCounterComparisonOp(parsedCondition[0]), parsedCondition[1]),
reverse_condition: buildCounterConditionString(
getReverseCounterComparisonOp(parsedCondition[0]),
parsedCondition[1],
),
};
});
});
const zTriggerInput = z.union([zTrigger, zTriggerFromString]);
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) => {
name: z
.never()
.optional()
.transform((_, ctx) => {
const ruleName = String(ctx.path[ctx.path.length - 2]).trim();
if (! ruleName) {
if (!ruleName) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Counters must have names",
@ -77,29 +94,21 @@ export const zCounter = z.strictObject({
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({
triggers: zBoundedRecord(z.record(zBoundedCharacters(0, 100), zTriggerInput), 1, MAX_TRIGGERS_PER_COUNTER),
decay: z
.strictObject({
amount: z.number(),
every: zDelayString,
}).nullable().default(null),
})
.nullable()
.default(null),
can_view: z.boolean().default(false),
can_edit: z.boolean().default(false),
can_reset_all: z.boolean().default(false),
});
export const zCountersConfig = z.strictObject({
counters: zBoundedRecord(
z.record(zBoundedCharacters(0, 100), zCounter),
0,
MAX_COUNTERS,
),
counters: zBoundedRecord(z.record(zBoundedCharacters(0, 100), zCounter), 0, MAX_COUNTERS),
can_view: z.boolean(),
can_edit: z.boolean(),
can_reset_all: z.boolean(),

View file

@ -9,14 +9,16 @@ import { CustomEventsPluginType, TCustomEvent } from "../types";
export const zSetChannelPermissionOverridesAction = z.strictObject({
type: z.literal("set_channel_permission_overrides"),
channel: zSnowflake,
overrides: z.array(
overrides: z
.array(
z.strictObject({
type: z.union([z.literal("member"), z.literal("role")]),
id: zSnowflake,
allow: z.number(),
deny: z.number(),
}),
).max(15),
)
.max(15),
});
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 const zCustomEventsConfig = z.strictObject({
events: zBoundedRecord(
z.record(zBoundedCharacters(0, 100), zCustomEvent),
0,
100,
),
events: zBoundedRecord(z.record(zBoundedCharacters(0, 100), zCustomEvent), 0, 100),
});
export interface CustomEventsPluginType extends BasePluginType {

View file

@ -1,10 +1,10 @@
import { Guild } from "discord.js";
import { BasePluginType, GlobalPluginData, globalPluginEventListener } from "knub";
import z from "zod";
import { AllowedGuilds } from "../../data/AllowedGuilds";
import { Configs } from "../../data/Configs";
import { env } from "../../env";
import { zeppelinGlobalPlugin } from "../ZeppelinPluginBlueprint";
import z from "zod";
interface GuildAccessMonitorPluginType extends BasePluginType {
state: {

View file

@ -1,11 +1,11 @@
import { Guild } from "discord.js";
import { guildPluginEventListener } from "knub";
import z from "zod";
import { AllowedGuilds } from "../../data/AllowedGuilds";
import { ApiPermissionAssignments } from "../../data/ApiPermissionAssignments";
import { MINUTES } from "../../utils";
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
import { GuildInfoSaverPluginType } from "./types";
import z from "zod";
export const GuildInfoSaverPlugin = zeppelinGuildPlugin<GuildInfoSaverPluginType>()({
name: "guild_info_saver",

View file

@ -32,7 +32,9 @@ export function logMessageDelete(pluginData: GuildPluginData<LogsPluginType>, da
// See comment on FORMAT_NO_TIMESTAMP in types.ts
const config = pluginData.config.get();
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(
pluginData,

View file

@ -29,10 +29,12 @@ const MAX_BATCH_TIME = 5000;
type ZLogFormatsHelper = {
-readonly [K in keyof typeof LogType]: typeof zMessageContent;
};
export const zLogFormats = z.strictObject(keys(LogType).reduce((map, logType) => {
export const zLogFormats = z.strictObject(
keys(LogType).reduce((map, logType) => {
map[logType] = zMessageContent;
return map;
}, {} as ZLogFormatsHelper));
}, {} as ZLogFormatsHelper),
);
export type TLogFormats = z.infer<typeof zLogFormats>;
const zLogChannel = z.strictObject({
@ -58,10 +60,12 @@ export type TLogChannelMap = z.infer<typeof zLogChannelMap>;
export const zLogsConfig = z.strictObject({
channels: zLogChannelMap,
format: zLogFormats.merge(z.strictObject({
format: zLogFormats.merge(
z.strictObject({
// 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
ping_user: z.boolean(),
allow_user_mentions: z.boolean(),

View file

@ -2,6 +2,10 @@ import { GuildPluginData } from "knub";
import { LogType } from "../../../data/LogType";
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);
}

View file

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

View file

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

View file

@ -11,7 +11,8 @@ const zRoleButtonOption = z.strictObject({
label: z.string().nullable().default(null),
emoji: z.string().nullable().default(null),
// https://discord.js.org/#/docs/discord.js/v13/typedef/MessageButtonStyle
style: z.union([
style: z
.union([
z.literal(ButtonStyle.Primary),
z.literal(ButtonStyle.Secondary),
z.literal(ButtonStyle.Success),
@ -23,17 +24,23 @@ const zRoleButtonOption = z.strictObject({
z.literal("SUCCESS"),
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
]).nullable().default(null),
])
.nullable()
.default(null),
start_new_row: z.boolean().default(false),
});
export type TRoleButtonOption = z.infer<typeof zRoleButtonOption>;
const zRoleButtonsConfigItem = z.strictObject({
const zRoleButtonsConfigItem = 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) => {
name: z
.never()
.optional()
.transform((_, ctx) => {
const ruleName = String(ctx.path[ctx.path.length - 2]).trim();
if (! ruleName) {
if (!ruleName) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Role buttons must have names",
@ -54,8 +61,9 @@ const zRoleButtonsConfigItem = z.strictObject({
]),
options: z.array(zRoleButtonOption).max(25),
exclusive: z.boolean().default(false),
})
.refine((parsed) => {
})
.refine(
(parsed) => {
try {
createButtonComponents(parsed);
} catch (err) {
@ -65,20 +73,20 @@ const zRoleButtonsConfigItem = z.strictObject({
throw err;
}
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 const zRoleButtonsConfig = z.strictObject({
buttons: zBoundedRecord(
z.record(zBoundedCharacters(1, 16), zRoleButtonsConfigItem),
0,
100,
),
export const zRoleButtonsConfig = z
.strictObject({
buttons: zBoundedRecord(z.record(zBoundedCharacters(1, 16), zRoleButtonsConfigItem), 0, 100),
can_reset: z.boolean(),
})
.refine((parsed) => {
})
.refine(
(parsed) => {
const seenMessages = new Set();
for (const button of Object.values(parsed.buttons)) {
if (button.message) {
@ -91,9 +99,11 @@ export const zRoleButtonsConfig = z.strictObject({
}
}
return true;
}, {
},
{
message: "Can't target the same message with two sets of role buttons",
});
},
);
export interface RoleButtonsPluginType extends BasePluginType {
config: z.infer<typeof zRoleButtonsConfig>;

View file

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

View file

@ -11,14 +11,8 @@ const zBaseSingleSpamConfig = z.strictObject({
count: z.number(),
mute: z.boolean().default(false),
mute_time: z.number().nullable().default(null),
remove_roles_on_mute: z.union([
z.boolean(),
z.array(zSnowflake),
]).default(false),
restore_roles_on_mute: z.union([
z.boolean(),
z.array(zSnowflake),
]).default(false),
remove_roles_on_mute: z.union([z.boolean(), z.array(zSnowflake)]).default(false),
restore_roles_on_mute: z.union([z.boolean(), z.array(zSnowflake)]).default(false),
clean: z.boolean().default(false),
});
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 const zStarboardConfig = z.strictObject({
boards: zBoundedRecord(
z.record(z.string(), zStarboardOpts),
0,
100,
),
boards: zBoundedRecord(z.record(z.string(), zStarboardOpts), 0, 100),
can_migrate: z.boolean(),
});

View file

@ -1,11 +1,11 @@
import { MessageCreateOptions } from "discord.js";
import { commandTypeHelpers as ct } from "../../../commandTypes";
import { logger } from "../../../logger";
import { sendErrorMessage } from "../../../pluginUtils";
import { TemplateParseError } from "../../../templateFormatter";
import { memberToTemplateSafeMember, userToTemplateSafeUser } from "../../../utils/templateSafeObjects";
import { tagsCmd } from "../types";
import { renderTagBody } from "../util/renderTagBody";
import { logger } from "../../../logger";
export const TagEvalCmd = tagsCmd({
trigger: "tag eval",
@ -35,13 +35,11 @@ export const TagEvalCmd = tagsCmd({
msg.channel.send(rendered);
} catch (e) {
const errorMessage = e instanceof TemplateParseError
? e.message
: "Internal error";
const errorMessage = e instanceof TemplateParseError ? e.message : "Internal error";
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}`);
}

View file

@ -9,7 +9,8 @@ import { zEmbedInput } from "../../utils";
export const zTag = z.union([z.string(), zEmbedInput]);
export type TTag = z.infer<typeof zTag>;
export const zTagCategory = z.strictObject({
export const zTagCategory = z
.strictObject({
prefix: z.string().nullable().default(null),
delete_with_command: z.boolean().default(false),
@ -23,16 +24,14 @@ export const zTagCategory = z.strictObject({
tags: z.record(z.string(), zTag),
can_use: z.boolean().nullable().default(null),
})
.refine(
(parsed) => ! (parsed.auto_delete_command && parsed.delete_with_command),
{
})
.refine((parsed) => !(parsed.auto_delete_command && parsed.delete_with_command), {
message: "Cannot have both (category specific) delete_with_command and auto_delete_command enabled",
},
);
});
export type TTagCategory = z.infer<typeof zTagCategory>;
export const zTagsConfig = z.strictObject({
export const zTagsConfig = z
.strictObject({
prefix: z.string(),
delete_with_command: z.boolean(),
@ -48,13 +47,10 @@ export const zTagsConfig = z.strictObject({
can_create: z.boolean(),
can_use: z.boolean(),
can_list: z.boolean(),
})
.refine(
(parsed) => ! (parsed.auto_delete_command && parsed.delete_with_command),
{
})
.refine((parsed) => !(parsed.auto_delete_command && parsed.delete_with_command), {
message: "Cannot have both (category specific) delete_with_command and auto_delete_command enabled",
},
);
});
export interface TagsPluginType extends BasePluginType {
config: z.infer<typeof zTagsConfig>;

View file

@ -2,11 +2,11 @@ import { Snowflake, TextChannel } from "discord.js";
import { GuildPluginData } from "knub";
import { SavedMessage } from "../../../data/entities/SavedMessage";
import { convertDelayStringToMS, resolveMember, zStrictMessageContent } from "../../../utils";
import { erisAllowedMentionsToDjsMentionOptions } from "../../../utils/erisAllowedMentionsToDjsMentionOptions";
import { messageIsEmpty } from "../../../utils/messageIsEmpty";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { TagsPluginType } from "../types";
import { matchAndRenderTagFromString } from "./matchAndRenderTagFromString";
import { erisAllowedMentionsToDjsMentionOptions } from "../../../utils/erisAllowedMentionsToDjsMentionOptions";
export async function onMessageCreate(pluginData: GuildPluginData<TagsPluginType>, msg: SavedMessage) {
if (msg.is_bot) return;
@ -85,7 +85,7 @@ export async function onMessageCreate(pluginData: GuildPluginData<TagsPluginType
}
const validated = zStrictMessageContent.safeParse(tagResult.renderedContent);
if (! validated.success) {
if (!validated.success) {
pluginData.getPlugin(LogsPlugin).logBotAlert({
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 { GuildPluginData } from "knub";
import { parseArguments } from "knub-command-manager";
import { logger } from "../../../logger";
import { TemplateParseError } from "../../../templateFormatter";
import { StrictMessageContent, validateAndParseMessageContent } from "../../../utils";
import { memberToTemplateSafeMember, userToTemplateSafeUser } from "../../../utils/templateSafeObjects";
import { LogsPlugin } from "../../Logs/LogsPlugin";
import { TTag, TagsPluginType } from "../types";
import { renderTagBody } from "./renderTagBody";
import { logger } from "../../../logger";
export async function renderTagFromString(
pluginData: GuildPluginData<TagsPluginType>,
@ -36,14 +36,12 @@ export async function renderTagFromString(
return validateAndParseMessageContent(rendered);
} catch (e) {
const logs = pluginData.getPlugin(LogsPlugin);
const errorMessage = e instanceof TemplateParseError
? e.message
: "Internal error";
const errorMessage = e instanceof TemplateParseError ? e.message : "Internal error";
logs.logBotAlert({
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}`);
}

View file

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

View file

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

View file

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

View file

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

View file

@ -14,7 +14,15 @@ import { ArgsFromSignatureOrArray, GuildPluginData } from "knub";
import moment from "moment-timezone";
import { RegExpRunner, allowTimeout } from "../../RegExpRunner";
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 { hasDiscordPermissions } from "../../utils/hasDiscordPermissions";
import { banSearchSignature } from "./commands/BanSearchCmd";

View file

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

View file

@ -87,8 +87,10 @@ export function isDiscordAPIError(err: Error | string): err is DiscordAPIError {
}
// null | undefined -> undefined
export function zNullishToUndefined<T extends z.ZodTypeAny>(type: T): ZodEffects<T, NonNullable<z.output<T>> | undefined> {
return type.transform(v => v ?? undefined);
export function zNullishToUndefined<T extends z.ZodTypeAny>(
type: T,
): ZodEffects<T, NonNullable<z.output<T>> | undefined> {
return type.transform((v) => v ?? undefined);
}
export function getScalarDifference<T extends object>(
@ -151,15 +153,18 @@ export type GroupDMInvite = Invite & {
};
export function zBoundedCharacters(min: number, max: number) {
return z.string().refine(str => {
return z.string().refine(
(str) => {
const len = [...str].length; // Unicode aware character split
return (len >= min && len <= max);
}, {
return len >= min && len <= max;
},
{
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",
});
@ -188,7 +193,7 @@ export function zRegex<T extends ZodString>(zStr: T) {
if (err instanceof InvalidRegexError) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Invalid regex"
message: "Invalid regex",
});
return z.NEVER;
}
@ -343,8 +348,18 @@ function dropNullValuesRecursively(obj: any) {
*/
export const zAllowedMentions = z.strictObject({
everyone: zNullishToUndefined(z.boolean().nullable().optional()),
users: zNullishToUndefined(z.union([z.boolean(), z.array(z.string())]).nullable().optional()),
roles: zNullishToUndefined(z.union([z.boolean(), z.array(z.string())]).nullable().optional()),
users: zNullishToUndefined(
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()),
});
@ -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> {
return record.refine(data => {
export function zBoundedRecord<TRecord extends ZodRecord<any, any>>(
record: TRecord,
minKeys: number,
maxKeys: number,
): ZodEffects<TRecord> {
return record.refine(
(data) => {
const len = Object.keys(data).length;
return (len >= minKeys && len <= maxKeys);
}, {
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
.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.
// 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);
}
type Entries<T> = Array<{
type Entries<T> = Array<
{
[Key in keyof T]-?: [Key, T[Key]];
}[keyof T]>;
}[keyof T]
>;
export function entries<T extends object>(object: T) {
return Object.entries(object) as Entries<T>;

View file

@ -6,7 +6,10 @@ import { loadYamlSafely } from "./utils/loadYamlSafely";
import { ObjectAliasError } from "./utils/validateNoObjectAliases";
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 prefixed = prefix + indented.slice(prefix.length);
console.log(prefixed + "\n\n");