@rilong/grammyjs-conversations-esm 2.0.2

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.
@@ -0,0 +1,885 @@
1
+ import { type CallbackQueryContext, type CommandContext, Context, type Filter, type FilterQuery, type GameQueryContext, type HearsContext, type MiddlewareFn, type ReactionContext, type ReactionType, type ReactionTypeEmoji, type Update, type User } from "./deps.node.js";
2
+ import { type Checkpoint, type ReplayControls } from "./engine.js";
3
+ import { ConversationForm } from "./form.js";
4
+ import { type ConversationMenuOptions } from "./menu.js";
5
+ type MaybeArray<T> = T | T[];
6
+ /** Alias for `string` but with auto-complete for common commands */
7
+ export type StringWithCommandSuggestions = (string & Record<never, never>) | "start" | "help" | "settings" | "privacy" | "developer_info";
8
+ /**
9
+ * Specifies an external operation and how to serialize and deserialize its
10
+ * return and error values.
11
+ *
12
+ * @typeParam OC Type of the outside context object
13
+ * @typeParam R Type of the return value
14
+ * @typeParam I Type of the intermediate (serialized) representation
15
+ */
16
+ export interface ExternalOp<OC extends Context, R, I = any> {
17
+ /**
18
+ * The external operation to perform.
19
+ *
20
+ * Receives the current context object from the surrounding middleware. This
21
+ * gives the task access to sessions (if used) and other values that are not
22
+ * present inside the conversation.
23
+ *
24
+ * @param ctx The outside context object of the surrounding middleware
25
+ */
26
+ task(ctx: OC): R | Promise<R>;
27
+ /**
28
+ * Converts a value returned from the task to an object that can safely be
29
+ * passed to `JSON.stringify`.
30
+ *
31
+ * @param value A value to serialize
32
+ */
33
+ beforeStore?(value: R): I | Promise<I>;
34
+ /**
35
+ * Restores the original value from the intermediate representation that
36
+ * `beforeStore` generated.
37
+ *
38
+ * @param value The value obtained from `JSON.parse`
39
+ */
40
+ afterLoad?(value: I): R | Promise<R>;
41
+ /**
42
+ * Converts an error thrown by the task to an object that can safely be
43
+ * passed to `JSON.stringify`.
44
+ *
45
+ * @param value A thrown error
46
+ */
47
+ beforeStoreError?(value: unknown): unknown | Promise<unknown>;
48
+ /**
49
+ * Restores the original error from the intermediate representation that
50
+ * `beforeStoreError` generated.
51
+ *
52
+ * @param value The value obtained from `JSON.parse`
53
+ */
54
+ afterLoadError?(value: unknown): unknown | Promise<unknown>;
55
+ }
56
+ /** A function that applies a context object to a callback */
57
+ type ApplyContext<OC extends Context> = <F extends (ctx: OC) => unknown>(fn: F) => Promise<ReturnType<F>>;
58
+ /** Options for creating a conversation handle */
59
+ export interface ConversationHandleOptions {
60
+ /** Callback for when the conversation is halted */
61
+ onHalt?(): void | Promise<void>;
62
+ /** Default wait timeout */
63
+ maxMillisecondsToWait?: number;
64
+ /**
65
+ * `true` if this conversation can be entered while this or another
66
+ * conversation is already active, and `false` otherwise. Defaults to
67
+ * `false`.
68
+ */
69
+ parallel?: boolean;
70
+ }
71
+ /**
72
+ * Options for a call to `conversation.wait()`.
73
+ */
74
+ export interface WaitOptions {
75
+ /**
76
+ * Specifies a timeout for the wait call.
77
+ *
78
+ * When the wait call is reached, `Date.now()` is called. When the wait call
79
+ * resolves, `Date.now()` is called again, and the two values are compared.
80
+ * If the wait call resolved more than the specified number of milliseconds
81
+ * after it was reached initially, then the conversation will be halted, any
82
+ * exit handlers will be called, and the surrounding middleware will resume
83
+ * normally so that subsequent handlers can run.
84
+ *
85
+ * To the outside middleware system, this will look like the conversation
86
+ * was never active.
87
+ */
88
+ maxMilliseconds?: number;
89
+ /**
90
+ * Collation key for the wait call, safety measure to protect against data
91
+ * corruption. This is used extensively by the plugin internally, but it is
92
+ * rarely useful to changes this behavior.
93
+ */
94
+ collationKey?: string;
95
+ }
96
+ /**
97
+ * Options for a call to `conversation.skip()`.
98
+ */
99
+ export interface SkipOptions {
100
+ /**
101
+ * Determines whether [the outside middleware
102
+ * system](https://grammy.dev/guide/middleware) should resume after the
103
+ * update is skipped.
104
+ *
105
+ * Pass `{ next: true }` to make sure that subsequent handlers will run.
106
+ * This effectively causes `next` to be called by the plugin.
107
+ *
108
+ * Defaults to `false` unless the conversation is marked as parallel, in
109
+ * which case this option defaults to `true`.
110
+ */
111
+ next?: boolean;
112
+ }
113
+ /**
114
+ * Options to pass to a chained `wait` call.
115
+ */
116
+ export interface AndOtherwiseOptions<C extends Context> extends SkipOptions {
117
+ /**
118
+ * Callback that will be invoked when the validation fails for a context
119
+ * object.
120
+ *
121
+ * @param ctx The context object that failed validation
122
+ */
123
+ otherwise?(ctx: C): unknown | Promise<unknown>;
124
+ }
125
+ /**
126
+ * Options for a filtered wait call. A filtered wait call is a wait call that
127
+ * have extra valiation attached, such as `waitFor`, `waitUntil`, etc.
128
+ */
129
+ export interface OtherwiseOptions<C extends Context> extends WaitOptions, AndOtherwiseOptions<C> {
130
+ }
131
+ /**
132
+ * Options for a call to `conversation.halt()`.
133
+ */
134
+ export interface HaltOptions {
135
+ /**
136
+ * Determines whether [the outside middleware
137
+ * system](https://grammy.dev/guide/middleware) should resume after the
138
+ * conversation is halted.
139
+ *
140
+ * Pass `{ next: true }` to make sure that subsequent handlers will run.
141
+ * This effectively causes `next` to be called by the plugin.
142
+ *
143
+ * Defaults to `false`.
144
+ */
145
+ next?: boolean;
146
+ }
147
+ /**
148
+ * A conversation handle lets you control the conversation, such as waiting for
149
+ * updates, skipping them, halting the conversation, and much more. It is the
150
+ * first parameter in each conversation builder function and provides the core
151
+ * features of this plugin.
152
+ *
153
+ * ```ts
154
+ * async function exmaple(conversation, ctx) {
155
+ * // ^ this is an instance of this class
156
+ *
157
+ * // This is how you can wait for updates:
158
+ * ctx = await conversation.wait()
159
+ * }
160
+ * ```
161
+ *
162
+ * Be sure to consult this plugin's documentation:
163
+ * https://grammy.dev/plugins/conversations
164
+ */
165
+ export declare class Conversation<OC extends Context = Context, C extends Context = Context> {
166
+ private controls;
167
+ private hydrate;
168
+ private escape;
169
+ private plugins;
170
+ private options;
171
+ /** `true` if `external` is currently running, `false` otherwise */
172
+ private insideExternal;
173
+ private menuPool;
174
+ private combineAnd;
175
+ /**
176
+ * Constructs a new conversation handle.
177
+ *
178
+ * This is called internally in order to construct the first argument for a
179
+ * conversation builder function. You typically don't need to construct this
180
+ * class yourself.
181
+ *
182
+ * @param controls Controls for the underlying replay engine
183
+ * @param hydrate Context construction callback
184
+ * @param escape Callback to support outside context objects in `external`
185
+ * @param plugins Middleware to hydrate context objects
186
+ * @param options Additional configuration options
187
+ */
188
+ constructor(controls: ReplayControls, hydrate: (update: Update) => C, escape: ApplyContext<OC>, plugins: MiddlewareFn<C>, options: ConversationHandleOptions);
189
+ /**
190
+ * Waits for a new update and returns the corresponding context object as
191
+ * soon as it arrives.
192
+ *
193
+ * Note that wait calls terminate the conversation function, save the state
194
+ * of execution, and only resolve when the conversation is replayed. If this
195
+ * is not obvious to you, it means that you probably should read [the
196
+ * documentation of this plugin](https://grammy.dev/plugins/conversations)
197
+ * in order to avoid common pitfalls.
198
+ *
199
+ * You can pass a timeout in the optional options object. This lets you
200
+ * terminate the conversation automatically if the update arrives too late.
201
+ *
202
+ * @param options Optional options for wait timeouts etc
203
+ */
204
+ wait(options?: WaitOptions): AndPromise<C>;
205
+ /**
206
+ * Performs a filtered wait call that is defined by a given predicate. In
207
+ * other words, this method waits for an update, and calls `skip` if the
208
+ * received context object does not pass validation performed by the given
209
+ * predicate function.
210
+ *
211
+ * If a context object is discarded, you can perform any action by
212
+ * specifying `otherwise` in the options.
213
+ *
214
+ * ```ts
215
+ * const ctx = await conversation.waitUntil(ctx => ctx.msg?.text?.endsWith("grammY"), {
216
+ * otherwise: ctx => ctx.reply("Send a message that ends with grammY!")
217
+ * })
218
+ * ```
219
+ *
220
+ * If you pass a type predicate, the type of the resulting context object
221
+ * will be narrowed down.
222
+ *
223
+ * ```ts
224
+ * const ctx = await conversation.waitUntil(Context.has.filterQuery(":text"))
225
+ * const text = ctx.msg.text;
226
+ * ```
227
+ *
228
+ * You can combine calls to `waitUntil` with other filtered wait calls by
229
+ * chaining them.
230
+ *
231
+ * ```ts
232
+ * const ctx = await conversation.waitUntil(ctx => ctx.msg?.text?.endsWith("grammY"))
233
+ * .andFor("::hashtag")
234
+ * ```
235
+ *
236
+ * @param predicate A predicate function to validate context objects
237
+ * @param opts Optional options object
238
+ */
239
+ waitUntil<D extends C>(predicate: (ctx: C) => ctx is D, opts?: OtherwiseOptions<C>): AndPromise<D>;
240
+ waitUntil(predicate: (ctx: C) => boolean | Promise<boolean>, opts?: OtherwiseOptions<C>): AndPromise<C>;
241
+ /**
242
+ * Performs a filtered wait call that is defined by a given negated
243
+ * predicate. In other words, this method waits for an update, and calls
244
+ * `skip` if the received context object passed validation performed by the
245
+ * given predicate function. That is the exact same thigs as calling
246
+ * {@link Conversation.waitUntil} but with the predicate function being
247
+ * negated.
248
+ *
249
+ * If a context object is discarded (the predicate function returns `true`
250
+ * for it), you can perform any action by specifying `otherwise` in the
251
+ * options.
252
+ *
253
+ * ```ts
254
+ * const ctx = await conversation.waitUnless(ctx => ctx.msg?.text?.endsWith("grammY"), {
255
+ * otherwise: ctx => ctx.reply("Send a message that does not end with grammY!")
256
+ * })
257
+ * ```
258
+ *
259
+ * You can combine calls to `waitUnless` with other filtered wait calls by
260
+ * chaining them.
261
+ *
262
+ * ```ts
263
+ * const ctx = await conversation.waitUnless(ctx => ctx.msg?.text?.endsWith("grammY"))
264
+ * .andFor("::hashtag")
265
+ * ```
266
+ *
267
+ * @param predicate A predicate function to discard context objects
268
+ * @param opts Optional options object
269
+ */
270
+ waitUnless(predicate: (ctx: C) => boolean | Promise<boolean>, opts?: OtherwiseOptions<C>): AndPromise<C>;
271
+ /**
272
+ * Performs a filtered wait call that is defined by a filter query. In other
273
+ * words, this method waits for an update, and calls `skip` if the received
274
+ * context object does not match the filter query. This uses the same logic
275
+ * as `bot.on`.
276
+ *
277
+ * If a context object is discarded, you can perform any action by
278
+ * specifying `otherwise` in the options.
279
+ *
280
+ * ```ts
281
+ * const ctx = await conversation.waitFor(":text", {
282
+ * otherwise: ctx => ctx.reply("Please send a text message!")
283
+ * })
284
+ * // Type inference works:
285
+ * const text = ctx.msg.text;
286
+ * ```
287
+ *
288
+ * You can combine calls to `waitFor` with other filtered wait calls by
289
+ * chaining them.
290
+ *
291
+ * ```ts
292
+ * const ctx = await conversation.waitFor(":text").andFor("::hashtag")
293
+ * ```
294
+ *
295
+ * @param query A filter query to match
296
+ * @param opts Optional options object
297
+ */
298
+ waitFor<Q extends FilterQuery>(query: Q | Q[], opts?: OtherwiseOptions<C>): AndPromise<Filter<C, Q>>;
299
+ /**
300
+ * Performs a filtered wait call that is defined by a hears filter. In other
301
+ * words, this method waits for an update, and calls `skip` if the received
302
+ * context object does not contain text that matches the given text or
303
+ * regular expression. This uses the same logic as `bot.hears`.
304
+ *
305
+ * If a context object is discarded, you can perform any action by
306
+ * specifying `otherwise` in the options.
307
+ *
308
+ * ```ts
309
+ * const ctx = await conversation.waitForHears(["yes", "no"], {
310
+ * otherwise: ctx => ctx.reply("Please send yes or no!")
311
+ * })
312
+ * // Type inference works:
313
+ * const answer = ctx.match
314
+ * ```
315
+ *
316
+ * You can combine calls to `waitForHears` with other filtered wait calls by
317
+ * chaining them. For instance, this can be used to only receive text from
318
+ * text messages—not including channel posts or media captions.
319
+ *
320
+ * ```ts
321
+ * const ctx = await conversation.waitForHears(["yes", "no"])
322
+ * .andFor("message:text")
323
+ * const text = ctx.message.text
324
+ * ```
325
+ *
326
+ * @param trigger The text to look for
327
+ * @param opts Optional options object
328
+ */
329
+ waitForHears(trigger: MaybeArray<string | RegExp>, opts?: OtherwiseOptions<C>): AndPromise<HearsContext<C>>;
330
+ /**
331
+ * Performs a filtered wait call that is defined by a command filter. In
332
+ * other words, this method waits for an update, and calls `skip` if the
333
+ * received context object does not contain the expected command. This uses
334
+ * the same logic as `bot.command`.
335
+ *
336
+ * If a context object is discarded, you can perform any action by
337
+ * specifying `otherwise` in the options.
338
+ *
339
+ * ```ts
340
+ * const ctx = await conversation.waitForCommand("start", {
341
+ * otherwise: ctx => ctx.reply("Please send /start!")
342
+ * })
343
+ * // Type inference works for deep links:
344
+ * const args = ctx.match
345
+ * ```
346
+ *
347
+ * You can combine calls to `waitForCommand` with other filtered wait calls
348
+ * by chaining them. For instance, this can be used to only receive commands
349
+ * from text messages—not including channel posts.
350
+ *
351
+ * ```ts
352
+ * const ctx = await conversation.waitForCommand("start")
353
+ * .andFor("message")
354
+ * ```
355
+ *
356
+ * @param command The command to look for
357
+ * @param opts Optional options object
358
+ */
359
+ waitForCommand(command: MaybeArray<StringWithCommandSuggestions>, opts?: OtherwiseOptions<C>): AndPromise<CommandContext<C>>;
360
+ /**
361
+ * Performs a filtered wait call that is defined by a reaction filter. In
362
+ * other words, this method waits for an update, and calls `skip` if the
363
+ * received context object does not contain the expected reaction update.
364
+ * This uses the same logic as `bot.reaction`.
365
+ *
366
+ * If a context object is discarded, you can perform any action by
367
+ * specifying `otherwise` in the options.
368
+ *
369
+ * ```ts
370
+ * const ctx = await conversation.waitForReaction('👍', {
371
+ * otherwise: ctx => ctx.reply("Please upvote a message!")
372
+ * })
373
+ * // Type inference works:
374
+ * const args = ctx.messageReaction
375
+ * ```
376
+ *
377
+ * You can combine calls to `waitForReaction` with other filtered wait calls
378
+ * by chaining them.
379
+ *
380
+ * ```ts
381
+ * const ctx = await conversation.waitForReaction('👍')
382
+ * .andFrom(ADMIN_USER_ID)
383
+ * ```
384
+ *
385
+ * @param reaction The reaction to look for
386
+ * @param opts Optional options object
387
+ */
388
+ waitForReaction(reaction: MaybeArray<ReactionTypeEmoji["emoji"] | ReactionType>, opts?: OtherwiseOptions<C>): AndPromise<ReactionContext<C>>;
389
+ /**
390
+ * Performs a filtered wait call that is defined by a callback query filter.
391
+ * In other words, this method waits for an update, and calls `skip` if the
392
+ * received context object does not contain the expected callback query
393
+ * update. This uses the same logic as `bot.callbackQuery`.
394
+ *
395
+ * If a context object is discarded, you can perform any action by
396
+ * specifying `otherwise` in the options.
397
+ *
398
+ * ```ts
399
+ * const ctx = await conversation.waitForCallbackQuery(/button-\d+/, {
400
+ * otherwise: ctx => ctx.reply("Please click a button!")
401
+ * })
402
+ * // Type inference works:
403
+ * const data = ctx.callbackQuery.data
404
+ * ```
405
+ *
406
+ * You can combine calls to `waitForCallbackQuery` with other filtered wait
407
+ * calls by chaining them.
408
+ *
409
+ * ```ts
410
+ * const ctx = await conversation.waitForCallbackQuery('data')
411
+ * .andFrom(ADMIN_USER_ID)
412
+ * ```
413
+ *
414
+ * @param trigger The string to look for in the payload
415
+ * @param opts Optional options object
416
+ */
417
+ waitForCallbackQuery(trigger: MaybeArray<string | RegExp>, opts?: OtherwiseOptions<C>): AndPromise<CallbackQueryContext<C>>;
418
+ /**
419
+ * Performs a filtered wait call that is defined by a game query filter. In
420
+ * other words, this method waits for an update, and calls `skip` if the
421
+ * received context object does not contain the expected game query update.
422
+ * This uses the same logic as `bot.gameQuery`.
423
+ *
424
+ * If a context object is discarded, you can perform any action by
425
+ * specifying `otherwise` in the options.
426
+ *
427
+ * ```ts
428
+ * const ctx = await conversation.waitForGameQuery(/game-\d+/, {
429
+ * otherwise: ctx => ctx.reply("Please play a game!")
430
+ * })
431
+ * // Type inference works:
432
+ * const data = ctx.callbackQuery.game_short_name
433
+ * ```
434
+ *
435
+ * You can combine calls to `waitForGameQuery` with other filtered wait
436
+ * calls by chaining them.
437
+ *
438
+ * ```ts
439
+ * const ctx = await conversation.waitForGameQuery('data')
440
+ * .andFrom(ADMIN_USER_ID)
441
+ * ```
442
+ *
443
+ * @param trigger The string to look for in the payload
444
+ * @param opts Optional options object
445
+ */
446
+ waitForGameQuery(trigger: MaybeArray<string | RegExp>, opts?: OtherwiseOptions<C>): AndPromise<GameQueryContext<C>>;
447
+ /**
448
+ * Performs a filtered wait call that is defined by a user-specific filter.
449
+ * In other words, this method waits for an update, and calls `skip` if the
450
+ * received context object was not triggered by the given user.
451
+ *
452
+ * If a context object is discarded, you can perform any action by
453
+ * specifying `otherwise` in the options.
454
+ *
455
+ * ```ts
456
+ * const ctx = await conversation.waitFrom(targetUser, {
457
+ * otherwise: ctx => ctx.reply("I did not mean you!")
458
+ * })
459
+ * // Type inference works:
460
+ * const user = ctx.from.first_name
461
+ * ```
462
+ *
463
+ * You can combine calls to `waitFrom` with other filtered wait calls by
464
+ * chaining them.
465
+ *
466
+ * ```ts
467
+ * const ctx = await conversation.waitFrom(targetUser).andFor(":text")
468
+ * ```
469
+ *
470
+ * @param user The user or user identifer to look for
471
+ * @param opts Optional options object
472
+ */
473
+ waitFrom(user: number | User, opts?: OtherwiseOptions<C>): AndPromise<C & {
474
+ from: User;
475
+ }>;
476
+ /**
477
+ * Performs a filtered wait call that is defined by a message reply. In
478
+ * other words, this method waits for an update, and calls `skip` if the
479
+ * received context object does not contain a reply to a given message.
480
+ *
481
+ * If a context object is discarded, you can perform any action by
482
+ * specifying `otherwise` in the options.
483
+ *
484
+ * ```ts
485
+ * const ctx = await conversation.waitForReplyTo(message, {
486
+ * otherwise: ctx => ctx.reply("Please reply to this message!", {
487
+ * reply_parameters: { message_id: message.message_id }
488
+ * })
489
+ * })
490
+ * // Type inference works:
491
+ * const id = ctx.msg.message_id
492
+ * ```
493
+ *
494
+ * You can combine calls to `waitForReplyTo` with other filtered wait calls
495
+ * by chaining them.
496
+ *
497
+ * ```ts
498
+ * const ctx = await conversation.waitForReplyTo(message).andFor(":text")
499
+ * ```
500
+ *
501
+ * @param message_id The message identifer or object to look for in a reply
502
+ * @param opts Optional options object
503
+ */
504
+ waitForReplyTo(message_id: number | {
505
+ message_id: number;
506
+ }, opts?: OtherwiseOptions<C>): AndPromise<Filter<C, "message" | "channel_post">>;
507
+ /**
508
+ * Skips the current update. The current update is the update that was
509
+ * received in the last wait call.
510
+ *
511
+ * In a sense, this will undo receiving an update. The replay logs will be
512
+ * reset so it will look like the conversation had never received the update
513
+ * in the first place. Note, however, that any API calls performs between
514
+ * wait and skip are not going to be reversed. In particular, messages will
515
+ * not be unsent.
516
+ *
517
+ * By default, skipping an update drops it. This means that no other
518
+ * handlers (including downstream middleware) will run. However, if this
519
+ * conversation is marked as parallel, skip will behave differently and
520
+ * resume middleware execution by default. This is needed for other parallel
521
+ * conversations with the same or a different identifier to receive the
522
+ * update.
523
+ *
524
+ * This behavior can be overridden by passing `{ next: true }` or `{ next:
525
+ * false }` to skip.
526
+ *
527
+ * If several wait calls are used concurrently inside the same conversation,
528
+ * they will resolve one after another until one of them does not skip the
529
+ * update. The conversation will only skip an update when all concurrent
530
+ * wait calls skip the update. Specifying `next` for a skip call that is not
531
+ * the final skip call has no effect.
532
+ *
533
+ * @param options Optional options to control middleware resumption
534
+ */
535
+ skip(options?: SkipOptions): Promise<never>;
536
+ /**
537
+ * Calls any exit handlers if installed, and then terminates the
538
+ * conversation immediately. This method never returns.
539
+ *
540
+ * By default, this will consume the update. Pass `{ next: true }` to make
541
+ * sure that downstream middleware is called.
542
+ *
543
+ * @param options Optional options to control middleware resumption
544
+ */
545
+ halt(options?: HaltOptions): Promise<never>;
546
+ /**
547
+ * Creates a new checkpoint at the current point of the conversation.
548
+ *
549
+ * This checkpoint can be passed to `rewind` in order to go back in the
550
+ * conversation and resume it from an earlier point.
551
+ *
552
+ * ```ts
553
+ * const check = conversation.checkpoint();
554
+ *
555
+ * // Later:
556
+ * await conversation.rewind(check);
557
+ * ```
558
+ */
559
+ checkpoint(): Checkpoint;
560
+ /**
561
+ * Rewinds the conversation to a previous point and continues execution from
562
+ * there. This point is specified by a checkpoint that can be created by
563
+ * calling {@link Conversation.checkpoint}.
564
+ *
565
+ * ```ts
566
+ * const check = conversation.checkpoint();
567
+ *
568
+ * // Later:
569
+ * await conversation.rewind(check);
570
+ * ```
571
+ *
572
+ * @param checkpoint A previously created checkpoint
573
+ */
574
+ rewind(checkpoint: Checkpoint): Promise<never>;
575
+ /**
576
+ * Runs a function outside of the replay engine. This provides a safe way to
577
+ * perform side-effects such as database communication, disk operations,
578
+ * session access, file downloads, requests to external APIs, randomness,
579
+ * time-based functions, and more. **It requires any data obtained from the
580
+ * outside to be serializable.**
581
+ *
582
+ * Remember that a conversation function is not executed like a normal
583
+ * JavaScript function. Instead, it is often interrupted and replayed,
584
+ * sometimes many times for the same update. If this is not obvious to you,
585
+ * it means that you probably should read [the documentation of this
586
+ * plugin](https://grammy.dev/plugins/conversations) in order to avoid
587
+ * common pitfalls.
588
+ *
589
+ * For instance, if you want to access to your database, you only want to
590
+ * read or write data once, rather than doing it once per replay. `external`
591
+ * provides an escape hatch to this situation. You can wrap your database
592
+ * call inside `external` to mark it as something that performs
593
+ * side-effects. The replay engine inside the conversations plugin will then
594
+ * make sure to only execute this operation once. This looks as follows.
595
+ *
596
+ * ```ts
597
+ * // Read from database
598
+ * const data = await conversation.external(async () => {
599
+ * return await readFromDatabase()
600
+ * })
601
+ *
602
+ * // Write to database
603
+ * await conversation.external(async () => {
604
+ * await writeToDatabase(data)
605
+ * })
606
+ * ```
607
+ *
608
+ * When `external` is called, it returns whichever data the given callback
609
+ * function returns. Note that this data has to be persisted by the plugin,
610
+ * so you have to make sure that it can be serialized. The data will be
611
+ * stored in the storage backend you provided when installing the
612
+ * conversations plugin via `bot.use`. In particular, it does not work well
613
+ * to return objects created by an ORM, as these objects have functions
614
+ * installed on them which will be lost during serialization.
615
+ *
616
+ * As a rule of thumb, imagine that all data from `external` is passed
617
+ * through `JSON.parse(JSON.stringify(data))` (even though this is not what
618
+ * actually happens under the hood).
619
+ *
620
+ * The callback function passed to `external` receives the outside context
621
+ * object from the current middleware pass. This lets you access properties
622
+ * on the context object that are only present in the outside middleware
623
+ * system, but that have not been installed on the context objects inside a
624
+ * conversation. For example, you can access your session data this way.
625
+ *
626
+ * ```ts
627
+ * // Read from session
628
+ * const data = await conversation.external((ctx) => {
629
+ * return ctx.session.data
630
+ * })
631
+ *
632
+ * // Write to session
633
+ * await conversation.external((ctx) => {
634
+ * ctx.session.data = data
635
+ * })
636
+ * ```
637
+ *
638
+ * Note that while a call to `external` is running, you cannot do any of the
639
+ * following things.
640
+ *
641
+ * - start a concurrent call to `external` from the same conversation
642
+ * - start a nested call to `external` from the same conversation
643
+ * - start a Bot API call from the same conversation
644
+ *
645
+ * Naturally, it is possible to have several concurrent calls to `externals`
646
+ * if they happen in unrelated chats. This still means that you should keep
647
+ * the code inside `external` to a minimum and actually only perform the
648
+ * desired side-effect itself.
649
+ *
650
+ * If you want to return data from `external` that cannot be serialized, you
651
+ * can specify a custom serialization function. This allows you choose a
652
+ * different intermediate data representation during storage than what is
653
+ * present at runtime.
654
+ *
655
+ * ```ts
656
+ * // Read bigint from an API but persist it as a string
657
+ * const largeNumber: bigint = await conversation.external({
658
+ * task: () => fetchCoolBigIntFromTheInternet(),
659
+ * beforeStore: (largeNumber) => String(largeNumber),
660
+ * afterLoad: (str) => BigInt(str),
661
+ * })
662
+ * ```
663
+ *
664
+ * Note how we read a bigint from the internet, but we convert it to string
665
+ * during persistence. This now allows us to use a storage adapter that only
666
+ * handles strings but does not need to support the bigint type.
667
+ *
668
+ * @param op An operation to perform outside of the conversation
669
+ */
670
+ external<R, I = any>(op: ExternalOp<OC, R, I>["task"] | ExternalOp<OC, R, I>): Promise<R>;
671
+ /**
672
+ * Takes `Date.now()` once when reached, and returns the same value during
673
+ * every replay. Prefer this over calling `Date.now()` directly.
674
+ */
675
+ now(): Promise<number>;
676
+ /**
677
+ * Takes `Math.random()` once when reached, and returns the same value
678
+ * during every replay. Prefer this over calling `Math.random()` directly.
679
+ */
680
+ random(): Promise<number>;
681
+ /**
682
+ * Calls `console.log` only the first time it is reached, but not during
683
+ * subsequent replays. Prefer this over calling `console.log` directly.
684
+ */
685
+ log(...data: unknown[]): Promise<void>;
686
+ /**
687
+ * Calls `console.error` only the first time it is reached, but not during
688
+ * subsequent replays. Prefer this over calling `console.error` directly.
689
+ */
690
+ error(...data: unknown[]): Promise<void>;
691
+ /**
692
+ * Creates a new conversational menu.
693
+ *
694
+ * A conversational menu is a an interactive inline keyboard that is sent to
695
+ * the user from within a conversation.
696
+ *
697
+ * ```ts
698
+ * const menu = conversation.menu()
699
+ * .text("Send message", ctx => ctx.reply("Hi!"))
700
+ * .text("Close", ctx => ctx.menu.close())
701
+ *
702
+ * await ctx.reply("Menu message", { reply_markup: menu })
703
+ * ```
704
+ *
705
+ * If a menu identifier is specified, conversational menus enable seamless
706
+ * navigation.
707
+ *
708
+ * ```ts
709
+ * const menu = conversation.menu("root")
710
+ * .submenu("Open submenu", ctx => ctx.editMessageText("submenu"))
711
+ * .text("Close", ctx => ctx.menu.close())
712
+ * conversation.menu("child", { parent: "root" })
713
+ * .back("Go back", ctx => ctx.editMessageText("Root menu"))
714
+ *
715
+ * await ctx.reply("Root menu", { reply_markup: menu })
716
+ * ```
717
+ *
718
+ * You can also interact with the conversation from inside button handlers.
719
+ *
720
+ * ```ts
721
+ * let name = ""
722
+ * const menu = conversation.menu()
723
+ * .text("Set name", async ctx => {
724
+ * await ctx.reply("What's your name?")
725
+ * name = await conversation.form.text()
726
+ * await ctx.editMessageText(name)
727
+ * })
728
+ * .text("Clear name", ctx => {
729
+ * name = ""
730
+ * await ctx.editMessageText("No name")
731
+ * })
732
+ *
733
+ * await ctx.reply("No name (yet)", { reply_markup: menu })
734
+ * ```
735
+ *
736
+ * More information about conversational menus can be found [in the
737
+ * documentation](https://grammy.dev/plugins/conversations).
738
+ *
739
+ * @param id Optional menu identifier
740
+ * @param options Optional menu options
741
+ */
742
+ menu(id?: string, options?: Partial<ConversationMenuOptions<C>>): import("./menu.js").ConversationMenu<C>;
743
+ /**
744
+ * A namespace full of various utitilies for building forms.
745
+ *
746
+ * Typically, `wait` calls return context objects. Optionally, these context
747
+ * objects can be accepted or rejected based on validation, such as with
748
+ * `waitFor` which only returns context objects matching a given filter
749
+ * query.
750
+ *
751
+ * Forms add another level of convenience on top of this. They no longer
752
+ * require you to deal with context objects. Each form field performs both
753
+ * validation and selection. This means that it picks out certain property
754
+ * from the context object—such as the message text—and returns this
755
+ * property directly.
756
+ *
757
+ * As an example, here is how you can wait for a number using the form field
758
+ * `.number`.
759
+ *
760
+ * ```ts
761
+ * // Wait for a number
762
+ * const n = await conversation.form.number()
763
+ * // Send back its square
764
+ * await ctx.reply(`The square of ${n} is ${n * n}!`)
765
+ * ```
766
+ *
767
+ * There are many more form fields that let you wait for virtually any type
768
+ * of message content.
769
+ *
770
+ * All form fields give you the option to perform an action if the
771
+ * validation fails by accepting an `otherwise` function. This is similar to
772
+ * filtered wait calls.
773
+ *
774
+ * ```ts
775
+ * const text = await conversation.form.select(["Yes", "No"], {
776
+ * otherwise: ctx => ctx.reply("Please send Yes or No.")
777
+ * })
778
+ * ```
779
+ *
780
+ * In addition, all form fields give you the option to perform some action
781
+ * when a value is accepted. For example, this is how you can delete
782
+ * incoming messages.
783
+ *
784
+ * ```ts
785
+ * const text = await conversation.form.select(["Yes", "No"], {
786
+ * action: ctx => ctx.deleteMessage()
787
+ * })
788
+ * ```
789
+ *
790
+ * Note that either `otherwise` or `action` will be called, but never both
791
+ * for the same update.
792
+ */
793
+ form: ConversationForm<C>;
794
+ }
795
+ /** A promise that also contains methods for chaining filtered wait calls */
796
+ export type AndPromise<C extends Context> = Promise<C> & AndExtension<C>;
797
+ /** A container for methods that filter wait calls */
798
+ export interface AndExtension<C extends Context> {
799
+ /**
800
+ * Filters down the wait call using another custom predicate function.
801
+ * Corresponds with {@link Conversation.waitUntil}.
802
+ *
803
+ * @param predicate An extra predicate function to check
804
+ * @param opts Optional options object
805
+ */
806
+ and<D extends C>(predicate: (ctx: C) => ctx is D, opts?: AndOtherwiseOptions<C>): AndPromise<D>;
807
+ and(predicate: (ctx: C) => boolean | Promise<boolean>, opts?: AndOtherwiseOptions<C>): AndPromise<C>;
808
+ /**
809
+ * Filters down the wait call using another negated custom predicate
810
+ * function. Corresponds with {@link Conversation.waitUnless}.
811
+ *
812
+ * @param predicate An extra predicate function to check
813
+ * @param opts Optional options object
814
+ */
815
+ unless(predicate: (ctx: C) => boolean | Promise<boolean>, opts?: AndOtherwiseOptions<C>): AndPromise<C>;
816
+ /**
817
+ * Filters down the wait call using another filter query. Corresponds with
818
+ * {@link Conversation.waitFor}.
819
+ *
820
+ * @param query An extra filter query to check
821
+ * @param opts Optional options object
822
+ */
823
+ andFor<Q extends FilterQuery>(query: Q | Q[], opts?: AndOtherwiseOptions<C>): AndPromise<Filter<C, Q>>;
824
+ /**
825
+ * Filters down the wait call using another hears check. Corresponds with
826
+ * {@link Conversation.waitForHears}.
827
+ *
828
+ * @param trigger An extra text to look for
829
+ * @param opts Optional options object
830
+ */
831
+ andForHears(trigger: MaybeArray<string | RegExp>, opts?: AndOtherwiseOptions<C>): AndPromise<HearsContext<C>>;
832
+ /**
833
+ * Filters down the wait call using another command check. Corresponds with
834
+ * {@link Conversation.waitForCommand}.
835
+ *
836
+ * @param command An extra command to look for
837
+ * @param opts Optional options object
838
+ */
839
+ andForCommand(command: MaybeArray<StringWithCommandSuggestions>, opts?: AndOtherwiseOptions<C>): AndPromise<CommandContext<C>>;
840
+ /**
841
+ * Filters down the wait call using another reaction check. Corresponds with
842
+ * {@link Conversation.waitForReaction}.
843
+ *
844
+ * @param reaction An extra reaction to look for
845
+ * @param opts Optional options object
846
+ */
847
+ andForReaction(reaction: MaybeArray<ReactionTypeEmoji["emoji"] | ReactionType>, opts?: AndOtherwiseOptions<C>): AndPromise<ReactionContext<C>>;
848
+ /**
849
+ * Filters down the wait call using another callback query check.
850
+ * Corresponds with {@link Conversation.waitForCallbackQuery}.
851
+ *
852
+ * @param trigger An extra callback query to look for
853
+ * @param opts Optional options object
854
+ */
855
+ andForCallbackQuery(trigger: MaybeArray<string | RegExp>, opts?: AndOtherwiseOptions<C>): AndPromise<CallbackQueryContext<C>>;
856
+ /**
857
+ * Filters down the wait call using another game query check. Corresponds
858
+ * with {@link Conversation.waitForGameQuery}.
859
+ *
860
+ * @param trigger An extra game query to look for
861
+ * @param opts Optional options object
862
+ */
863
+ andForGameQuery(trigger: MaybeArray<string | RegExp>, opts?: AndOtherwiseOptions<C>): AndPromise<GameQueryContext<C>>;
864
+ /**
865
+ * Filters down the wait call using another check for a user. Corresponds
866
+ * with {@link Conversation.waitFrom}.
867
+ *
868
+ * @param user An extra user to look for
869
+ * @param opts Optional options object
870
+ */
871
+ andFrom(user: number | User, opts?: AndOtherwiseOptions<C>): AndPromise<C & {
872
+ from: User;
873
+ }>;
874
+ /**
875
+ * Filters down the wait call using another check for a reply. Corresponds
876
+ * with {@link Conversation.waitForReplyTo}.
877
+ *
878
+ * @param message_id An extra message to look for in a reply
879
+ * @param opts Optional options object
880
+ */
881
+ andForReplyTo(message_id: number | {
882
+ message_id: number;
883
+ }, opts?: AndOtherwiseOptions<C>): AndPromise<Filter<C, "message" | "channel_post">>;
884
+ }
885
+ export {};