/** * Match interactive button_reply / template button payloads to handlers by * **prefix** (longest match wins) or by exact id. */ export interface ButtonHandlerArgs { ctx: Ctx; buttonId: string; suffix: string; } export type ButtonHandler = (args: Ctx & { buttonId: string; suffix: string }) => void | Promise; interface PrefixEntry { prefix: string; handler: ButtonHandler; } export class ButtonRouter { private exactHandlers = new Map>(); private prefixHandlers: PrefixEntry[] = []; private _fallback: ButtonHandler | null = null; exact(id: string, handler: ButtonHandler): this { this.exactHandlers.set(id, handler); return this; } prefix(prefix: string, handler: ButtonHandler): this { this.prefixHandlers.push({ prefix, handler }); this.prefixHandlers.sort((a, b) => b.prefix.length - a.prefix.length); return this; } fallback(handler: ButtonHandler): this { this._fallback = handler; return this; } async dispatch(buttonId: string | undefined | null, ctx: Ctx): Promise { if (!buttonId) return false; const exact = this.exactHandlers.get(buttonId); if (exact) { await exact({ ...(ctx as object), buttonId, suffix: '' } as Ctx & { buttonId: string; suffix: string }); return true; } for (const { prefix, handler } of this.prefixHandlers) { if (buttonId.startsWith(prefix)) { await handler({ ...(ctx as object), buttonId, suffix: buttonId.slice(prefix.length) } as Ctx & { buttonId: string; suffix: string }); return true; } } if (this._fallback) { await this._fallback({ ...(ctx as object), buttonId, suffix: '' } as Ctx & { buttonId: string; suffix: string }); return true; } return false; } }