UNPKG

onebot-commander

Version:

OneBot12 Message Segment Commander - TypeScript version with dual ESM/CJS format support

280 lines (279 loc) 9.56 kB
import { PatternParser } from './pattern_parser.js'; import { SegmentMatcher } from './segment_matcher.js'; import { ValidationError } from './errors.js'; /** * 快速参数验证 */ function fastValidateSegments(segments) { // 快速路径:null 或 undefined if (segments == null) { return false; } // 检查是否为数组 return Array.isArray(segments); } /** * 优化的回调链执行器 */ class OptimizedCallbackChain { constructor(callbacks) { this.callbacks = [...callbacks]; // 创建副本避免修改原数组 } /** * 同步执行回调链 */ executeSync(...initialValues) { if (this.callbacks.length === 0) { return initialValues; } let values = initialValues; for (const callback of this.callbacks) { values = [callback(...values)]; } return Array.isArray(values) ? values : [values]; } /** * 异步执行回调链 */ async executeAsync(...initialValues) { if (this.callbacks.length === 0) { return initialValues; } let values = initialValues; for (const callback of this.callbacks) { const result = callback(...values); values = result instanceof Promise ? [await result] : [result]; } return Array.isArray(values) ? values : [values]; } } /** * OneBot Commander 主类 * * 用于解析和匹配 OneBot12 消息段的命令解析器。 * 支持复杂的模式匹配、参数提取和链式回调处理。 * * @example * ```typescript * const commander = new Commander('hello <name:text>'); * commander.action((params) => { * console.log(`Hello, ${params.name}!`); * }); * ``` */ export class Commander { /** * 构造函数 * * @param pattern - 命令模式字符串,定义匹配规则 * @param typedLiteralFields - 自定义的类型化字面量字段映射(可选) * * @throws {ValidationError} 当模式为空或格式错误时抛出 * * @example * ```typescript * // 基础用法 * const commander = new Commander('hello <name:text>'); * * // 自定义字段映射 * const customCommander = new Commander('{image:avatar.png}<name:text>', { * image: 'src' // 使用 'src' 字段而不是默认的 'file' 或 'url' * }); * ``` */ constructor(pattern, typedLiteralFields = { ...Commander.DEFAULT_TYPED_LITERAL_FIELD_MAP }) { /** 缓存的回调链执行器 */ this.callbackChain = null; // 参数验证:确保模式是有效的字符串 if (typeof pattern !== 'string') { throw new ValidationError('Pattern must be a string', 'pattern', pattern); } // 参数验证:确保模式不为空 if (!pattern.trim()) { throw new ValidationError('Pattern cannot be empty', 'pattern', pattern); } // 参数验证:确保字段映射是有效的对象 if (typedLiteralFields && typeof typedLiteralFields !== 'object') { throw new ValidationError('typedLiteralFields must be an object', 'typedLiteralFields', typedLiteralFields); } // 初始化实例属性 this.callbacks = []; this.tokens = PatternParser.parse(pattern); // 合并默认字段映射和自定义字段映射 // 自定义映射会覆盖默认映射 this.typedLiteralFields = Object.assign({}, { ...Commander.DEFAULT_TYPED_LITERAL_FIELD_MAP, }, typedLiteralFields); } /** * 添加回调函数到处理链 * * 支持同步和异步回调函数,可以链式调用。 * 回调函数会按添加顺序执行,前一个回调的返回值会作为下一个回调的参数。 * * @param callback - 回调函数,接收匹配参数和剩余消息段 * @returns 当前实例,支持链式调用 * * @example * ```typescript * commander * .action((params) => { * console.log('First action:', params); * return params.name.toUpperCase(); * }) * .action((upperName) => { * console.log('Second action:', upperName); * return `Hello, ${upperName}!`; * }); * ``` */ action(callback) { this.callbacks.push(callback); // 清除缓存的回调链执行器,因为回调发生了变化 this.callbackChain = null; return this; } /** * 获取或创建优化的回调链执行器 */ getCallbackChain() { if (!this.callbackChain) { this.callbackChain = new OptimizedCallbackChain(this.callbacks); } return this.callbackChain; } /** * 同步匹配消息段并执行回调链 * * 根据模式匹配消息段,如果匹配成功则按顺序执行所有回调函数。 * 每个回调函数的返回值会作为下一个回调函数的参数。 * * @param segments - OneBot12 消息段数组 * @returns 返回最后一个回调函数的返回值数组,如果匹配失败返回空数组 * * @throws {ValidationError} 当消息段参数无效时抛出 * * @example * ```typescript * const segments = [ * { type: 'text', data: { text: 'hello Alice' } } * ]; * * const result = commander.match(segments); * // result 包含所有回调函数的最终返回值 * ``` */ match(segments) { // 快速参数验证 if (!fastValidateSegments(segments)) { throw new ValidationError('Segments must be an array', 'segments', segments); } // 使用 SegmentMatcher 进行模式匹配 const result = SegmentMatcher.match(this.tokens, segments, this.typedLiteralFields); // 获取优化的回调链执行器 const callbackChain = this.getCallbackChain(); // 如果没有匹配结果,直接执行回调链(可能返回默认值) if (!result) return callbackChain.executeSync(); // 如果 params 有内容(即使全是默认值),也应返回 [params] const args = [result.params, ...result.remaining]; // 判断 params 是否为空对象 const hasParams = Object.keys(result.params).length > 0; if (hasParams || result.matched.length > 0) { return callbackChain.executeSync(...args); } else { return []; } } /** * 异步匹配消息段并执行回调链 * * 异步版本的 match 方法,支持异步回调函数。 * 会自动检测回调函数的返回值是否为 Promise,并正确处理异步执行。 * * @param segments - OneBot12 消息段数组 * @returns 返回 Promise<匹配结果数组>,如果匹配失败返回 Promise<空数组> * * @throws {ValidationError} 当消息段参数无效时抛出 * * @example * ```typescript * const segments = [ * { type: 'text', data: { text: 'hello Alice' } } * ]; * * const result = await commander.matchAsync(segments); * // result 包含所有异步回调函数的最终返回值 * ``` */ async matchAsync(segments) { // 快速参数验证 if (!fastValidateSegments(segments)) { throw new ValidationError('Segments must be an array', 'segments', segments); } // 使用 SegmentMatcher 进行模式匹配 const result = SegmentMatcher.match(this.tokens, segments, this.typedLiteralFields); // 获取优化的回调链执行器 const callbackChain = this.getCallbackChain(); // 如果没有匹配结果,直接执行回调链 if (!result) return callbackChain.executeAsync(); // 将匹配参数和剩余消息段作为回调函数的参数 const args = [result.params, ...result.remaining]; return callbackChain.executeAsync(...args); } /** * 获取解析后的模式令牌 * * 主要用于调试和测试,返回模式解析器生成的令牌数组。 * * @returns 模式令牌数组 * * @example * ```typescript * const commander = new Commander('hello <name:text>'); * const tokens = commander.getTokens(); * console.log(tokens); // 显示解析后的令牌结构 * ``` */ getTokens() { return this.tokens; } } /** * 默认的类型化字面量字段映射规则 * * 定义了不同消息段类型对应的数据字段: * - text: 使用 'text' 字段 * - face: 使用 'id' 字段 * - image: 使用 'file' 或 'url' 字段(支持多字段) * - at: 使用 'user_id' 字段 */ Commander.DEFAULT_TYPED_LITERAL_FIELD_MAP = { text: 'text', face: 'id', image: ['file', 'url'], at: 'user_id', }; /** * 便捷函数:创建命令匹配器实例 * * 这是一个工厂函数,提供更简洁的 API 来创建 Commander 实例。 * * @param pattern - 命令模式字符串 * @param typedLiteralFields - 自定义的类型化字面量字段映射(可选) * @returns 新创建的 Commander 实例 * * @example * ```typescript * // 使用便捷函数创建实例 * const commander = match('hello <name:text>'); * * // 等同于 * const commander = new Commander('hello <name:text>'); * ``` */ export function match(pattern, typedLiteralFields) { return new Commander(pattern, typedLiteralFields); }