@tgify/tgify 0.1.0 → 0.1.5
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.
- package/LICENSE +23 -23
- package/README.md +358 -356
- package/lib/cli.mjs +9 -9
- package/package.json +1 -1
- package/src/button.ts +182 -182
- package/src/composer.ts +1008 -1008
- package/src/context.ts +1661 -1661
- package/src/core/helpers/args.ts +63 -63
- package/src/core/helpers/check.ts +71 -71
- package/src/core/helpers/compact.ts +18 -18
- package/src/core/helpers/deunionize.ts +26 -26
- package/src/core/helpers/formatting.ts +119 -119
- package/src/core/helpers/util.ts +96 -96
- package/src/core/network/client.ts +396 -396
- package/src/core/network/error.ts +29 -29
- package/src/core/network/multipart-stream.ts +45 -45
- package/src/core/network/polling.ts +94 -94
- package/src/core/network/webhook.ts +58 -58
- package/src/core/types/typegram.ts +54 -54
- package/src/filters.ts +109 -109
- package/src/format.ts +110 -110
- package/src/future.ts +213 -213
- package/src/index.ts +17 -17
- package/src/input.ts +59 -59
- package/src/markup.ts +142 -142
- package/src/middleware.ts +24 -24
- package/src/reactions.ts +118 -118
- package/src/router.ts +55 -55
- package/src/scenes/base.ts +52 -52
- package/src/scenes/context.ts +136 -136
- package/src/scenes/index.ts +21 -21
- package/src/scenes/stage.ts +71 -71
- package/src/scenes/wizard/context.ts +58 -58
- package/src/scenes/wizard/index.ts +63 -63
- package/src/scenes.ts +1 -1
- package/src/session.ts +204 -204
- package/src/telegraf.ts +354 -354
- package/src/telegram-types.ts +219 -219
- package/src/telegram.ts +1635 -1635
- package/src/types.ts +2 -2
- package/src/utils.ts +1 -1
- package/typings/telegraf.d.ts.map +1 -1
package/src/composer.ts
CHANGED
|
@@ -1,1008 +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
|
|
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
|