UNPKG

20.9 kBJavaScriptView Raw
1"use strict";
2/** @format */
3var __importDefault = (this && this.__importDefault) || function (mod) {
4 return (mod && mod.__esModule) ? mod : { "default": mod };
5};
6Object.defineProperty(exports, "__esModule", { value: true });
7exports.Composer = void 0;
8const context_1 = __importDefault(require("./context"));
9const filters_1 = require("./filters");
10const args_1 = require("./core/helpers/args");
11function always(x) {
12 return () => x;
13}
14const anoop = always(Promise.resolve());
15class Composer {
16 constructor(...fns) {
17 this.handler = Composer.compose(fns);
18 }
19 /**
20 * Registers a middleware.
21 */
22 use(...fns) {
23 this.handler = Composer.compose([this.handler, ...fns]);
24 return this;
25 }
26 /**
27 * Registers middleware for handling updates
28 * matching given type guard function.
29 * @deprecated use `Composer::on`
30 */
31 guard(guardFn, ...fns) {
32 return this.use(Composer.guard(guardFn, ...fns));
33 }
34 on(filters, ...fns) {
35 // @ts-expect-error This should get resolved when the overloads are removed in v5
36 return this.use(Composer.on(filters, ...fns));
37 }
38 /**
39 * Registers middleware for handling matching text messages.
40 */
41 hears(triggers, ...fns) {
42 return this.use(Composer.hears(triggers, ...fns));
43 }
44 /**
45 * Registers middleware for handling specified commands.
46 */
47 command(command, ...fns) {
48 return this.use(Composer.command(command, ...fns));
49 }
50 /**
51 * Registers middleware for handling matching callback queries.
52 */
53 action(triggers, ...fns) {
54 return this.use(Composer.action(triggers, ...fns));
55 }
56 /**
57 * Registers middleware for handling matching inline queries.
58 */
59 inlineQuery(triggers, ...fns) {
60 return this.use(Composer.inlineQuery(triggers, ...fns));
61 }
62 /**
63 * Registers middleware for handling game queries
64 */
65 gameQuery(...fns) {
66 return this.use(Composer.gameQuery(...fns));
67 }
68 /**
69 * Registers middleware for dropping matching updates.
70 */
71 drop(predicate) {
72 return this.use(Composer.drop(predicate));
73 }
74 /** @deprecated use `Composer::drop` */
75 filter(predicate) {
76 return this.use(Composer.filter(predicate));
77 }
78 entity(predicate, ...fns) {
79 return this.use(Composer.entity(predicate, ...fns));
80 }
81 email(email, ...fns) {
82 return this.use(Composer.email(email, ...fns));
83 }
84 url(url, ...fns) {
85 return this.use(Composer.url(url, ...fns));
86 }
87 textLink(link, ...fns) {
88 return this.use(Composer.textLink(link, ...fns));
89 }
90 textMention(mention, ...fns) {
91 return this.use(Composer.textMention(mention, ...fns));
92 }
93 mention(mention, ...fns) {
94 return this.use(Composer.mention(mention, ...fns));
95 }
96 phone(number, ...fns) {
97 return this.use(Composer.phone(number, ...fns));
98 }
99 hashtag(hashtag, ...fns) {
100 return this.use(Composer.hashtag(hashtag, ...fns));
101 }
102 cashtag(cashtag, ...fns) {
103 return this.use(Composer.cashtag(cashtag, ...fns));
104 }
105 spoiler(text, ...fns) {
106 return this.use(Composer.spoiler(text, ...fns));
107 }
108 /**
109 * Registers a middleware for handling /start
110 */
111 start(...fns) {
112 const handler = Composer.compose(fns);
113 return this.command('start', (ctx, next) => handler(Object.assign(ctx, { startPayload: ctx.payload }), next));
114 }
115 /**
116 * Registers a middleware for handling /help
117 */
118 help(...fns) {
119 return this.command('help', ...fns);
120 }
121 /**
122 * Registers a middleware for handling /settings
123 */
124 settings(...fns) {
125 return this.command('settings', ...fns);
126 }
127 middleware() {
128 return this.handler;
129 }
130 static reply(...args) {
131 return (ctx) => ctx.reply(...args);
132 }
133 static catch(errorHandler, ...fns) {
134 const handler = Composer.compose(fns);
135 // prettier-ignore
136 return (ctx, next) => Promise.resolve(handler(ctx, next))
137 .catch((err) => errorHandler(err, ctx));
138 }
139 /**
140 * Generates middleware that runs in the background.
141 */
142 static fork(middleware) {
143 const handler = Composer.unwrap(middleware);
144 return async (ctx, next) => {
145 await Promise.all([handler(ctx, anoop), next()]);
146 };
147 }
148 static tap(middleware) {
149 const handler = Composer.unwrap(middleware);
150 return (ctx, next) => Promise.resolve(handler(ctx, anoop)).then(() => next());
151 }
152 /**
153 * Generates middleware that gives up control to the next middleware.
154 */
155 static passThru() {
156 return (ctx, next) => next();
157 }
158 static lazy(factoryFn) {
159 if (typeof factoryFn !== 'function') {
160 throw new Error('Argument must be a function');
161 }
162 return (ctx, next) => Promise.resolve(factoryFn(ctx)).then((middleware) => Composer.unwrap(middleware)(ctx, next));
163 }
164 static log(logFn = console.log) {
165 return (ctx, next) => {
166 logFn(JSON.stringify(ctx.update, null, 2));
167 return next();
168 };
169 }
170 /**
171 * @param trueMiddleware middleware to run if the predicate returns true
172 * @param falseMiddleware middleware to run if the predicate returns false
173 */
174 static branch(predicate, trueMiddleware, falseMiddleware) {
175 if (typeof predicate !== 'function') {
176 return Composer.unwrap(predicate ? trueMiddleware : falseMiddleware);
177 }
178 return Composer.lazy((ctx) => Promise.resolve(predicate(ctx)).then((value) => value ? trueMiddleware : falseMiddleware));
179 }
180 /**
181 * Generates optional middleware.
182 * @param predicate predicate to decide on a context object whether to run the middleware
183 * @param middleware middleware to run if the predicate returns true
184 */
185 static optional(predicate, ...fns) {
186 return Composer.branch(predicate, Composer.compose(fns), Composer.passThru());
187 }
188 /** @deprecated use `Composer.drop` */
189 static filter(predicate) {
190 return Composer.branch(predicate, Composer.passThru(), anoop);
191 }
192 /**
193 * Generates middleware for dropping matching updates.
194 */
195 static drop(predicate) {
196 return Composer.branch(predicate, anoop, Composer.passThru());
197 }
198 static dispatch(routeFn, handlers) {
199 return Composer.lazy((ctx) => Promise.resolve(routeFn(ctx)).then((value) => handlers[value]));
200 }
201 // EXPLANATION FOR THE ts-expect-error ANNOTATIONS
202 // The annotations around function invocations with `...fns` are there
203 // whenever we perform validation logic that the flow analysis of TypeScript
204 // cannot comprehend. We always make sure that the middleware functions are
205 // only invoked with properly constrained context objects, but this cannot be
206 // determined automatically.
207 /**
208 * Generates optional middleware based on a predicate that only operates on `ctx.update`.
209 *
210 * Example:
211 * ```ts
212 * import { Composer, Update } from 'telegraf'
213 *
214 * const predicate = (u): u is Update.MessageUpdate => 'message' in u
215 * const middleware = Composer.guard(predicate, (ctx) => {
216 * const message = ctx.update.message
217 * })
218 * ```
219 *
220 * Note that `Composer.on('message')` is preferred over this.
221 *
222 * @param guardFn predicate to decide whether to run the middleware based on the `ctx.update` object
223 * @param fns middleware to run if the predicate returns true
224 * @see `Composer.optional` for a more generic version of this method that allows the predicate to operate on `ctx` itself
225 * @deprecated use `Composer.on`
226 */
227 static guard(guardFn, ...fns) {
228 return Composer.optional((ctx) => guardFn(ctx.update),
229 // @ts-expect-error see explanation above
230 ...fns);
231 }
232 static on(updateType, ...fns) {
233 const filters = Array.isArray(updateType) ? updateType : [updateType];
234 const predicate = (update) => {
235 for (const filter of filters) {
236 if (
237 // TODO: this should change to === 'function' once TS bug is fixed
238 // https://github.com/microsoft/TypeScript/pull/51502
239 typeof filter !== 'string'
240 ? // filter is a type guard
241 filter(update)
242 : // check if filter is the update type
243 filter in update ||
244 // check if filter is the msg type
245 // TODO: remove in v5!
246 ('message' in update && filter in update.message)) {
247 return true;
248 }
249 }
250 return false;
251 };
252 return Composer.optional((ctx) => predicate(ctx.update), ...fns);
253 }
254 static entity(predicate, ...fns) {
255 if (typeof predicate !== 'function') {
256 const entityTypes = normaliseTextArguments(predicate);
257 return Composer.entity(({ type }) => entityTypes.includes(type), ...fns);
258 }
259 return Composer.optional((ctx) => {
260 var _a;
261 const msg = (_a = ctx.message) !== null && _a !== void 0 ? _a : ctx.channelPost;
262 if (msg === undefined) {
263 return false;
264 }
265 const text = getText(msg);
266 const entities = getEntities(msg);
267 if (text === undefined)
268 return false;
269 return entities.some((entity) => predicate(entity, text.substring(entity.offset, entity.offset + entity.length), ctx));
270 },
271 // @ts-expect-error see explanation above
272 ...fns);
273 }
274 static entityText(entityType, predicate, ...fns) {
275 if (fns.length === 0) {
276 // prettier-ignore
277 return Array.isArray(predicate)
278 // @ts-expect-error predicate is really the middleware
279 ? Composer.entity(entityType, ...predicate)
280 // @ts-expect-error predicate is really the middleware
281 : Composer.entity(entityType, predicate);
282 }
283 const triggers = normaliseTriggers(predicate);
284 return Composer.entity(({ type }, value, ctx) => {
285 if (type !== entityType) {
286 return false;
287 }
288 for (const trigger of triggers) {
289 // @ts-expect-error define so far unknown property `match`
290 if ((ctx.match = trigger(value, ctx))) {
291 return true;
292 }
293 }
294 return false;
295 },
296 // @ts-expect-error see explanation above
297 ...fns);
298 }
299 static email(email, ...fns) {
300 return Composer.entityText('email', email, ...fns);
301 }
302 static phone(number, ...fns) {
303 return Composer.entityText('phone_number', number, ...fns);
304 }
305 static url(url, ...fns) {
306 return Composer.entityText('url', url, ...fns);
307 }
308 static textLink(link, ...fns) {
309 return Composer.entityText('text_link', link, ...fns);
310 }
311 static textMention(mention, ...fns) {
312 return Composer.entityText('text_mention', mention, ...fns);
313 }
314 static mention(mention, ...fns) {
315 return Composer.entityText('mention', normaliseTextArguments(mention, '@'), ...fns);
316 }
317 static hashtag(hashtag, ...fns) {
318 return Composer.entityText('hashtag', normaliseTextArguments(hashtag, '#'), ...fns);
319 }
320 static cashtag(cashtag, ...fns) {
321 return Composer.entityText('cashtag', normaliseTextArguments(cashtag, '$'), ...fns);
322 }
323 static spoiler(text, ...fns) {
324 return Composer.entityText('spoiler', text, ...fns);
325 }
326 static match(triggers, ...fns) {
327 const handler = Composer.compose(fns);
328 return (ctx, next) => {
329 var _a, _b, _c, _d;
330 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;
331 if (text === undefined)
332 return next();
333 for (const trigger of triggers) {
334 const match = trigger(text, ctx);
335 if (match)
336 return handler(Object.assign(ctx, { match }), next);
337 }
338 return next();
339 };
340 }
341 /**
342 * Generates middleware for handling matching text messages.
343 */
344 static hears(triggers, ...fns) {
345 return Composer.on('text', Composer.match(normaliseTriggers(triggers), ...fns));
346 }
347 /**
348 * Generates middleware for handling specified commands.
349 */
350 static command(command, ...fns) {
351 if (fns.length === 0)
352 // @ts-expect-error command is really the middleware
353 return Composer.entity('bot_command', command);
354 const triggers = normaliseTriggers(command);
355 const filter = (0, filters_1.message)('text');
356 const handler = Composer.compose(fns);
357 return Composer.on(filter, (ctx, next) => {
358 const { entities } = ctx.message;
359 const cmdEntity = entities === null || entities === void 0 ? void 0 : entities[0];
360 if ((cmdEntity === null || cmdEntity === void 0 ? void 0 : cmdEntity.type) !== 'bot_command')
361 return next();
362 if (cmdEntity.offset > 0)
363 return next();
364 const len = cmdEntity.length;
365 const text = ctx.message.text;
366 const [cmdPart, to] = text.slice(0, len).split('@');
367 if (!cmdPart)
368 return next();
369 // always check for bot's own username case-insensitively
370 if (to && to.toLowerCase() !== ctx.me.toLowerCase())
371 return next();
372 const command = cmdPart.slice(1);
373 for (const trigger of triggers)
374 if (trigger(command, ctx)) {
375 const payloadOffset = len + 1;
376 const payload = text.slice(payloadOffset);
377 const c = Object.assign(ctx, { command, payload, args: [] });
378 let _args = undefined;
379 // using defineProperty only to make parsing lazy on access
380 Object.defineProperty(c, 'args', {
381 enumerable: true,
382 configurable: true,
383 get() {
384 if (_args != null)
385 return _args;
386 // once parsed, cache and don't parse again on every access
387 return (_args = (0, args_1.argsParser)(payload, entities, payloadOffset));
388 },
389 set(args) {
390 _args = args;
391 },
392 });
393 return handler(c, next);
394 }
395 return next();
396 });
397 }
398 /**
399 * Generates middleware for handling matching callback queries.
400 */
401 static action(triggers, ...fns) {
402 return Composer.on('callback_query', Composer.match(normaliseTriggers(triggers), ...fns));
403 }
404 /**
405 * Generates middleware for handling matching inline queries.
406 */
407 static inlineQuery(triggers, ...fns) {
408 return Composer.on('inline_query', Composer.match(normaliseTriggers(triggers), ...fns));
409 }
410 /**
411 * Generates middleware responding only to specified users.
412 */
413 static acl(userId, ...fns) {
414 if (typeof userId === 'function') {
415 return Composer.optional(userId, ...fns);
416 }
417 const allowed = Array.isArray(userId) ? userId : [userId];
418 // prettier-ignore
419 return Composer.optional((ctx) => !ctx.from || allowed.includes(ctx.from.id), ...fns);
420 }
421 static memberStatus(status, ...fns) {
422 const statuses = Array.isArray(status) ? status : [status];
423 return Composer.optional(async (ctx) => {
424 if (ctx.message === undefined)
425 return false;
426 const member = await ctx.getChatMember(ctx.message.from.id);
427 return statuses.includes(member.status);
428 }, ...fns);
429 }
430 /**
431 * Generates middleware responding only to chat admins and chat creator.
432 */
433 static admin(...fns) {
434 return Composer.memberStatus(['administrator', 'creator'], ...fns);
435 }
436 /**
437 * Generates middleware responding only to chat creator.
438 */
439 static creator(...fns) {
440 return Composer.memberStatus('creator', ...fns);
441 }
442 /**
443 * Generates middleware running only in specified chat types.
444 */
445 static chatType(type, ...fns) {
446 const types = Array.isArray(type) ? type : [type];
447 return Composer.optional((ctx) => {
448 const chat = ctx.chat;
449 return chat !== undefined && types.includes(chat.type);
450 }, ...fns);
451 }
452 /**
453 * Generates middleware running only in private chats.
454 */
455 static privateChat(...fns) {
456 return Composer.chatType('private', ...fns);
457 }
458 /**
459 * Generates middleware running only in groups and supergroups.
460 */
461 static groupChat(...fns) {
462 return Composer.chatType(['group', 'supergroup'], ...fns);
463 }
464 /**
465 * Generates middleware for handling game queries.
466 */
467 static gameQuery(...fns) {
468 return Composer.guard((0, filters_1.callbackQuery)('game_short_name'), ...fns);
469 }
470 static unwrap(handler) {
471 if (!handler) {
472 throw new Error('Handler is undefined');
473 }
474 return 'middleware' in handler ? handler.middleware() : handler;
475 }
476 static compose(middlewares) {
477 if (!Array.isArray(middlewares)) {
478 throw new Error('Middlewares must be an array');
479 }
480 if (middlewares.length === 0) {
481 return Composer.passThru();
482 }
483 if (middlewares.length === 1) {
484 // Quite literally asserted in the above condition
485 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
486 return Composer.unwrap(middlewares[0]);
487 }
488 return (ctx, next) => {
489 let index = -1;
490 return execute(0, ctx);
491 async function execute(i, context) {
492 var _a;
493 if (!(context instanceof context_1.default)) {
494 throw new Error('next(ctx) called with invalid context');
495 }
496 if (i <= index) {
497 throw new Error('next() called multiple times');
498 }
499 index = i;
500 const handler = Composer.unwrap((_a = middlewares[i]) !== null && _a !== void 0 ? _a : next);
501 await handler(context, async (ctx = context) => {
502 await execute(i + 1, ctx);
503 });
504 }
505 };
506 }
507}
508exports.Composer = Composer;
509/**
510 * Generates middleware for handling provided update types.
511 * @deprecated use `Composer.on` instead
512 */
513Composer.mount = Composer.on;
514function escapeRegExp(s) {
515 // $& means the whole matched string
516 return s.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&');
517}
518function normaliseTriggers(triggers) {
519 if (!Array.isArray(triggers))
520 triggers = [triggers];
521 return triggers.map((trigger) => {
522 if (!trigger)
523 throw new Error('Invalid trigger');
524 if (typeof trigger === 'function')
525 return trigger;
526 if (trigger instanceof RegExp)
527 return (value = '') => {
528 trigger.lastIndex = 0;
529 return trigger.exec(value);
530 };
531 const regex = new RegExp(`^${escapeRegExp(trigger)}$`);
532 return (value) => regex.exec(value);
533 });
534}
535function getEntities(msg) {
536 var _a, _b;
537 if (msg == null)
538 return [];
539 if ('caption_entities' in msg)
540 return (_a = msg.caption_entities) !== null && _a !== void 0 ? _a : [];
541 if ('entities' in msg)
542 return (_b = msg.entities) !== null && _b !== void 0 ? _b : [];
543 return [];
544}
545function getText(msg) {
546 if (msg == null)
547 return undefined;
548 if ('caption' in msg)
549 return msg.caption;
550 if ('text' in msg)
551 return msg.text;
552 if ('data' in msg)
553 return msg.data;
554 if ('game_short_name' in msg)
555 return msg.game_short_name;
556 return undefined;
557}
558function normaliseTextArguments(argument, prefix = '') {
559 const args = Array.isArray(argument) ? argument : [argument];
560 // prettier-ignore
561 return args
562 .filter(Boolean)
563 .map((arg) => prefix && typeof arg === 'string' && !arg.startsWith(prefix) ? `${prefix}${arg}` : arg);
564}
565exports.default = Composer;