UNPKG

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