import { BranchMiddlewareCondition, Middleware, MiddlewareReturn, NextMiddleware, UnknownObject } from 'middleware-io'; import { Composer as BaseComposer, MessageContext } from 'vk-io'; declare module 'middleware-io' { class Composer { filter(condition: BranchMiddlewareCondition, filterMiddleware: Middleware): this; } } type AllowArray = T | T[]; type HearFunctionCondition = (value: V, context: T) => boolean; type HearCondition = HearFunctionCondition | RegExp | string | number | boolean; type HearObjectCondition> = Record>> & { [P in keyof T]?: AllowArray>; }; type HearConditions> = ( AllowArray> | AllowArray> ); function splitPath(path: string): string[] { return ( path .replace(/\[([^[\]]*)\]/g, '.$1.') .split('.') .filter(Boolean) ); } function getObjectValue(source: Record, selectors: string[]): any { let link = source; for (const selector of selectors) { if (!link[selector]) { return undefined; } link = link[selector]; } return link; } function unifyCondition(condition: unknown): Function { if (typeof condition === 'function') { return condition; } if (condition instanceof RegExp) { return (text: string | undefined): boolean => ( condition.test(text!) ); } if (Array.isArray(condition)) { const arrayConditions = condition.map(unifyCondition); return (value: string | undefined): boolean => ( Array.isArray(value) ? arrayConditions.every((cond): boolean => ( value.some((val): boolean => cond(val)) )) : arrayConditions.some((cond): boolean => ( cond(value) )) ); } return (value: string | undefined): boolean => value === condition; } export class Composer extends BaseComposer { constructor() { super(); } public hear( hearConditions: HearConditions, handler: Middleware ): this { const rawConditions = !Array.isArray(hearConditions) ? [hearConditions] : hearConditions; const hasConditions = rawConditions.every(Boolean); if (!hasConditions) { throw new Error('Condition should be not empty'); } if (typeof handler !== 'function') { throw new TypeError('Handler must be a function'); } let textCondition = false; let functionCondtion = false; const conditions = rawConditions.map((condition): Function => { if (typeof condition === 'object' && !(condition instanceof RegExp)) { functionCondtion = true; const entries = Object.entries(condition).map(([path, value]): [string[], Function] => ( [splitPath(path), unifyCondition(value)] )); return (text: string | undefined, context: C): boolean => ( entries.every(([selectors, callback]): boolean => { const value = getObjectValue(context, selectors); return callback(value, context); }) ); } if (typeof condition === 'function') { functionCondtion = true; return condition; } textCondition = true; if (condition instanceof RegExp) { return (text: string | undefined, context: C): boolean => { const passed = condition.test(text!); if (passed) { context.$match = text!.match(condition)!; } return passed; }; } const stringCondition = String(condition); return (text: string | undefined): boolean => text === stringCondition; }); const needText = textCondition && functionCondtion === false; this.use((context: C & T, next: NextMiddleware): MiddlewareReturn => { const { text } = context; if (needText && text === undefined) { return next(); } const hasSome = conditions.some((condition): boolean => ( condition(text, context) )); return hasSome ? handler(context, next) : next(); }); return this; } }