@tgify/tgify 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +356 -0
  3. package/filters.d.ts +1 -0
  4. package/filters.js +1 -0
  5. package/format.d.ts +1 -0
  6. package/format.js +1 -0
  7. package/future.d.ts +1 -0
  8. package/future.js +1 -0
  9. package/lib/button.js +100 -0
  10. package/lib/cli.mjs +105 -0
  11. package/lib/composer.js +582 -0
  12. package/lib/context.js +1219 -0
  13. package/lib/core/helpers/args.js +57 -0
  14. package/lib/core/helpers/check.js +55 -0
  15. package/lib/core/helpers/compact.js +16 -0
  16. package/lib/core/helpers/deunionize.js +12 -0
  17. package/lib/core/helpers/formatting.js +91 -0
  18. package/lib/core/helpers/util.js +50 -0
  19. package/lib/core/network/client.js +330 -0
  20. package/lib/core/network/error.js +21 -0
  21. package/lib/core/network/multipart-stream.js +71 -0
  22. package/lib/core/network/polling.js +87 -0
  23. package/lib/core/network/webhook.js +54 -0
  24. package/lib/core/types/typegram.js +27 -0
  25. package/lib/filters.js +69 -0
  26. package/lib/format.js +38 -0
  27. package/lib/future.js +149 -0
  28. package/lib/index.js +58 -0
  29. package/lib/input.js +61 -0
  30. package/lib/markup.js +121 -0
  31. package/lib/middleware.js +2 -0
  32. package/lib/reactions.js +84 -0
  33. package/lib/router.js +46 -0
  34. package/lib/scenes/base.js +39 -0
  35. package/lib/scenes/context.js +104 -0
  36. package/lib/scenes/index.js +21 -0
  37. package/lib/scenes/stage.js +49 -0
  38. package/lib/scenes/wizard/context.js +31 -0
  39. package/lib/scenes/wizard/index.js +45 -0
  40. package/lib/scenes.js +17 -0
  41. package/lib/session.js +166 -0
  42. package/lib/telegraf.js +256 -0
  43. package/lib/telegram-types.js +6 -0
  44. package/lib/telegram.js +1240 -0
  45. package/lib/types.js +2 -0
  46. package/lib/utils.js +5 -0
  47. package/markup.d.ts +1 -0
  48. package/markup.js +1 -0
  49. package/package.json +140 -0
  50. package/scenes.d.ts +1 -0
  51. package/scenes.js +1 -0
  52. package/session.d.ts +1 -0
  53. package/session.js +1 -0
  54. package/src/button.ts +182 -0
  55. package/src/composer.ts +1008 -0
  56. package/src/context.ts +1661 -0
  57. package/src/core/helpers/args.ts +63 -0
  58. package/src/core/helpers/check.ts +71 -0
  59. package/src/core/helpers/compact.ts +18 -0
  60. package/src/core/helpers/deunionize.ts +26 -0
  61. package/src/core/helpers/formatting.ts +119 -0
  62. package/src/core/helpers/util.ts +96 -0
  63. package/src/core/network/client.ts +396 -0
  64. package/src/core/network/error.ts +29 -0
  65. package/src/core/network/multipart-stream.ts +45 -0
  66. package/src/core/network/polling.ts +94 -0
  67. package/src/core/network/webhook.ts +58 -0
  68. package/src/core/types/typegram.ts +54 -0
  69. package/src/filters.ts +109 -0
  70. package/src/format.ts +110 -0
  71. package/src/future.ts +213 -0
  72. package/src/index.ts +17 -0
  73. package/src/input.ts +59 -0
  74. package/src/markup.ts +142 -0
  75. package/src/middleware.ts +24 -0
  76. package/src/reactions.ts +118 -0
  77. package/src/router.ts +55 -0
  78. package/src/scenes/base.ts +52 -0
  79. package/src/scenes/context.ts +136 -0
  80. package/src/scenes/index.ts +21 -0
  81. package/src/scenes/stage.ts +71 -0
  82. package/src/scenes/wizard/context.ts +58 -0
  83. package/src/scenes/wizard/index.ts +63 -0
  84. package/src/scenes.ts +1 -0
  85. package/src/session.ts +204 -0
  86. package/src/telegraf.ts +354 -0
  87. package/src/telegram-types.ts +219 -0
  88. package/src/telegram.ts +1635 -0
  89. package/src/types.ts +2 -0
  90. package/src/utils.ts +1 -0
  91. package/types.d.ts +1 -0
  92. package/types.js +1 -0
  93. package/typings/button.d.ts +36 -0
  94. package/typings/button.d.ts.map +1 -0
  95. package/typings/composer.d.ts +227 -0
  96. package/typings/composer.d.ts.map +1 -0
  97. package/typings/context.d.ts +655 -0
  98. package/typings/context.d.ts.map +1 -0
  99. package/typings/core/helpers/args.d.ts +11 -0
  100. package/typings/core/helpers/args.d.ts.map +1 -0
  101. package/typings/core/helpers/check.d.ts +56 -0
  102. package/typings/core/helpers/check.d.ts.map +1 -0
  103. package/typings/core/helpers/compact.d.ts +4 -0
  104. package/typings/core/helpers/compact.d.ts.map +1 -0
  105. package/typings/core/helpers/deunionize.d.ts +18 -0
  106. package/typings/core/helpers/deunionize.d.ts.map +1 -0
  107. package/typings/core/helpers/formatting.d.ts +30 -0
  108. package/typings/core/helpers/formatting.d.ts.map +1 -0
  109. package/typings/core/helpers/util.d.ts +26 -0
  110. package/typings/core/helpers/util.d.ts.map +1 -0
  111. package/typings/core/network/client.d.ts +53 -0
  112. package/typings/core/network/client.d.ts.map +1 -0
  113. package/typings/core/network/error.d.ts +16 -0
  114. package/typings/core/network/error.d.ts.map +1 -0
  115. package/typings/core/network/multipart-stream.d.ts +16 -0
  116. package/typings/core/network/multipart-stream.d.ts.map +1 -0
  117. package/typings/core/network/polling.d.ts +16 -0
  118. package/typings/core/network/polling.d.ts.map +1 -0
  119. package/typings/core/network/webhook.d.ts +6 -0
  120. package/typings/core/network/webhook.d.ts.map +1 -0
  121. package/typings/core/types/typegram.d.ts +42 -0
  122. package/typings/core/types/typegram.d.ts.map +1 -0
  123. package/typings/filters.d.ts +18 -0
  124. package/typings/filters.d.ts.map +1 -0
  125. package/typings/format.d.ts +22 -0
  126. package/typings/format.d.ts.map +1 -0
  127. package/typings/future.d.ts +12 -0
  128. package/typings/future.d.ts.map +1 -0
  129. package/typings/index.d.ts +15 -0
  130. package/typings/index.d.ts.map +1 -0
  131. package/typings/input.d.ts +50 -0
  132. package/typings/input.d.ts.map +1 -0
  133. package/typings/markup.d.ts +27 -0
  134. package/typings/markup.d.ts.map +1 -0
  135. package/typings/middleware.d.ts +8 -0
  136. package/typings/middleware.d.ts.map +1 -0
  137. package/typings/reactions.d.ts +32 -0
  138. package/typings/reactions.d.ts.map +1 -0
  139. package/typings/router.d.ts +21 -0
  140. package/typings/router.d.ts.map +1 -0
  141. package/typings/scenes/base.d.ts +22 -0
  142. package/typings/scenes/base.d.ts.map +1 -0
  143. package/typings/scenes/context.d.ts +36 -0
  144. package/typings/scenes/context.d.ts.map +1 -0
  145. package/typings/scenes/index.d.ts +11 -0
  146. package/typings/scenes/index.d.ts.map +1 -0
  147. package/typings/scenes/stage.d.ts +24 -0
  148. package/typings/scenes/stage.d.ts.map +1 -0
  149. package/typings/scenes/wizard/context.d.ts +29 -0
  150. package/typings/scenes/wizard/context.d.ts.map +1 -0
  151. package/typings/scenes/wizard/index.d.ts +16 -0
  152. package/typings/scenes/wizard/index.d.ts.map +1 -0
  153. package/typings/scenes.d.ts +2 -0
  154. package/typings/scenes.d.ts.map +1 -0
  155. package/typings/session.d.ts +55 -0
  156. package/typings/session.d.ts.map +1 -0
  157. package/typings/telegraf.d.ts +115 -0
  158. package/typings/telegraf.d.ts.map +1 -0
  159. package/typings/telegram-types.d.ts +117 -0
  160. package/typings/telegram-types.d.ts.map +1 -0
  161. package/typings/telegram.d.ts +675 -0
  162. package/typings/telegram.d.ts.map +1 -0
  163. package/typings/types.d.ts +3 -0
  164. package/typings/types.d.ts.map +1 -0
  165. package/typings/utils.d.ts +2 -0
  166. package/typings/utils.d.ts.map +1 -0
  167. package/utils.d.ts +1 -0
  168. package/utils.js +1 -0
