mirror of
https://github.com/ZeppelinBot/Zeppelin.git
synced 2025-05-10 12:25:02 +00:00
refactor: replace io-ts with zod
This commit is contained in:
parent
fafaefa1fb
commit
28692962bc
161 changed files with 1450 additions and 2105 deletions
|
@ -1,15 +1,12 @@
|
|||
import { EventEmitter } from "events";
|
||||
import { PluginOptions } from "knub";
|
||||
import {
|
||||
buildCounterConditionString,
|
||||
CounterTrigger,
|
||||
getReverseCounterComparisonOp,
|
||||
parseCounterConditionString,
|
||||
} from "../../data/entities/CounterTrigger";
|
||||
import { GuildCounters } from "../../data/GuildCounters";
|
||||
import {
|
||||
CounterTrigger,
|
||||
parseCounterConditionString
|
||||
} from "../../data/entities/CounterTrigger";
|
||||
import { mapToPublicFn } from "../../pluginUtils";
|
||||
import { convertDelayStringToMS, MINUTES } from "../../utils";
|
||||
import { parseIoTsSchema, StrictValidationError } from "../../validatorUtils";
|
||||
import { MINUTES, convertDelayStringToMS, values } from "../../utils";
|
||||
import { zeppelinGuildPlugin } from "../ZeppelinPluginBlueprint";
|
||||
import { AddCounterCmd } from "./commands/AddCounterCmd";
|
||||
import { CountersListCmd } from "./commands/CountersListCmd";
|
||||
|
@ -25,10 +22,8 @@ import { getPrettyNameForCounterTrigger } from "./functions/getPrettyNameForCoun
|
|||
import { offCounterEvent } from "./functions/offCounterEvent";
|
||||
import { onCounterEvent } from "./functions/onCounterEvent";
|
||||
import { setCounterValue } from "./functions/setCounterValue";
|
||||
import { ConfigSchema, CountersPluginType, TTrigger } from "./types";
|
||||
import { CountersPluginType, zCountersConfig } from "./types";
|
||||
|
||||
const MAX_COUNTERS = 5;
|
||||
const MAX_TRIGGERS_PER_COUNTER = 5;
|
||||
const DECAY_APPLY_INTERVAL = 5 * MINUTES;
|
||||
|
||||
const defaultOptions: PluginOptions<CountersPluginType> = {
|
||||
|
@ -72,50 +67,12 @@ export const CountersPlugin = zeppelinGuildPlugin<CountersPluginType>()({
|
|||
description:
|
||||
"Keep track of per-user, per-channel, or global numbers and trigger specific actions based on this number",
|
||||
configurationGuide: "See <a href='/docs/setup-guides/counters'>Counters setup guide</a>",
|
||||
configSchema: ConfigSchema,
|
||||
configSchema: zCountersConfig,
|
||||
},
|
||||
|
||||
defaultOptions,
|
||||
// TODO: Separate input and output types
|
||||
configParser: (input) => {
|
||||
for (const [counterName, counter] of Object.entries<any>((input as any).counters || {})) {
|
||||
counter.name = counterName;
|
||||
counter.per_user = counter.per_user ?? false;
|
||||
counter.per_channel = counter.per_channel ?? false;
|
||||
counter.initial_value = counter.initial_value ?? 0;
|
||||
counter.triggers = counter.triggers || {};
|
||||
|
||||
if (Object.values(counter.triggers).length > MAX_TRIGGERS_PER_COUNTER) {
|
||||
throw new StrictValidationError([`You can only have at most ${MAX_TRIGGERS_PER_COUNTER} triggers per counter`]);
|
||||
}
|
||||
|
||||
// Normalize triggers
|
||||
for (const [triggerName, trigger] of Object.entries(counter.triggers)) {
|
||||
const triggerObj = (typeof trigger === "string" ? { condition: trigger } : trigger) as Partial<TTrigger>;
|
||||
|
||||
triggerObj.name = triggerName;
|
||||
const parsedCondition = parseCounterConditionString(triggerObj.condition || "");
|
||||
if (!parsedCondition) {
|
||||
throw new StrictValidationError([
|
||||
`Invalid comparison in counter trigger ${counterName}/${triggerName}: "${triggerObj.condition}"`,
|
||||
]);
|
||||
}
|
||||
|
||||
triggerObj.condition = buildCounterConditionString(parsedCondition[0], parsedCondition[1]);
|
||||
triggerObj.reverse_condition =
|
||||
triggerObj.reverse_condition ||
|
||||
buildCounterConditionString(getReverseCounterComparisonOp(parsedCondition[0]), parsedCondition[1]);
|
||||
|
||||
counter.triggers[triggerName] = triggerObj as TTrigger;
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.values((input as any).counters || {}).length > MAX_COUNTERS) {
|
||||
throw new StrictValidationError([`You can only have at most ${MAX_COUNTERS} counters`]);
|
||||
}
|
||||
|
||||
return parseIoTsSchema(ConfigSchema, input);
|
||||
},
|
||||
configParser: (input) => zCountersConfig.parse(input),
|
||||
|
||||
public: {
|
||||
counterExists: mapToPublicFn(counterExists),
|
||||
|
@ -163,13 +120,12 @@ export const CountersPlugin = zeppelinGuildPlugin<CountersPluginType>()({
|
|||
state.counterTriggersByCounterId.set(dbCounter.id, thisCounterTriggers);
|
||||
|
||||
// Initialize triggers
|
||||
for (const trigger of Object.values(counter.triggers)) {
|
||||
const theTrigger = trigger as TTrigger;
|
||||
const parsedCondition = parseCounterConditionString(theTrigger.condition)!;
|
||||
const parsedReverseCondition = parseCounterConditionString(theTrigger.reverse_condition)!;
|
||||
for (const trigger of values(counter.triggers)) {
|
||||
const parsedCondition = parseCounterConditionString(trigger.condition)!;
|
||||
const parsedReverseCondition = parseCounterConditionString(trigger.reverse_condition)!;
|
||||
const counterTrigger = await state.counters.initCounterTrigger(
|
||||
dbCounter.id,
|
||||
theTrigger.name,
|
||||
trigger.name,
|
||||
parsedCondition[0],
|
||||
parsedCondition[1],
|
||||
parsedReverseCondition[0],
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { GuildPluginData } from "knub";
|
||||
import { CountersPluginType, TTrigger } from "../types";
|
||||
import { CountersPluginType } from "../types";
|
||||
|
||||
export function getPrettyNameForCounterTrigger(
|
||||
pluginData: GuildPluginData<CountersPluginType>,
|
||||
|
@ -12,6 +12,6 @@ export function getPrettyNameForCounterTrigger(
|
|||
return "Unknown Counter Trigger";
|
||||
}
|
||||
|
||||
const trigger = counter.triggers[triggerName] as TTrigger | undefined;
|
||||
const trigger = counter.triggers[triggerName];
|
||||
return trigger ? trigger.pretty_name || trigger.name : "Unknown Counter Trigger";
|
||||
}
|
||||
|
|
|
@ -1,45 +1,98 @@
|
|||
import { EventEmitter } from "events";
|
||||
import * as t from "io-ts";
|
||||
import { BasePluginType } from "knub";
|
||||
import z from "zod";
|
||||
import { GuildCounters } from "../../data/GuildCounters";
|
||||
import { CounterTrigger } from "../../data/entities/CounterTrigger";
|
||||
import { tDelayString, tNullable } from "../../utils";
|
||||
import { CounterTrigger, buildCounterConditionString, getReverseCounterComparisonOp, parseCounterConditionString } from "../../data/entities/CounterTrigger";
|
||||
import { zBoundedCharacters, zBoundedRecord, zDelayString } from "../../utils";
|
||||
import Timeout = NodeJS.Timeout;
|
||||
|
||||
export const Trigger = t.type({
|
||||
name: t.string,
|
||||
pretty_name: tNullable(t.string),
|
||||
condition: t.string,
|
||||
reverse_condition: t.string,
|
||||
});
|
||||
export type TTrigger = t.TypeOf<typeof Trigger>;
|
||||
const MAX_COUNTERS = 5;
|
||||
const MAX_TRIGGERS_PER_COUNTER = 5;
|
||||
|
||||
export const Counter = t.type({
|
||||
name: t.string,
|
||||
pretty_name: tNullable(t.string),
|
||||
per_channel: t.boolean,
|
||||
per_user: t.boolean,
|
||||
initial_value: t.number,
|
||||
triggers: t.record(t.string, t.union([t.string, Trigger])),
|
||||
decay: tNullable(
|
||||
t.type({
|
||||
amount: t.number,
|
||||
every: tDelayString,
|
||||
}),
|
||||
export const zTrigger = z.strictObject({
|
||||
// Dummy type because name gets replaced by the property key in zTriggerInput
|
||||
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" },
|
||||
),
|
||||
can_view: tNullable(t.boolean),
|
||||
can_edit: tNullable(t.boolean),
|
||||
can_reset_all: tNullable(t.boolean),
|
||||
});
|
||||
export type TCounter = t.TypeOf<typeof Counter>;
|
||||
|
||||
export const ConfigSchema = t.type({
|
||||
counters: t.record(t.string, Counter),
|
||||
can_view: t.boolean,
|
||||
can_edit: t.boolean,
|
||||
can_reset_all: t.boolean,
|
||||
const zTriggerInput = z.union([zBoundedCharacters(0, 100), zTrigger])
|
||||
.transform((val, ctx) => {
|
||||
const ruleName = String(ctx.path[ctx.path.length - 2]).trim();
|
||||
if (typeof val === "string") {
|
||||
const parsedCondition = parseCounterConditionString(val);
|
||||
if (!parsedCondition) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Invalid counter trigger condition",
|
||||
});
|
||||
return z.NEVER;
|
||||
}
|
||||
return {
|
||||
name: ruleName,
|
||||
pretty_name: null,
|
||||
condition: buildCounterConditionString(parsedCondition[0], parsedCondition[1]),
|
||||
reverse_condition: buildCounterConditionString(getReverseCounterComparisonOp(parsedCondition[0]), parsedCondition[1]),
|
||||
};
|
||||
}
|
||||
return {
|
||||
...val,
|
||||
name: ruleName,
|
||||
};
|
||||
});
|
||||
|
||||
export const zCounter = z.strictObject({
|
||||
// Typed as "never" because you are not expected to supply this directly.
|
||||
// The transform instead picks it up from the property key and the output type is a string.
|
||||
name: z.never().optional().transform((_, ctx) => {
|
||||
const ruleName = String(ctx.path[ctx.path.length - 2]).trim();
|
||||
if (! ruleName) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Counters must have names",
|
||||
});
|
||||
return z.NEVER;
|
||||
}
|
||||
return ruleName;
|
||||
}),
|
||||
pretty_name: zBoundedCharacters(0, 100).nullable().default(null),
|
||||
per_channel: z.boolean().default(false),
|
||||
per_user: z.boolean().default(false),
|
||||
initial_value: z.number().default(0),
|
||||
triggers: zBoundedRecord(
|
||||
z.record(
|
||||
zBoundedCharacters(0, 100),
|
||||
zTriggerInput,
|
||||
),
|
||||
1,
|
||||
MAX_TRIGGERS_PER_COUNTER,
|
||||
),
|
||||
decay: z.strictObject({
|
||||
amount: z.number(),
|
||||
every: zDelayString,
|
||||
}).nullable().default(null),
|
||||
can_view: z.boolean(),
|
||||
can_edit: z.boolean(),
|
||||
can_reset_all: z.boolean(),
|
||||
});
|
||||
|
||||
export const zCountersConfig = z.strictObject({
|
||||
counters: zBoundedRecord(
|
||||
z.record(zBoundedCharacters(0, 100), zCounter),
|
||||
0,
|
||||
MAX_COUNTERS,
|
||||
),
|
||||
can_view: z.boolean(),
|
||||
can_edit: z.boolean(),
|
||||
can_reset_all: z.boolean(),
|
||||
});
|
||||
export type TConfigSchema = t.TypeOf<typeof ConfigSchema>;
|
||||
|
||||
export interface CounterEvents {
|
||||
trigger: (counterName: string, triggerName: string, channelId: string | null, userId: string | null) => void;
|
||||
|
@ -52,7 +105,7 @@ export interface CounterEventEmitter extends EventEmitter {
|
|||
}
|
||||
|
||||
export interface CountersPluginType extends BasePluginType {
|
||||
config: TConfigSchema;
|
||||
config: z.infer<typeof zCountersConfig>;
|
||||
state: {
|
||||
counters: GuildCounters;
|
||||
counterIds: Record<string, number>;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue