UNPKG

telegraf-ts

Version:
485 lines (418 loc) 11.9 kB
const Context = require('./context'); class Composer { constructor(...fns) { this.handler = Composer.compose(fns); } use(...fns) { this.handler = Composer.compose([this.handler, ...fns]); return this; } on(updateTypes, ...fns) { return this.use(Composer.mount(updateTypes, ...fns)); } hears(triggers, ...fns) { return this.use(Composer.hears(triggers, ...fns)); } command(commands, ...fns) { return this.use(Composer.command(commands, ...fns)); } action(triggers, ...fns) { return this.use(Composer.action(triggers, ...fns)); } inlineQuery(triggers, ...fns) { return this.use(Composer.inlineQuery(triggers, ...fns)); } gameQuery(...fns) { return this.use(Composer.gameQuery(...fns)); } drop(predicate) { return this.use(Composer.drop(predicate)); } filter(predicate) { return this.use(Composer.filter(predicate)); } entity(...args) { return this.use(Composer.entity(...args)); } email(...args) { return this.use(Composer.email(...args)); } url(...args) { return this.use(Composer.url(...args)); } textLink(...args) { return this.use(Composer.textLink(...args)); } textMention(...args) { return this.use(Composer.textMention(...args)); } mention(...args) { return this.use(Composer.mention(...args)); } phone(...args) { return this.use(Composer.phone(...args)); } hashtag(...args) { return this.use(Composer.hashtag(...args)); } cashtag(...args) { return this.use(Composer.cashtag(...args)); } start(...fns) { return this.command( 'start', Composer.tap(ctx => { ctx.startPayload = ctx.message.text.substring(7); }), ...fns, ); } help(...fns) { return this.command('help', ...fns); } settings(...fns) { return this.command('settings', ...fns); } middleware() { return this.handler; } static reply(...args) { return ctx => ctx.reply(...args); } static catchAll(...fns) { return Composer.catch(err => { console.error(); console.error((err.stack || err.toString()).replace(/^/gm, ' ')); console.error(); }, ...fns); } static catch(errorHandler, ...fns) { const handler = Composer.compose(fns); return (ctx, next) => Promise.resolve(handler(ctx, next)).catch(err => errorHandler(err, ctx)); } static fork(middleware) { const handler = Composer.unwrap(middleware); return (ctx, next) => { setImmediate(handler, ctx, Composer.safePassThru()); return next(ctx); }; } static tap(middleware) { const handler = Composer.unwrap(middleware); return (ctx, next) => Promise.resolve(handler(ctx, Composer.safePassThru())).then(() => next(ctx), ); } static passThru() { return (ctx, next) => next(ctx); } static safePassThru() { return (ctx, next) => typeof next === 'function' ? next(ctx) : Promise.resolve(); } static lazy(factoryFn) { if (typeof factoryFn !== 'function') { throw new Error('Argument must be a function'); } return (ctx, next) => Promise.resolve(factoryFn(ctx)).then(middleware => Composer.unwrap(middleware)(ctx, next), ); } static log(logFn = console.log) { return Composer.fork(ctx => logFn(JSON.stringify(ctx.update, null, 2))); } static branch(predicate, trueMiddleware, falseMiddleware) { if (typeof predicate !== 'function') { return predicate ? trueMiddleware : falseMiddleware; } return Composer.lazy(ctx => Promise.resolve(predicate(ctx)).then(value => value ? trueMiddleware : falseMiddleware, ), ); } static optional(predicate, ...fns) { return Composer.branch( predicate, Composer.compose(fns), Composer.safePassThru(), ); } static filter(predicate) { return Composer.branch(predicate, Composer.safePassThru(), () => {}); } static drop(predicate) { return Composer.branch(predicate, () => {}, Composer.safePassThru()); } static dispatch(routeFn, handlers) { return typeof routeFn === 'function' ? Composer.lazy(ctx => Promise.resolve(routeFn(ctx)).then(value => handlers[value]), ) : handlers[routeFn]; } static mount(updateType, ...fns) { const updateTypes = normalizeTextArguments(updateType); const predicate = ctx => updateTypes.includes(ctx.updateType) || updateTypes.some(type => ctx.updateSubTypes.includes(type)); return Composer.optional(predicate, ...fns); } static entity(predicate, ...fns) { if (typeof predicate !== 'function') { const entityTypes = normalizeTextArguments(predicate); return Composer.entity(({ type }) => entityTypes.includes(type), ...fns); } return Composer.optional(ctx => { const message = ctx.message || ctx.channelPost; const entities = message && (message.entities || message.caption_entities); const text = message && (message.text || message.caption); return ( entities && entities.some(entity => predicate( entity, text.substring(entity.offset, entity.offset + entity.length), ctx, ), ) ); }, ...fns); } static entityText(entityType, predicate, ...fns) { if (fns.length === 0) { return Array.isArray(predicate) ? Composer.entity(entityType, ...predicate) : Composer.entity(entityType, predicate); } const triggers = normalizeTriggers(predicate); return Composer.entity(({ type }, value, ctx) => { if (type !== entityType) { return false; } for (const trigger of triggers) { ctx.match = trigger(value, ctx); if (ctx.match) { return true; } } }, ...fns); } static email(email, ...fns) { return Composer.entityText('email', email, ...fns); } static phone(number, ...fns) { return Composer.entityText('phone_number', number, ...fns); } static url(url, ...fns) { return Composer.entityText('url', url, ...fns); } static textLink(link, ...fns) { return Composer.entityText('text_link', link, ...fns); } static textMention(mention, ...fns) { return Composer.entityText('text_mention', mention, ...fns); } static mention(mention, ...fns) { return Composer.entityText( 'mention', normalizeTextArguments(mention, '@'), ...fns, ); } static hashtag(hashtag, ...fns) { return Composer.entityText( 'hashtag', normalizeTextArguments(hashtag, '#'), ...fns, ); } static cashtag(cashtag, ...fns) { return Composer.entityText( 'cashtag', normalizeTextArguments(cashtag, '$'), ...fns, ); } static match(triggers, ...fns) { return Composer.optional(ctx => { const text = (ctx.message && (ctx.message.caption || ctx.message.text)) || (ctx.callbackQuery && ctx.callbackQuery.data) || (ctx.inlineQuery && ctx.inlineQuery.query); for (const trigger of triggers) { ctx.match = trigger(text, ctx); if (ctx.match) { return true; } } }, ...fns); } static hears(triggers, ...fns) { return Composer.mount( 'text', Composer.match(normalizeTriggers(triggers), ...fns), ); } static command(command, ...fns) { if (fns.length === 0) { return Composer.entity(['bot_command'], command); } const commands = normalizeTextArguments(command, '/'); return Composer.mount( 'text', Composer.lazy(ctx => { const groupCommands = ctx.me && (ctx.chat.type === 'group' || ctx.chat.type === 'supergroup') ? commands.map(command => `${command}@${ctx.me}`) : []; return Composer.entity( ({ offset, type }, value) => offset === 0 && type === 'bot_command' && (commands.includes(value) || groupCommands.includes(value)), ...fns, ); }), ); } static action(triggers, ...fns) { return Composer.mount( 'callback_query', Composer.match(normalizeTriggers(triggers), ...fns), ); } static inlineQuery(triggers, ...fns) { return Composer.mount( 'inline_query', Composer.match(normalizeTriggers(triggers), ...fns), ); } static acl(userId, ...fns) { if (typeof userId === 'function') { return Composer.optional(userId, ...fns); } const allowed = Array.isArray(userId) ? userId : [userId]; return Composer.optional( ctx => !ctx.from || allowed.includes(ctx.from.id), ...fns, ); } static memberStatus(status, ...fns) { const statuses = Array.isArray(status) ? status : [status]; return Composer.optional( ctx => ctx.message && ctx .getChatMember(ctx.message.from.id) .then(member => member && statuses.includes(member.status)), ...fns, ); } static admin(...fns) { return Composer.memberStatus(['administrator', 'creator'], ...fns); } static creator(...fns) { return Composer.memberStatus('creator', ...fns); } static chatType(type, ...fns) { const types = Array.isArray(type) ? type : [type]; return Composer.optional( ctx => ctx.chat && types.includes(ctx.chat.type), ...fns, ); } static privateChat(...fns) { return Composer.chatType('private', ...fns); } static groupChat(...fns) { return Composer.chatType(['group', 'supergroup'], ...fns); } static gameQuery(...fns) { return Composer.mount( 'callback_query', Composer.optional(ctx => ctx.callbackQuery.game_short_name, ...fns), ); } static unwrap(handler) { if (!handler) { throw new Error('Handler is undefined'); } return typeof handler.middleware === 'function' ? handler.middleware() : handler; } static compose(middlewares) { if (!Array.isArray(middlewares)) { throw new Error('Middlewares must be an array'); } if (middlewares.length === 0) { return Composer.safePassThru(); } if (middlewares.length === 1) { return Composer.unwrap(middlewares[0]); } return (ctx, next) => { let index = -1; return execute(0, ctx); function execute(i, context) { if (!(context instanceof Context)) { return Promise.reject( new Error('next(ctx) called with invalid context'), ); } if (i <= index) { return Promise.reject(new Error('next() called multiple times')); } index = i; const handler = middlewares[i] ? Composer.unwrap(middlewares[i]) : next; if (!handler) { return Promise.resolve(); } try { return Promise.resolve( handler(context, (ctx = context) => execute(i + 1, ctx)), ); } catch (err) { return Promise.reject(err); } } }; } } function normalizeTriggers(triggers) { if (!Array.isArray(triggers)) { triggers = [triggers]; } return triggers.map(trigger => { if (!trigger) { throw new Error('Invalid trigger'); } if (typeof trigger === 'function') { return trigger; } if (trigger instanceof RegExp) { return value => { trigger.lastIndex = 0; return trigger.exec(value || ''); }; } return value => (trigger === value ? value : null); }); } function normalizeTextArguments(argument, prefix) { const args = Array.isArray(argument) ? argument : [argument]; return args .filter(Boolean) .map(arg => prefix && typeof arg === 'string' && !arg.startsWith(prefix) ? `${prefix}${arg}` : arg, ); } module.exports = Composer;