@@ -0,0 +1,1008 @@
1
+ /** @format */
2
+
3
+ import * as tg from './core/types/typegram'
4
+ import * as tt from './telegram-types'
5
+ import { Middleware, MiddlewareFn, MiddlewareObj } from './middleware'
6
+ import Context, { FilteredContext, NarrowedContext } from './context'
7
+ import {
8
+ MaybeArray,
9
+ NonemptyReadonlyArray,
10
+ MaybePromise,
11
+ Guard,
12
+ } from './core/helpers/util'
13
+ import { type CallbackQuery } from './core/types/typegram'
14
+ import { message, callbackQuery } from './filters'
15
+ import { argsParser } from './core/helpers/args'
16
+ import { Digit, Reaction } from './reactions'
17
+
18
+ type ReactionAddedOrRemoved =
19
+ | Reaction
20
+ | `-${tg.TelegramEmoji}`
21
+ | `-${Digit}${string}`
22
+
23
+ export type Triggers<C> = MaybeArray<string | RegExp | TriggerFn<C>>
24
+ type TriggerFn<C> = (value: string, ctx: C) => RegExpExecArray | null
25
+
26
+ export type Predicate<T> = (t: T) => boolean
27
+ export type AsyncPredicate<T> = (t: T) => Promise<boolean>
28
+
29
+ export type MatchedMiddleware<
30
+ C extends Context,
31
+ T extends tt.UpdateType | tt.MessageSubType = 'message' | 'channel_post',
32
+ > = NonemptyReadonlyArray<
33
+ Middleware<
34
+ NarrowedContext<C, tt.MountMap[T]> & { match: RegExpExecArray },
35
+ tt.MountMap[T]
36
+ >
37
+ >
38
+
39
+ /** Takes: a context type and an update type (or message subtype).
40
+ Produces: a context that has some properties required, and some undefined.
41
+ The required ones are those that are always present when the given update (or message) arrives.
42
+ The undefined ones are those that are always absent when the given update (or message) arrives. */
43
+ /** @deprecated */
44
+ type MatchedContext<
45
+ C extends Context,
46
+ T extends tt.UpdateType | tt.MessageSubType,
47
+ > = NarrowedContext<C, tt.MountMap[T]>
48
+
49
+ function always<T>(x: T) {
50
+ return () => x
51
+ }
52
+
53
+ interface StartContextExtn extends tt.CommandContextExtn {
54
+ /**
55
+ * @deprecated Use ctx.payload instead
56
+ */
57
+ startPayload: string
58
+ }
59
+
60
+ const anoop = always(Promise.resolve())
61
+
62
+ export class Composer<C extends Context> implements MiddlewareObj<C> {
63
+ private handler: MiddlewareFn<C>
64
+
65
+ constructor(...fns: ReadonlyArray<Middleware<C>>) {
66
+ this.handler = Composer.compose(fns)
67
+ }
68
+
69
+ /**
70
+ * Registers a middleware.
71
+ */
72
+ use(...fns: ReadonlyArray<Middleware<C>>) {
73
+ this.handler = Composer.compose([this.handler, ...fns])
74
+ return this
75
+ }
76
+
77
+ /**
78
+ * Registers middleware for handling updates
79
+ * matching given type guard function.
80
+ * @deprecated use `Composer::on`
81
+ */
82
+ guard<U extends tg.Update>(
83
+ guardFn: (update: tg.Update) => update is U,
84
+ ...fns: NonemptyReadonlyArray<Middleware<NarrowedContext<C, U>, U>>
85
+ ) {
86
+ return this.use(Composer.guard<C, U>(guardFn, ...fns))
87
+ }
88
+
89
+ /**
90
+ * Registers middleware for handling updates narrowed by update types or filter queries.
91
+ */
92
+ on<Filter extends tt.UpdateType | Guard<C['update']>>(
93
+ filters: MaybeArray<Filter>,
94
+ ...fns: NonemptyReadonlyArray<Middleware<FilteredContext<C, Filter>>>
95
+ ): this
96
+
97
+ /**
98
+ * Registers middleware for handling updates narrowed by update types or message subtypes.
99
+ * @deprecated Use filter utils instead. Support for Message subtype in `Composer::on` will be removed in Telegraf v5.
100
+ */
101
+ on<Filter extends tt.UpdateType | tt.MessageSubType>(
102
+ filters: MaybeArray<Filter>,
103
+ ...fns: NonemptyReadonlyArray<
104
+ Middleware<NarrowedContext<C, tt.MountMap[Filter]>, tt.MountMap[Filter]>
105
+ >
106
+ ): this
107
+
108
+ on<Filter extends tt.UpdateType | tt.MessageSubType | Guard<C['update']>>(
109
+ filters: MaybeArray<Filter>,
110
+ ...fns: NonemptyReadonlyArray<
111
+ Middleware<
112
+ Filter extends tt.MessageSubType
113
+ ? MatchedContext<C, Filter>
114
+ : Filter extends tt.UpdateType | Guard<C['update']>
115
+ ? FilteredContext<C, Filter>
116
+ : never
117
+ >
118
+ >
119
+ ): this {
120
+ // @ts-expect-error This should get resolved when the overloads are removed in v5
121
+ return this.use(Composer.on(filters, ...fns))
122
+ }
123
+
124
+ /**
125
+ * Registers middleware for handling matching text messages.
126
+ */
127
+ hears(
128
+ triggers: Triggers<NarrowedContext<C, tt.MountMap['text']>>,
129
+ ...fns: MatchedMiddleware<C, 'text'>
130
+ ) {
131
+ return this.use(Composer.hears<C>(triggers, ...fns))
132
+ }
133
+
134
+ /**
135
+ * Registers middleware for handling specified commands.
136
+ */
137
+ command(
138
+ command: Triggers<NarrowedContext<C, tt.MountMap['text']>>,
139
+ ...fns: NonemptyReadonlyArray<
140
+ Middleware<
141
+ NarrowedContext<C, tt.MountMap['text']> & tt.CommandContextExtn
142
+ >
143
+ >
144
+ ) {
145
+ return this.use(Composer.command<C>(command, ...fns))
146
+ }
147
+
148
+ /**
149
+ * Registers middleware for handling matching callback queries.
150
+ */
151
+ action(
152
+ triggers: Triggers<NarrowedContext<C, tt.MountMap['callback_query']>>,
153
+ ...fns: MatchedMiddleware<C, 'callback_query'>
154
+ ) {
155
+ return this.use(Composer.action<C>(triggers, ...fns))
156
+ }
157
+
158
+ /**
159
+ * Registers middleware for handling matching inline queries.
160
+ */
161
+ inlineQuery(
162
+ triggers: Triggers<NarrowedContext<C, tt.MountMap['inline_query']>>,
163
+ ...fns: MatchedMiddleware<C, 'inline_query'>
164
+ ) {
165
+ return this.use(Composer.inlineQuery<C>(triggers, ...fns))
166
+ }
167
+
168
+ /**
169
+ * Registers middleware for handling game queries
170
+ */
171
+ gameQuery(
172
+ ...fns: NonemptyReadonlyArray<
173
+ Middleware<
174
+ NarrowedContext<
175
+ C,
176
+ tg.Update.CallbackQueryUpdate<CallbackQuery.GameQuery>
177
+ >
178
+ >
179
+ >
180
+ ) {
181
+ return this.use(Composer.gameQuery(...fns))
182
+ }
183
+
184
+ reaction(
185
+ reaction: MaybeArray<ReactionAddedOrRemoved>,
186
+ ...fns: NonemptyReadonlyArray<
187
+ Middleware<
188
+ NarrowedContext<C, tg.Update.MessageReactionUpdate> & {
189
+ match: ReactionAddedOrRemoved
190
+ },
191
+ tg.Update.MessageReactionUpdate
192
+ >
193
+ >
194
+ ) {
195
+ return this.use(Composer.reaction<C>(reaction, ...fns))
196
+ }
197
+
198
+ /**
199
+ * Registers middleware for dropping matching updates.
200
+ */
201
+ drop(predicate: Predicate<C>) {
202
+ return this.use(Composer.drop(predicate))
203
+ }
204
+
205
+ /** @deprecated use `Composer::drop` */
206
+ filter(predicate: Predicate<C>) {
207
+ return this.use(Composer.filter(predicate))
208
+ }
209
+
210
+ private entity<
211
+ T extends 'message' | 'channel_post' | tt.MessageSubType =
212
+ | 'message'
213
+ | 'channel_post',
214
+ >(
215
+ predicate:
216
+ | MaybeArray<string>
217
+ | ((entity: tg.MessageEntity, s: string, ctx: C) => boolean),
218
+ ...fns: ReadonlyArray<
219
+ Middleware<NarrowedContext<C, tt.MountMap[T]>, tt.MountMap[T]>
220
+ >
221
+ ) {
222
+ return this.use(Composer.entity<C, T>(predicate, ...fns))
223
+ }
224
+
225
+ email(email: Triggers<C>, ...fns: MatchedMiddleware<C>) {
226
+ return this.use(Composer.email<C>(email, ...fns))
227
+ }
228
+
229
+ url(url: Triggers<C>, ...fns: MatchedMiddleware<C>) {
230
+ return this.use(Composer.url<C>(url, ...fns))
231
+ }
232
+
233
+ textLink(link: Triggers<C>, ...fns: MatchedMiddleware<C>) {
234
+ return this.use(Composer.textLink<C>(link, ...fns))
235
+ }
236
+
237
+ textMention(mention: Triggers<C>, ...fns: MatchedMiddleware<C>) {
238
+ return this.use(Composer.textMention<C>(mention, ...fns))
239
+ }
240
+
241
+ mention(mention: MaybeArray<string>, ...fns: MatchedMiddleware<C>) {
242
+ return this.use(Composer.mention<C>(mention, ...fns))
243
+ }
244
+
245
+ phone(number: Triggers<C>, ...fns: MatchedMiddleware<C>) {
246
+ return this.use(Composer.phone<C>(number, ...fns))
247
+ }
248
+
249
+ hashtag(hashtag: MaybeArray<string>, ...fns: MatchedMiddleware<C>) {
250
+ return this.use(Composer.hashtag<C>(hashtag, ...fns))
251
+ }
252
+
253
+ cashtag(cashtag: MaybeArray<string>, ...fns: MatchedMiddleware<C>) {
254
+ return this.use(Composer.cashtag<C>(cashtag, ...fns))
255
+ }
256
+
257
+ spoiler(text: Triggers<C>, ...fns: MatchedMiddleware<C>) {
258
+ return this.use(Composer.spoiler<C>(text, ...fns))
259
+ }
260
+
261
+ /**
262
+ * Registers a middleware for handling /start
263
+ */
264
+ start(
265
+ ...fns: NonemptyReadonlyArray<
266
+ Middleware<NarrowedContext<C, tt.MountMap['text']> & StartContextExtn>
267
+ >
268
+ ) {
269
+ const handler = Composer.compose(fns)
270
+ return this.command('start', (ctx, next) =>
271
+ handler(Object.assign(ctx, { startPayload: ctx.payload }), next)
272
+ )
273
+ }
274
+
275
+ /**
276
+ * Registers a middleware for handling /help
277
+ */
278
+ help(
279
+ ...fns: NonemptyReadonlyArray<
280
+ Middleware<
281
+ NarrowedContext<C, tt.MountMap['text']> & tt.CommandContextExtn
282
+ >
283
+ >
284
+ ) {
285
+ return this.command('help', ...fns)
286
+ }
287
+
288
+ /**
289
+ * Registers a middleware for handling /settings
290
+ */
291
+ settings(
292
+ ...fns: NonemptyReadonlyArray<
293
+ Middleware<
294
+ NarrowedContext<C, tt.MountMap['text']> & tt.CommandContextExtn
295
+ >
296
+ >
297
+ ) {
298
+ return this.command('settings', ...fns)
299
+ }
300
+
301
+ middleware() {
302
+ return this.handler
303
+ }
304
+
305
+ static reply(...args: Parameters<Context['reply']>): MiddlewareFn<Context> {
306
+ return (ctx) => ctx.reply(...args)
307
+ }
308
+
309
+ static catch<C extends Context>(
310
+ errorHandler: (err: unknown, ctx: C) => void,
311
+ ...fns: ReadonlyArray<Middleware<C>>
312
+ ): MiddlewareFn<C> {
313
+ const handler = Composer.compose(fns)
314
+ // prettier-ignore
315
+ return (ctx, next) => Promise.resolve(handler(ctx, next))
316
+ .catch((err) => errorHandler(err, ctx))
317
+ }
318
+
319
+ /**
320
+ * Generates middleware that runs in the background.
321
+ */
322
+ static fork<C extends Context>(middleware: Middleware<C>): MiddlewareFn<C> {
323
+ const handler = Composer.unwrap(middleware)
324
+ return async (ctx, next) => {
325
+ await Promise.all([handler(ctx, anoop), next()])
326
+ }
327
+ }
328
+
329
+ static tap<C extends Context>(middleware: Middleware<C>): MiddlewareFn<C> {
330
+ const handler = Composer.unwrap(middleware)
331
+ return (ctx, next) =>
332
+ Promise.resolve(handler(ctx, anoop)).then(() => next())
333
+ }
334
+
335
+ /**
336
+ * Generates middleware that gives up control to the next middleware.
337
+ */
338
+ static passThru(): MiddlewareFn<Context> {
339
+ return (ctx, next) => next()
340
+ }
341
+
342
+ static lazy<C extends Context>(
343
+ factoryFn: (ctx: C) => MaybePromise<Middleware<C>>
344
+ ): MiddlewareFn<C> {
345
+ if (typeof factoryFn !== 'function') {
346
+ throw new Error('Argument must be a function')
347
+ }
348
+ return (ctx, next) =>
349
+ Promise.resolve(factoryFn(ctx)).then((middleware) =>
350
+ Composer.unwrap(middleware)(ctx, next)
351
+ )
352
+ }
353
+
354
+ static log(logFn: (s: string) => void = console.log): MiddlewareFn<Context> {
355
+ return (ctx, next) => {
356
+ logFn(JSON.stringify(ctx.update, null, 2))
357
+ return next()
358
+ }
359
+ }
360
+
361
+ /**
362
+ * @param trueMiddleware middleware to run if the predicate returns true
363
+ * @param falseMiddleware middleware to run if the predicate returns false
364
+ */
365
+ static branch<C extends Context>(
366
+ predicate: boolean | Predicate<C> | AsyncPredicate<C>,
367
+ trueMiddleware: Middleware<C>,
368
+ falseMiddleware: Middleware<C>
369
+ ): MiddlewareFn<C> {
370
+ if (typeof predicate !== 'function') {
371
+ return Composer.unwrap(predicate ? trueMiddleware : falseMiddleware)
372
+ }
373
+ return Composer.lazy<C>((ctx) =>
374
+ Promise.resolve(predicate(ctx)).then((value) =>
375
+ value ? trueMiddleware : falseMiddleware
376
+ )
377
+ )
378
+ }
379
+
380
+ /**
381
+ * Generates optional middleware.
382
+ * @param predicate predicate to decide on a context object whether to run the middleware
383
+ * @param fns middleware to run if the predicate returns true
384
+ */
385
+ static optional<C extends Context>(
386
+ predicate: Predicate<C> | AsyncPredicate<C>,
387
+ ...fns: NonemptyReadonlyArray<Middleware<C>>
388
+ ): MiddlewareFn<C> {
389
+ return Composer.branch(
390
+ predicate,
391
+ Composer.compose(fns),
392
+ Composer.passThru()
393
+ )
394
+ }
395
+
396
+ /** @deprecated use `Composer.drop` */
397
+ static filter<C extends Context>(predicate: Predicate<C>): MiddlewareFn<C> {
398
+ return Composer.branch(predicate, Composer.passThru(), anoop)
399
+ }
400
+
401
+ /**
402
+ * Generates middleware for dropping matching updates.
403
+ */
404
+ static drop<C extends Context>(predicate: Predicate<C>): MiddlewareFn<C> {
405
+ return Composer.branch(predicate, anoop, Composer.passThru())
406
+ }
407
+
408
+ static dispatch<
409
+ C extends Context,
410
+ Handlers extends Record<string | number | symbol, Middleware<C>>,
411
+ >(
412
+ routeFn: (ctx: C) => MaybePromise<keyof Handlers>,
413
+ handlers: Handlers
414
+ ): Middleware<C> {
415
+ return Composer.lazy<C>((ctx) =>
416
+ Promise.resolve(routeFn(ctx)).then((value) => handlers[value])
417
+ )
418
+ }
419
+
420
+ // EXPLANATION FOR THE ts-expect-error ANNOTATIONS
421
+
422
+ // The annotations around function invocations with `...fns` are there
423
+ // whenever we perform validation logic that the flow analysis of TypeScript
424
+ // cannot comprehend. We always make sure that the middleware functions are
425
+ // only invoked with properly constrained context objects, but this cannot be
426
+ // determined automatically.
427
+
428
+ /**
429
+ * Generates optional middleware based on a predicate that only operates on `ctx.update`.
430
+ *
431
+ * Example:
432
+ * ```ts
433
+ * import { Composer, Update } from 'telegraf'
434
+ *
435
+ * const predicate = (u): u is Update.MessageUpdate => 'message' in u
436
+ * const middleware = Composer.guard(predicate, (ctx) => {
437
+ * const message = ctx.update.message
438
+ * })
439
+ * ```
440
+ *
441
+ * Note that `Composer.on('message')` is preferred over this.
442
+ *
443
+ * @param guardFn predicate to decide whether to run the middleware based on the `ctx.update` object
444
+ * @param fns middleware to run if the predicate returns true
445
+ * @see `Composer.optional` for a more generic version of this method that allows the predicate to operate on `ctx` itself
446
+ * @deprecated use `Composer.on`
447
+ */
448
+ static guard<C extends Context, U extends tg.Update>(
449
+ guardFn: (u: tg.Update) => u is U,
450
+ ...fns: NonemptyReadonlyArray<Middleware<NarrowedContext<C, U>, U>>
451
+ ): MiddlewareFn<C> {
452
+ return Composer.optional<C>(
453
+ (ctx) => guardFn(ctx.update),
454
+ // @ts-expect-error see explanation above
455
+ ...fns
456
+ )
457
+ }
458
+
459
+ /**
460
+ * Generates middleware for handling updates narrowed by update types or filter queries.
461
+ */
462
+ static on<
463
+ Ctx extends Context,
464
+ Filter extends tt.UpdateType | Guard<Ctx['update']>,
465
+ >(
466
+ filters: MaybeArray<Filter>,
467
+ ...fns: NonemptyReadonlyArray<Middleware<FilteredContext<Ctx, Filter>>>
468
+ ): MiddlewareFn<Ctx>
469
+
470
+ /**
471
+ * Generates middleware for handling updates narrowed by update types or message subtype.
472
+ * @deprecated Use filter utils instead. Support for Message subtype in `Composer.on` will be removed in Telegraf v5.
473
+ */
474
+ static on<
475
+ Ctx extends Context,
476
+ Filter extends tt.UpdateType | tt.MessageSubType,
477
+ >(
478
+ filters: MaybeArray<Filter>,
479
+ ...fns: NonemptyReadonlyArray<
480
+ Middleware<NarrowedContext<Ctx, tt.MountMap[Filter]>, tt.MountMap[Filter]>
481
+ >
482
+ ): MiddlewareFn<Ctx>
483
+
484
+ static on<
485
+ Ctx extends Context,
486
+ Filter extends tt.UpdateType | tt.MessageSubType | Guard<Ctx['update']>,
487
+ >(
488
+ updateType: MaybeArray<Filter>,
489
+ ...fns: NonemptyReadonlyArray<Middleware<Ctx>>
490
+ ): MiddlewareFn<Ctx> {
491
+ const filters = Array.isArray(updateType) ? updateType : [updateType]
492
+
493
+ const predicate = (update: tg.Update): update is tg.Update => {
494
+ for (const filter of filters) {
495
+ if (
496
+ // TODO: this should change to === 'function' once TS bug is fixed
497
+ // https://github.com/microsoft/TypeScript/pull/51502
498
+ typeof filter !== 'string'
499
+ ? // filter is a type guard
500
+ filter(update)
501
+ : // check if filter is the update type
502
+ filter in update ||
503
+ // check if filter is the msg type
504
+ // TODO: remove in v5!
505
+ ('message' in update && filter in update.message)
506
+ ) {
507
+ return true
508
+ }
509
+ }
510
+
511
+ return false
512
+ }
513
+
514
+ return Composer.optional((ctx) => predicate(ctx.update), ...fns)
515
+ }
516
+
517
+ /**
518
+ * Generates middleware for handling provided update types.
519
+ * @deprecated use `Composer.on` instead
520
+ */
521
+ static mount = Composer.on
522
+
523
+ private static entity<
524
+ C extends Context,
525
+ T extends 'message' | 'channel_post' | tt.MessageSubType =
526
+ | 'message'
527
+ | 'channel_post',
528
+ >(
529
+ predicate:
530
+ | MaybeArray<string>
531
+ | ((entity: tg.MessageEntity, s: string, ctx: C) => boolean),
532
+ ...fns: ReadonlyArray<
533
+ Middleware<NarrowedContext<C, tt.MountMap[T]>, tt.MountMap[T]>
534
+ >
535
+ ): MiddlewareFn<C> {
536
+ if (typeof predicate !== 'function') {
537
+ const entityTypes = normaliseTextArguments(predicate)
538
+ return Composer.entity(({ type }) => entityTypes.includes(type), ...fns)
539
+ }
540
+ return Composer.optional<C>(
541
+ (ctx) => {
542
+ const msg: tg.Message | undefined = ctx.message ?? ctx.channelPost
543
+ if (msg === undefined) {
544
+ return false
545
+ }
546
+ const text = getText(msg)
547
+ const entities = getEntities(msg)
548
+ if (text === undefined) return false
549
+ return entities.some((entity) =>
550
+ predicate(
551
+ entity,
552
+ text.substring(entity.offset, entity.offset + entity.length),
553
+ ctx
554
+ )
555
+ )
556
+ },
557
+ // @ts-expect-error see explanation above
558
+ ...fns
559
+ )
560
+ }
561
+
562
+ static entityText<C extends Context>(
563
+ entityType: MaybeArray<string>,
564
+ predicate: Triggers<C>,
565
+ ...fns: MatchedMiddleware<C>
566
+ ): MiddlewareFn<C> {
567
+ if (fns.length === 0) {
568
+ // prettier-ignore
569
+ return Array.isArray(predicate)
570
+ // @ts-expect-error predicate is really the middleware
571
+ ? Composer.entity(entityType, ...predicate)
572
+ // @ts-expect-error predicate is really the middleware
573
+ : Composer.entity(entityType, predicate)
574
+ }
575
+ const triggers = normaliseTriggers(predicate)
576
+ return Composer.entity<C>(
577
+ ({ type }, value, ctx) => {
578
+ if (type !== entityType) {
579
+ return false
580
+ }
581
+ for (const trigger of triggers) {
582
+ // @ts-expect-error define so far unknown property `match`
583
+ if ((ctx.match = trigger(value, ctx))) {
584
+ return true
585
+ }
586
+ }
587
+ return false
588
+ },
589
+ // @ts-expect-error see explanation above
590
+ ...fns
591
+ )
592
+ }
593
+
594
+ static email<C extends Context>(
595
+ email: Triggers<C>,
596
+ ...fns: MatchedMiddleware<C>
597
+ ): MiddlewareFn<C> {
598
+ return Composer.entityText<C>('email', email, ...fns)
599
+ }
600
+
601
+ static phone<C extends Context>(
602
+ number: Triggers<C>,
603
+ ...fns: MatchedMiddleware<C>
604
+ ): MiddlewareFn<C> {
605
+ return Composer.entityText<C>('phone_number', number, ...fns)
606
+ }
607
+
608
+ static url<C extends Context>(
609
+ url: Triggers<C>,
610
+ ...fns: MatchedMiddleware<C>
611
+ ): MiddlewareFn<C> {
612
+ return Composer.entityText<C>('url', url, ...fns)
613
+ }
614
+
615
+ static textLink<C extends Context>(
616
+ link: Triggers<C>,
617
+ ...fns: MatchedMiddleware<C>
618
+ ): MiddlewareFn<C> {
619
+ return Composer.entityText<C>('text_link', link, ...fns)
620
+ }
621
+
622
+ static textMention<C extends Context>(
623
+ mention: Triggers<C>,
624
+ ...fns: MatchedMiddleware<C>
625
+ ): MiddlewareFn<C> {
626
+ return Composer.entityText<C>('text_mention', mention, ...fns)
627
+ }
628
+
629
+ static mention<C extends Context>(
630
+ mention: MaybeArray<string>,
631
+ ...fns: MatchedMiddleware<C>
632
+ ): MiddlewareFn<C> {
633
+ return Composer.entityText<C>(
634
+ 'mention',
635
+ normaliseTextArguments(mention, '@'),
636
+ ...fns
637
+ )
638
+ }
639
+
640
+ static hashtag<C extends Context>(
641
+ hashtag: MaybeArray<string>,
642
+ ...fns: MatchedMiddleware<C>
643
+ ): MiddlewareFn<C> {
644
+ return Composer.entityText<C>(
645
+ 'hashtag',
646
+ normaliseTextArguments(hashtag, '#'),
647
+ ...fns
648
+ )
649
+ }
650
+
651
+ static cashtag<C extends Context>(
652
+ cashtag: MaybeArray<string>,
653
+ ...fns: MatchedMiddleware<C>
654
+ ): MiddlewareFn<C> {
655
+ return Composer.entityText<C>(
656
+ 'cashtag',
657
+ normaliseTextArguments(cashtag, '$'),
658
+ ...fns
659
+ )
660
+ }
661
+
662
+ static spoiler<C extends Context>(
663
+ text: Triggers<C>,
664
+ ...fns: MatchedMiddleware<C>
665
+ ): MiddlewareFn<C> {
666
+ return Composer.entityText<C>('spoiler', text, ...fns)
667
+ }
668
+
669
+ private static match<C extends Context>(
670
+ triggers: ReadonlyArray<TriggerFn<C>>,
671
+ ...fns: Middleware<C & { match: RegExpExecArray }>[]
672
+ ): MiddlewareFn<C> {
673
+ const handler = Composer.compose(fns)
674
+ return (ctx, next) => {
675
+ const text =
676
+ getText(ctx.message) ??
677
+ getText(ctx.channelPost) ??
678
+ getText(ctx.callbackQuery) ??
679
+ ctx.inlineQuery?.query
680
+ if (text === undefined) return next()
681
+ for (const trigger of triggers) {
682
+ const match = trigger(text, ctx)
683
+ if (match) return handler(Object.assign(ctx, { match }), next)
684
+ }
685
+ return next()
686
+ }
687
+ }
688
+
689
+ /**
690
+ * Generates middleware for handling matching text messages.
691
+ */
692
+ static hears<C extends Context>(
693
+ triggers: Triggers<NarrowedContext<C, tt.MountMap['text']>>,
694
+ ...fns: MatchedMiddleware<C, 'text'>
695
+ ): MiddlewareFn<C> {
696
+ return Composer.on(
697
+ 'text',
698
+ Composer.match<NarrowedContext<C, tt.MountMap['text']>>(
699
+ normaliseTriggers(triggers),
700
+ ...fns
701
+ )
702
+ )
703
+ }
704
+
705
+ /**
706
+ * Generates middleware for handling specified commands.
707
+ */
708
+ static command<C extends Context>(
709
+ command: Triggers<NarrowedContext<C, tt.MountMap['text']>>,
710
+ ...fns: NonemptyReadonlyArray<
711
+ Middleware<
712
+ NarrowedContext<C, tt.MountMap['text']> & tt.CommandContextExtn
713
+ >
714
+ >
715
+ ): MiddlewareFn<C> {
716
+ if (fns.length === 0)
717
+ // @ts-expect-error command is really the middleware
718
+ return Composer.entity('bot_command', command)
719
+
720
+ const triggers = normaliseTriggers(command)
721
+ const filter = message('text')
722
+ const handler = Composer.compose(fns)
723
+
724
+ return Composer.on<C, typeof filter>(filter, (ctx, next) => {
725
+ const { entities } = ctx.message
726
+ const cmdEntity = entities?.[0]
727
+ if (cmdEntity?.type !== 'bot_command') return next()
728
+ if (cmdEntity.offset > 0) return next()
729
+ const len = cmdEntity.length
730
+ const text = ctx.message.text
731
+ const [cmdPart, to] = text.slice(0, len).split('@')
732
+ if (!cmdPart) return next()
733
+ // always check for bot's own username case-insensitively
734
+ if (to && to.toLowerCase() !== ctx.me.toLowerCase()) return next()
735
+ const command = cmdPart.slice(1)
736
+ for (const trigger of triggers) {
737
+ const match = trigger(command, ctx)
738
+ if (match) {
739
+ const payloadOffset = len + 1
740
+ const payload = text.slice(payloadOffset)
741
+ const c = Object.assign(ctx, { match, command, payload, args: [] })
742
+ let _args: string[] | undefined = undefined
743
+ // using defineProperty only to make parsing lazy on access
744
+ Object.defineProperty(c, 'args', {
745
+ enumerable: true,
746
+ configurable: true,
747
+ get() {
748
+ if (_args != null) return _args
749
+ // once parsed, cache and don't parse again on every access
750
+ return (_args = argsParser(payload, entities, payloadOffset))
751
+ },
752
+ set(args: string[]) {
753
+ _args = args
754
+ },
755
+ })
756
+ return handler(c, next)
757
+ }
758
+ }
759
+ return next()
760
+ })
761
+ }
762
+
763
+ /**
764
+ * Generates middleware for handling matching callback queries.
765
+ */
766
+ static action<C extends Context>(
767
+ triggers: Triggers<NarrowedContext<C, tt.MountMap['callback_query']>>,
768
+ ...fns: MatchedMiddleware<C, 'callback_query'>
769
+ ): MiddlewareFn<C> {
770
+ return Composer.on(
771
+ 'callback_query',
772
+ Composer.match(normaliseTriggers(triggers), ...fns)
773
+ )
774
+ }
775
+
776
+ /**
777
+ * Generates middleware for handling matching inline queries.
778
+ */
779
+ static inlineQuery<C extends Context>(
780
+ triggers: Triggers<NarrowedContext<C, tt.MountMap['inline_query']>>,
781
+ ...fns: MatchedMiddleware<C, 'inline_query'>
782
+ ): MiddlewareFn<C> {
783
+ return Composer.on(
784
+ 'inline_query',
785
+ Composer.match(normaliseTriggers(triggers), ...fns)
786
+ )
787
+ }
788
+
789
+ static reaction<C extends Context>(
790
+ reaction: MaybeArray<ReactionAddedOrRemoved>,
791
+ ...fns: NonemptyReadonlyArray<
792
+ Middleware<
793
+ NarrowedContext<C, tg.Update.MessageReactionUpdate> & {
794
+ match: ReactionAddedOrRemoved
795
+ },
796
+ tg.Update.MessageReactionUpdate
797
+ >
798
+ >
799
+ ): MiddlewareFn<C> {
800
+ const reactions = Array.isArray(reaction) ? reaction : [reaction]
801
+ const handler = Composer.compose(fns)
802
+
803
+ return Composer.on<C, 'message_reaction'>(
804
+ 'message_reaction',
805
+ (ctx, next) => {
806
+ const match = reactions.find((r) =>
807
+ typeof r === 'string' && r.startsWith('-')
808
+ ? ctx.reactions.removed.has(r.slice(1) as Reaction)
809
+ : ctx.reactions.added.has(r as Reaction)
810
+ )
811
+
812
+ if (match) return handler(Object.assign(ctx, { match }), next)
813
+ return next()
814
+ }
815
+ )
816
+ }
817
+
818
+ /**
819
+ * Generates middleware responding only to specified users.
820
+ */
821
+ static acl<C extends Context>(
822
+ userId: MaybeArray<number>,
823
+ ...fns: NonemptyReadonlyArray<Middleware<C>>
824
+ ): MiddlewareFn<C> {
825
+ if (typeof userId === 'function') {
826
+ return Composer.optional(userId, ...fns)
827
+ }
828
+ const allowed = Array.isArray(userId) ? userId : [userId]
829
+ // prettier-ignore
830
+ return Composer.optional((ctx) => !ctx.from || allowed.includes(ctx.from.id), ...fns)
831
+ }
832
+
833
+ private static memberStatus<C extends Context>(
834
+ status: MaybeArray<tg.ChatMember['status']>,
835
+ ...fns: NonemptyReadonlyArray<Middleware<C>>
836
+ ): MiddlewareFn<C> {
837
+ const statuses = Array.isArray(status) ? status : [status]
838
+ return Composer.optional(
839
+ async (ctx) => {
840
+ if (ctx.message === undefined) return false
841
+ const member = await ctx.getChatMember(ctx.message.from.id)
842
+ return statuses.includes(member.status)
843
+ },
844
+ ...fns
845
+ )
846
+ }
847
+
848
+ /**
849
+ * Generates middleware responding only to chat admins and chat creator.
850
+ */
851
+ static admin<C extends Context>(
852
+ ...fns: NonemptyReadonlyArray<Middleware<C>>
853
+ ): MiddlewareFn<C> {
854
+ return Composer.memberStatus(['administrator', 'creator'], ...fns)
855
+ }
856
+
857
+ /**
858
+ * Generates middleware responding only to chat creator.
859
+ */
860
+ static creator<C extends Context>(
861
+ ...fns: NonemptyReadonlyArray<Middleware<C>>
862
+ ): MiddlewareFn<C> {
863
+ return Composer.memberStatus('creator', ...fns)
864
+ }
865
+
866
+ /**
867
+ * Generates middleware running only in specified chat types.
868
+ */
869
+ static chatType<C extends Context>(
870
+ type: MaybeArray<tg.Chat['type']>,
871
+ ...fns: NonemptyReadonlyArray<Middleware<C>>
872
+ ): MiddlewareFn<C> {
873
+ const types = Array.isArray(type) ? type : [type]
874
+ return Composer.optional(
875
+ (ctx) => {
876
+ const chat = ctx.chat
877
+ return chat !== undefined && types.includes(chat.type)
878
+ },
879
+ ...fns
880
+ )
881
+ }
882
+
883
+ /**
884
+ * Generates middleware running only in private chats.
885
+ */
886
+ static privateChat<C extends Context>(
887
+ ...fns: NonemptyReadonlyArray<Middleware<C>>
888
+ ): MiddlewareFn<C> {
889
+ return Composer.chatType('private', ...fns)
890
+ }
891
+
892
+ /**
893
+ * Generates middleware running only in groups and supergroups.
894
+ */
895
+ static groupChat<C extends Context>(
896
+ ...fns: NonemptyReadonlyArray<Middleware<C>>
897
+ ): MiddlewareFn<C> {
898
+ return Composer.chatType(['group', 'supergroup'], ...fns)
899
+ }
900
+
901
+ /**
902
+ * Generates middleware for handling game queries.
903
+ */
904
+ static gameQuery<C extends Context>(
905
+ ...fns: NonemptyReadonlyArray<
906
+ Middleware<
907
+ NarrowedContext<
908
+ C,
909
+ tg.Update.CallbackQueryUpdate<CallbackQuery.GameQuery>
910
+ >
911
+ >
912
+ >
913
+ ): MiddlewareFn<C> {
914
+ return Composer.guard(callbackQuery('game_short_name'), ...fns)
915
+ }
916
+
917
+ static unwrap<C extends Context>(handler: Middleware<C>): MiddlewareFn<C> {
918
+ if (!handler) {
919
+ throw new Error('Handler is undefined')
920
+ }
921
+ return 'middleware' in handler ? handler.middleware() : handler
922
+ }
923
+
924
+ static compose<C extends Context>(
925
+ middlewares: ReadonlyArray<Middleware<C>>
926
+ ): MiddlewareFn<C> {
927
+ if (!Array.isArray(middlewares)) {
928
+ throw new Error('Middlewares must be an array')
929
+ }
930
+ if (middlewares.length === 0) {
931
+ return Composer.passThru()
932
+ }
933
+ if (middlewares.length === 1) {
934
+ // Quite literally asserted in the above condition
935
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
936
+ return Composer.unwrap(middlewares[0]!)
937
+ }
938
+ return (ctx, next) => {
939
+ let index = -1
940
+ return execute(0, ctx)
941
+ async function execute(i: number, context: C): Promise<void> {
942
+ if (!(context instanceof Context)) {
943
+ throw new Error('next(ctx) called with invalid context')
944
+ }
945
+ if (i <= index) {
946
+ throw new Error('next() called multiple times')
947
+ }
948
+ index = i
949
+ const handler = Composer.unwrap(middlewares[i] ?? next)
950
+ await handler(context, async (ctx = context) => {
951
+ await execute(i + 1, ctx)
952
+ })
953
+ }
954
+ }
955
+ }
956
+ }
957
+
958
+ function escapeRegExp(s: string) {
959
+ // $& means the whole matched string
960
+ return s.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&')
961
+ }
962
+
963
+ function normaliseTriggers<C extends Context>(
964
+ triggers: Triggers<C>
965
+ ): Array<TriggerFn<C>> {
966
+ if (!Array.isArray(triggers)) triggers = [triggers]
967
+
968
+ return triggers.map((trigger) => {
969
+ if (!trigger) throw new Error('Invalid trigger')
970
+ if (typeof trigger === 'function') return trigger
971
+
972
+ if (trigger instanceof RegExp)
973
+ return (value = '') => {
974
+ trigger.lastIndex = 0
975
+ return trigger.exec(value)
976
+ }
977
+
978
+ const regex = new RegExp(`^${escapeRegExp(trigger)}$`)
979
+ return (value: string) => regex.exec(value)
980
+ })
981
+ }
982
+
983
+ function getEntities(msg: tg.Message | undefined): tg.MessageEntity[] {
984
+ if (msg == null) return []
985
+ if ('caption_entities' in msg) return msg.caption_entities ?? []
986
+ if ('entities' in msg) return msg.entities ?? []
987
+ return []
988
+ }
989
+ function getText(
990
+ msg: tg.Message | tg.CallbackQuery | undefined
991
+ ): string | undefined {
992
+ if (msg == null) return undefined
993
+ if ('caption' in msg) return msg.caption
994
+ if ('text' in msg) return msg.text
995
+ if ('data' in msg) return msg.data
996
+ if ('game_short_name' in msg) return msg.game_short_name
997
+ return undefined
998
+ }
999
+
1000
+ function normaliseTextArguments(argument: MaybeArray<string>, prefix = '') {
1001
+ const args = Array.isArray(argument) ? argument : [argument]
1002
+ // prettier-ignore
1003
+ return args
1004
+ .filter(Boolean)
1005
+ .map((arg) => prefix && typeof arg === 'string' && !arg.startsWith(prefix) ? `${prefix}${arg}` : arg)
1006
+ }
1007
+
1008
+ export default Composer