This package has been archived, and as such it is read-only.
Built and signed on GitHub ActionsBuilt and signed on GitHub Actions
Built and signed on GitHub Actions
latest
mybearworld/roarbotA library for creating bots for the Meower platform.
This package works with Deno, Bun, Browsers


JSR Score
100%
Published
8 months ago (1.8.2)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187import type { Messages, RichPost } from "./mod.ts"; /** * Possible types of patterns to a command. * - `"string"`: Any string * - `"number"`: Any floating point number * - `"full"`: A string that matches until the end of the command. * - `"reply"`: A post the command replied to. * - `string[]`: One of the specified strings */ export type PatternType = "string" | "number" | "full" | "reply" | string[]; /** * A list of arguments types. This is a list of objects formatted like this: * - type: A {@link PatternType}. * - optional: Whether it's optional or not. After an optional argument can only * be other optional arguments. * - name: The name of the argument. * If both the name and optional aren't given, the type can be standalone * without a wrapper object. * * @example Basic * ```js * ["number", "string"] * // @Bot cmd 2 4 → [2, "4"] * ``` * @example `full` * ```js * [ * { type: "number", name: "amount" }, * { type: "full", name: "string" } * ] * // @Bot cmd 7 Hello, world! → [7, "Hello, world!"] * ``` * @example Optionals * ```js * [ * { type: "string", name: "person to greet" }, * { type: "string", optional: true, name: "greeting to use" } * ] * // @Bot cmd Josh → ["Josh"] * // @Bot cmd Josh G'day → ["Josh", "G'day"] * ``` */ export type Pattern = ( | PatternType | { type: PatternType; name?: string; optional?: boolean } )[]; /** * Converts the passed in `TPattern` to its corresponding TypeScript type. */ export type ResolvePattern<TPattern extends Pattern> = { [K in keyof TPattern]: K extends `${number}` ? TPattern[K] extends PatternType ? ResolvePatternType<TPattern[K]> : TPattern[K] extends { type: PatternType } ? TPattern[K] extends { optional: true } ? ResolvePatternType<TPattern[K]["type"]> | undefined : ResolvePatternType<TPattern[K]["type"]> : never : TPattern[K]; }; type ResolvePatternType<TArgument extends PatternType> = TArgument extends "string" ? string : TArgument extends "number" ? number : TArgument extends "full" ? string : TArgument extends "reply" ? RichPost : TArgument extends string[] ? TArgument[number] : never; export const parseArgs = <const TPattern extends Pattern>( pattern: TPattern, args: string[], messages: Messages, replies?: (RichPost | null)[], ): | { error: true; message: string } | { error: false; parsed: ResolvePattern<TPattern> } => { const parsed = []; let hadOptionals = false; let hadFull = false; let i = 0; let replyAmount = 0; for (const slice of pattern) { const isObject = typeof slice === "object" && "type" in slice; const type = isObject ? slice.type : slice; const optional = isObject && !!slice.optional; if (hadOptionals && !optional) { return { error: true, message: "In this command's pattern, there is an optional argument following a non-optional one.\nThis is an issue with the bot, not your command.", }; } hadOptionals ||= optional; const name = isObject && !!slice.name; const repr = name ? `${slice.name} (${type})` : `${type}`; if (type === "reply") { if (!replies?.[replyAmount]) { if (optional) { parsed.push(undefined); continue; } return { error: true, message: messages.argsMissing(repr), }; } parsed.push(replies[replyAmount]); replyAmount++; break; } const current = args[i]; if (!current) { if (optional) { continue; } else if (type !== "full") { return { error: true, message: messages.argsMissing(repr) }; } } if (Array.isArray(type)) { if (!type.includes(current)) { return { error: true, message: messages.argsNotInSet( JSON.stringify(current), type.map((t) => JSON.stringify(t)).join(", "), ), }; } parsed.push(current); continue; } switch (type) { case "string": { parsed.push(current); i++; break; } case "number": { const number = Number(current); if (Number.isNaN(number)) { return { error: true, message: messages.argNan(JSON.stringify(current)), }; } parsed.push(number); i++; break; } case "full": { if (pattern[i + 1]) { return { error: true, message: "In this command's pattern, there is an argument following a `full` argument.\nThis is an issue with the bot, not your command.", }; } hadFull = true; parsed.push(args.slice(i).join(" ")); i++; break; } default: (type) satisfies never; } } if (!hadFull && args.length + replyAmount !== parsed.length) { return { error: true, message: messages.tooManyArgs }; } return { error: false, parsed: parsed as ResolvePattern<TPattern> }; }; /** * Turns the pattern type into a human readable format. * @param patternType The pattern type. */ export const stringifyPatternType = (patternType: PatternType): string => { return ( typeof patternType === "string" ? patternType === "full" ? "full string" : patternType : patternType.map((option) => JSON.stringify(option)).join(" | ") ); };