UNPKG

telegraf

Version:

Modern Telegram Bot Framework

522 lines (521 loc) 18.7 kB
"use strict"; /** @format */ Object.defineProperty(exports, "__esModule", { value: true }); exports.Composer = void 0; const context_1 = require("./context"); function always(x) { return () => x; } const anoop = always(Promise.resolve()); class Composer { constructor(...fns) { this.handler = Composer.compose(fns); } /** * Registers a middleware. */ use(...fns) { this.handler = Composer.compose([this.handler, ...fns]); return this; } /** * Registers middleware for handling updates * matching given type guard function. */ guard(guardFn, ...fns) { return this.use(Composer.guard(guardFn, ...fns)); } /** * Registers middleware for handling provided update types. */ on(updateType, ...fns) { return this.use(Composer.mount(updateType, ...fns)); } /** * Registers middleware for handling matching text messages. */ hears(triggers, ...fns) { return this.use(Composer.hears(triggers, ...fns)); } /** * Registers middleware for handling specified commands. */ command(command, ...fns) { return this.use(Composer.command(command, ...fns)); } /** * Registers middleware for handling matching callback queries. */ action(triggers, ...fns) { return this.use(Composer.action(triggers, ...fns)); } /** * Registers middleware for handling matching inline queries. */ inlineQuery(triggers, ...fns) { return this.use(Composer.inlineQuery(triggers, ...fns)); } /** * Registers middleware for handling game queries */ gameQuery(...fns) { return this.use(Composer.gameQuery(...fns)); } /** * Registers middleware for dropping matching updates. */ drop(predicate) { return this.use(Composer.drop(predicate)); } filter(predicate) { return this.use(Composer.filter(predicate)); } entity(predicate, ...fns) { return this.use(Composer.entity(predicate, ...fns)); } email(email, ...fns) { return this.use(Composer.email(email, ...fns)); } url(url, ...fns) { return this.use(Composer.url(url, ...fns)); } textLink(link, ...fns) { return this.use(Composer.textLink(link, ...fns)); } textMention(mention, ...fns) { return this.use(Composer.textMention(mention, ...fns)); } mention(mention, ...fns) { return this.use(Composer.mention(mention, ...fns)); } phone(number, ...fns) { return this.use(Composer.phone(number, ...fns)); } hashtag(hashtag, ...fns) { return this.use(Composer.hashtag(hashtag, ...fns)); } cashtag(cashtag, ...fns) { return this.use(Composer.cashtag(cashtag, ...fns)); } /** * Registers a middleware for handling /start */ start(...fns) { const handler = Composer.compose(fns); return this.command('start', (ctx, next) => { const entity = ctx.message.entities[0]; const startPayload = ctx.message.text.slice(entity.length + 1); return handler(Object.assign(ctx, { startPayload }), next); }); } /** * Registers a middleware for handling /help */ help(...fns) { return this.command('help', ...fns); } /** * Registers a middleware for handling /settings */ settings(...fns) { return this.command('settings', ...fns); } middleware() { return this.handler; } static reply(...args) { return (ctx) => ctx.reply(...args); } static catch(errorHandler, ...fns) { const handler = Composer.compose(fns); // prettier-ignore return (ctx, next) => Promise.resolve(handler(ctx, next)) .catch((err) => errorHandler(err, ctx)); } /** * Generates middleware that runs in the background. */ static fork(middleware) { const handler = Composer.unwrap(middleware); return async (ctx, next) => { await Promise.all([handler(ctx, anoop), next()]); }; } static tap(middleware) { const handler = Composer.unwrap(middleware); return (ctx, next) => Promise.resolve(handler(ctx, anoop)).then(() => next()); } /** * Generates middleware that gives up control to the next middleware. */ static passThru() { return (ctx, next) => next(); } 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 (ctx, next) => { logFn(JSON.stringify(ctx.update, null, 2)); return next(); }; } /** * @param trueMiddleware middleware to run if the predicate returns true * @param falseMiddleware middleware to run if the predicate returns false */ static branch(predicate, trueMiddleware, falseMiddleware) { if (typeof predicate !== 'function') { return Composer.unwrap(predicate ? trueMiddleware : falseMiddleware); } return Composer.lazy((ctx) => Promise.resolve(predicate(ctx)).then((value) => value ? trueMiddleware : falseMiddleware)); } /** * Generates optional middleware. * @param predicate predicate to decide on a context object whether to run the middleware * @param middleware middleware to run if the predicate returns true */ static optional(predicate, ...fns) { return Composer.branch(predicate, Composer.compose(fns), Composer.passThru()); } static filter(predicate) { return Composer.branch(predicate, Composer.passThru(), anoop); } /** * Generates middleware for dropping matching updates. */ static drop(predicate) { return Composer.branch(predicate, anoop, Composer.passThru()); } static dispatch(routeFn, handlers) { return Composer.lazy((ctx) => Promise.resolve(routeFn(ctx)).then((value) => handlers[value])); } // EXPLANATION FOR THE ts-expect-error ANNOTATIONS // The annotations around function invocations with `...fns` are there // whenever we perform validation logic that the flow analysis of TypeScript // cannot comprehend. We always make sure that the middleware functions are // only invoked with properly constrained context objects, but this cannot be // determined automatically. /** * Generates optional middleware based on a predicate that only operates on `ctx.update`. * * Example: * ```ts * import { Composer, Update } from 'telegraf' * * const predicate = (u): u is Update.MessageUpdate => 'message' in u * const middleware = Composer.guard(predicate, (ctx) => { * const message = ctx.update.message * }) * ``` * * Note that `Composer.mount('message')` is preferred over this. * * @param guardFn predicate to decide whether to run the middleware based on the `ctx.update` object * @param fns middleware to run if the predicate returns true * @see `Composer.optional` for a more generic version of this method that allows the predicate to operate on `ctx` itself */ static guard(guardFn, ...fns) { return Composer.optional((ctx) => guardFn(ctx.update), // @ts-expect-error see explanation above ...fns); } /** * Generates middleware for handling provided update types. * @deprecated use `Composer.on` */ static mount(updateType, ...fns) { return Composer.on(updateType, ...fns); } /** * Generates middleware for handling provided update types. */ static on(updateType, ...fns) { const updateTypes = normalizeTextArguments(updateType); const predicate = (update) => updateTypes.some((type) => // Check update type type in update || // Check message sub-type ('message' in update && type in update.message)); return Composer.guard(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) => { var _a; const msg = (_a = ctx.message) !== null && _a !== void 0 ? _a : ctx.channelPost; if (msg === undefined) { return false; } const text = getText(msg); const entities = getEntities(msg); if (text === undefined) return false; return entities.some((entity) => predicate(entity, text.substring(entity.offset, entity.offset + entity.length), ctx)); // @ts-expect-error see explanation above }, ...fns); } static entityText(entityType, predicate, ...fns) { if (fns.length === 0) { // prettier-ignore return Array.isArray(predicate) // @ts-expect-error predicate is really the middleware ? Composer.entity(entityType, ...predicate) // @ts-expect-error predicate is really the middleware : Composer.entity(entityType, predicate); } const triggers = normalizeTriggers(predicate); return Composer.entity(({ type }, value, ctx) => { if (type !== entityType) { return false; } for (const trigger of triggers) { // @ts-expect-error define so far unknown property `match` if ((ctx.match = trigger(value, ctx))) { return true; } } return false; // @ts-expect-error see explanation above }, ...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) { const handler = Composer.compose(fns); return (ctx, next) => { var _a, _b, _c, _d; const text = (_c = (_b = (_a = getText(ctx.message)) !== null && _a !== void 0 ? _a : getText(ctx.channelPost)) !== null && _b !== void 0 ? _b : getText(ctx.callbackQuery)) !== null && _c !== void 0 ? _c : (_d = ctx.inlineQuery) === null || _d === void 0 ? void 0 : _d.query; if (text === undefined) return next(); for (const trigger of triggers) { // @ts-expect-error const match = trigger(text, ctx); if (match) { // @ts-expect-error define so far unknown property `match` return handler(Object.assign(ctx, { match }), next); } } return next(); }; } /** * Generates middleware for handling matching text messages. */ static hears(triggers, ...fns) { return Composer.mount('text', Composer.match(normalizeTriggers(triggers), ...fns)); } /** * Generates middleware for handling specified commands. */ static command(command, ...fns) { if (fns.length === 0) { // @ts-expect-error command is really the middleware return Composer.entity('bot_command', command); } const commands = normalizeTextArguments(command, '/'); return Composer.mount('text', Composer.lazy((ctx) => { var _a; const groupCommands = ctx.me && ((_a = ctx.chat) === null || _a === void 0 ? void 0 : _a.type.endsWith('group')) ? commands.map((command) => `${command}@${ctx.me}`) : []; return Composer.entity(({ offset, type }, value) => offset === 0 && type === 'bot_command' && (commands.includes(value) || groupCommands.includes(value)), // @ts-expect-error see explanation above ...fns); })); } /** * Generates middleware for handling matching callback queries. */ static action(triggers, ...fns) { return Composer.mount('callback_query', Composer.match(normalizeTriggers(triggers), ...fns)); } /** * Generates middleware for handling matching inline queries. */ static inlineQuery(triggers, ...fns) { return Composer.mount('inline_query', Composer.match(normalizeTriggers(triggers), ...fns)); } /** * Generates middleware responding only to specified users. */ static acl(userId, ...fns) { if (typeof userId === 'function') { return Composer.optional(userId, ...fns); } const allowed = Array.isArray(userId) ? userId : [userId]; // prettier-ignore 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(async (ctx) => { if (ctx.message === undefined) return false; const member = await ctx.getChatMember(ctx.message.from.id); return statuses.includes(member.status); }, ...fns); } /** * Generates middleware responding only to chat admins and chat creator. */ static admin(...fns) { return Composer.memberStatus(['administrator', 'creator'], ...fns); } /** * Generates middleware responding only to chat creator. */ static creator(...fns) { return Composer.memberStatus('creator', ...fns); } /** * Generates middleware running only in specified chat types. */ static chatType(type, ...fns) { const types = Array.isArray(type) ? type : [type]; return Composer.optional((ctx) => { const chat = ctx.chat; return chat !== undefined && types.includes(chat.type); }, ...fns); } /** * Generates middleware running only in private chats. */ static privateChat(...fns) { return Composer.chatType('private', ...fns); } /** * Generates middleware running only in groups and supergroups. */ static groupChat(...fns) { return Composer.chatType(['group', 'supergroup'], ...fns); } /** * Generates middleware for handling game queries. */ static gameQuery(...fns) { return Composer.guard((u) => 'callback_query' in u && 'game_short_name' in u.callback_query, ...fns); } static unwrap(handler) { if (!handler) { throw new Error('Handler is undefined'); } return 'middleware' in handler ? handler.middleware() : handler; } static compose(middlewares) { if (!Array.isArray(middlewares)) { throw new Error('Middlewares must be an array'); } if (middlewares.length === 0) { return Composer.passThru(); } if (middlewares.length === 1) { return Composer.unwrap(middlewares[0]); } return (ctx, next) => { let index = -1; return execute(0, ctx); async function execute(i, context) { var _a; if (!(context instanceof context_1.default)) { throw new Error('next(ctx) called with invalid context'); } if (i <= index) { throw new Error('next() called multiple times'); } index = i; const handler = Composer.unwrap((_a = middlewares[i]) !== null && _a !== void 0 ? _a : next); await handler(context, async (ctx = context) => { await execute(i + 1, ctx); }); } }; } } exports.Composer = Composer; function escapeRegExp(s) { // $& means the whole matched string return s.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); } 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); }; } const regex = new RegExp(`^${escapeRegExp(trigger)}$`); return (value) => regex.exec(value); }); } function getEntities(msg) { var _a, _b; if (msg == null) return []; if ('caption_entities' in msg) return (_a = msg.caption_entities) !== null && _a !== void 0 ? _a : []; if ('entities' in msg) return (_b = msg.entities) !== null && _b !== void 0 ? _b : []; return []; } function getText(msg) { if (msg == null) return undefined; if ('caption' in msg) return msg.caption; if ('text' in msg) return msg.text; if ('data' in msg) return msg.data; if ('game_short_name' in msg) return msg.game_short_name; return undefined; } function normalizeTextArguments(argument, prefix = '') { const args = Array.isArray(argument) ? argument : [argument]; // prettier-ignore return args .filter(Boolean) .map((arg) => prefix && typeof arg === 'string' && !arg.startsWith(prefix) ? `${prefix}${arg}` : arg); } exports.default = Composer;