@grammyjs/conversations
Version:
Conversational interfaces for grammY
886 lines (885 loc) • 36.2 kB
TypeScript
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";
import { type Checkpoint, type ReplayControls } from "./engine.js";
import { ConversationForm } from "./form.js";
import { type ConversationMenuOptions } from "./menu.js";
type MaybeArray<T> = T | T[];
/** Alias for `string` but with auto-complete for common commands */
export type StringWithCommandSuggestions = (string & Record<never, never>) | "start" | "help" | "settings" | "privacy" | "developer_info";
/**
* Specifies an external operation and how to serialize and deserialize its
* return and error values.
*
* @typeParam OC Type of the outside context object
* @typeParam R Type of the return value
* @typeParam I Type of the intermediate (serialized) representation
*/
export interface ExternalOp<OC extends Context, R, I = any> {
/**
* The external operation to perform.
*
* Receives the current context object from the surrounding middleware. This
* gives the task access to sessions (if used) and other values that are not
* present inside the conversation.
*
* @param ctx The outside context object of the surrounding middleware
*/
task(ctx: OC): R | Promise<R>;
/**
* Converts a value returned from the task to an object that can safely be
* passed to `JSON.stringify`.
*
* @param value A value to serialize
*/
beforeStore?(value: R): I | Promise<I>;
/**
* Restores the original value from the intermediate representation that
* `beforeStore` generated.
*
* @param value The value obtained from `JSON.parse`
*/
afterLoad?(value: I): R | Promise<R>;
/**
* Converts an error thrown by the task to an object that can safely be
* passed to `JSON.stringify`.
*
* @param value A thrown error
*/
beforeStoreError?(value: unknown): unknown | Promise<unknown>;
/**
* Restores the original error from the intermediate representation that
* `beforeStoreError` generated.
*
* @param value The value obtained from `JSON.parse`
*/
afterLoadError?(value: unknown): unknown | Promise<unknown>;
}
/** A function that applies a context object to a callback */
type ApplyContext<OC extends Context> = <F extends (ctx: OC) => unknown>(fn: F) => Promise<ReturnType<F>>;
/** Options for creating a conversation handle */
export interface ConversationHandleOptions {
/** Callback for when the conversation is halted */
onHalt?(): void | Promise<void>;
/** Default wait timeout */
maxMillisecondsToWait?: number;
/**
* `true` if this conversation can be entered while this or another
* conversation is already active, and `false` otherwise. Defaults to
* `false`.
*/
parallel?: boolean;
}
/**
* Options for a call to `conversation.wait()`.
*/
export interface WaitOptions {
/**
* Specifies a timeout for the wait call.
*
* When the wait call is reached, `Date.now()` is called. When the wait call
* resolves, `Date.now()` is called again, and the two values are compared.
* If the wait call resolved more than the specified number of milliseconds
* after it was reached initially, then the conversation will be halted, any
* exit handlers will be called, and the surrounding middleware will resume
* normally so that subsequent handlers can run.
*
* To the outside middleware system, this will look like the conversation
* was never active.
*/
maxMilliseconds?: number;
/**
* Collation key for the wait call, safety measure to protect against data
* corruption. This is used extensively by the plugin internally, but it is
* rarely useful to changes this behavior.
*/
collationKey?: string;
}
/**
* Options for a call to `conversation.skip()`.
*/
export interface SkipOptions {
/**
* Determines whether [the outside middleware
* system](https://grammy.dev/guide/middleware) should resume after the
* update is skipped.
*
* Pass `{ next: true }` to make sure that subsequent handlers will run.
* This effectively causes `next` to be called by the plugin.
*
* Defaults to `false` unless the conversation is marked as parallel, in
* which case this option defaults to `true`.
*/
next?: boolean;
}
/**
* Options to pass to a chained `wait` call.
*/
export interface AndOtherwiseOptions<C extends Context> extends SkipOptions {
/**
* Callback that will be invoked when the validation fails for a context
* object.
*
* @param ctx The context object that failed validation
*/
otherwise?(ctx: C): unknown | Promise<unknown>;
}
/**
* Options for a filtered wait call. A filtered wait call is a wait call that
* have extra valiation attached, such as `waitFor`, `waitUntil`, etc.
*/
export interface OtherwiseOptions<C extends Context> extends WaitOptions, AndOtherwiseOptions<C> {
}
/**
* Options for a call to `conversation.halt()`.
*/
export interface HaltOptions {
/**
* Determines whether [the outside middleware
* system](https://grammy.dev/guide/middleware) should resume after the
* conversation is halted.
*
* Pass `{ next: true }` to make sure that subsequent handlers will run.
* This effectively causes `next` to be called by the plugin.
*
* Defaults to `false`.
*/
next?: boolean;
}
/**
* A conversation handle lets you control the conversation, such as waiting for
* updates, skipping them, halting the conversation, and much more. It is the
* first parameter in each conversation builder function and provides the core
* features of this plugin.
*
* ```ts
* async function exmaple(conversation, ctx) {
* // ^ this is an instance of this class
*
* // This is how you can wait for updates:
* ctx = await conversation.wait()
* }
* ```
*
* Be sure to consult this plugin's documentation:
* https://grammy.dev/plugins/conversations
*/
export declare class Conversation<OC extends Context = Context, C extends Context = Context> {
private controls;
private hydrate;
private escape;
private plugins;
private options;
/** `true` if `external` is currently running, `false` otherwise */
private insideExternal;
private menuPool;
private combineAnd;
/**
* Constructs a new conversation handle.
*
* This is called internally in order to construct the first argument for a
* conversation builder function. You typically don't need to construct this
* class yourself.
*
* @param controls Controls for the underlying replay engine
* @param hydrate Context construction callback
* @param escape Callback to support outside context objects in `external`
* @param plugins Middleware to hydrate context objects
* @param options Additional configuration options
*/
constructor(controls: ReplayControls, hydrate: (update: Update) => C, escape: ApplyContext<OC>, plugins: MiddlewareFn<C>, options: ConversationHandleOptions);
/**
* Waits for a new update and returns the corresponding context object as
* soon as it arrives.
*
* Note that wait calls terminate the conversation function, save the state
* of execution, and only resolve when the conversation is replayed. If this
* is not obvious to you, it means that you probably should read [the
* documentation of this plugin](https://grammy.dev/plugins/conversations)
* in order to avoid common pitfalls.
*
* You can pass a timeout in the optional options object. This lets you
* terminate the conversation automatically if the update arrives too late.
*
* @param options Optional options for wait timeouts etc
*/
wait(options?: WaitOptions): AndPromise<C>;
/**
* Performs a filtered wait call that is defined by a given predicate. In
* other words, this method waits for an update, and calls `skip` if the
* received context object does not pass validation performed by the given
* predicate function.
*
* If a context object is discarded, you can perform any action by
* specifying `otherwise` in the options.
*
* ```ts
* const ctx = await conversation.waitUntil(ctx => ctx.msg?.text?.endsWith("grammY"), {
* otherwise: ctx => ctx.reply("Send a message that ends with grammY!")
* })
* ```
*
* If you pass a type predicate, the type of the resulting context object
* will be narrowed down.
*
* ```ts
* const ctx = await conversation.waitUntil(Context.has.filterQuery(":text"))
* const text = ctx.msg.text;
* ```
*
* You can combine calls to `waitUntil` with other filtered wait calls by
* chaining them.
*
* ```ts
* const ctx = await conversation.waitUntil(ctx => ctx.msg?.text?.endsWith("grammY"))
* .andFor("::hashtag")
* ```
*
* @param predicate A predicate function to validate context objects
* @param opts Optional options object
*/
waitUntil<D extends C>(predicate: (ctx: C) => ctx is D, opts?: OtherwiseOptions<C>): AndPromise<D>;
waitUntil(predicate: (ctx: C) => boolean | Promise<boolean>, opts?: OtherwiseOptions<C>): AndPromise<C>;
/**
* Performs a filtered wait call that is defined by a given negated
* predicate. In other words, this method waits for an update, and calls
* `skip` if the received context object passed validation performed by the
* given predicate function. That is the exact same thigs as calling
* {@link Conversation.waitUntil} but with the predicate function being
* negated.
*
* If a context object is discarded (the predicate function returns `true`
* for it), you can perform any action by specifying `otherwise` in the
* options.
*
* ```ts
* const ctx = await conversation.waitUnless(ctx => ctx.msg?.text?.endsWith("grammY"), {
* otherwise: ctx => ctx.reply("Send a message that does not end with grammY!")
* })
* ```
*
* You can combine calls to `waitUnless` with other filtered wait calls by
* chaining them.
*
* ```ts
* const ctx = await conversation.waitUnless(ctx => ctx.msg?.text?.endsWith("grammY"))
* .andFor("::hashtag")
* ```
*
* @param predicate A predicate function to discard context objects
* @param opts Optional options object
*/
waitUnless(predicate: (ctx: C) => boolean | Promise<boolean>, opts?: OtherwiseOptions<C>): AndPromise<C>;
/**
* Performs a filtered wait call that is defined by a filter query. In other
* words, this method waits for an update, and calls `skip` if the received
* context object does not match the filter query. This uses the same logic
* as `bot.on`.
*
* If a context object is discarded, you can perform any action by
* specifying `otherwise` in the options.
*
* ```ts
* const ctx = await conversation.waitFor(":text", {
* otherwise: ctx => ctx.reply("Please send a text message!")
* })
* // Type inference works:
* const text = ctx.msg.text;
* ```
*
* You can combine calls to `waitFor` with other filtered wait calls by
* chaining them.
*
* ```ts
* const ctx = await conversation.waitFor(":text").andFor("::hashtag")
* ```
*
* @param query A filter query to match
* @param opts Optional options object
*/
waitFor<Q extends FilterQuery>(query: Q | Q[], opts?: OtherwiseOptions<C>): AndPromise<Filter<C, Q>>;
/**
* Performs a filtered wait call that is defined by a hears filter. In other
* words, this method waits for an update, and calls `skip` if the received
* context object does not contain text that matches the given text or
* regular expression. This uses the same logic as `bot.hears`.
*
* If a context object is discarded, you can perform any action by
* specifying `otherwise` in the options.
*
* ```ts
* const ctx = await conversation.waitForHears(["yes", "no"], {
* otherwise: ctx => ctx.reply("Please send yes or no!")
* })
* // Type inference works:
* const answer = ctx.match
* ```
*
* You can combine calls to `waitForHears` with other filtered wait calls by
* chaining them. For instance, this can be used to only receive text from
* text messages—not including channel posts or media captions.
*
* ```ts
* const ctx = await conversation.waitForHears(["yes", "no"])
* .andFor("message:text")
* const text = ctx.message.text
* ```
*
* @param trigger The text to look for
* @param opts Optional options object
*/
waitForHears(trigger: MaybeArray<string | RegExp>, opts?: OtherwiseOptions<C>): AndPromise<HearsContext<C>>;
/**
* Performs a filtered wait call that is defined by a command filter. In
* other words, this method waits for an update, and calls `skip` if the
* received context object does not contain the expected command. This uses
* the same logic as `bot.command`.
*
* If a context object is discarded, you can perform any action by
* specifying `otherwise` in the options.
*
* ```ts
* const ctx = await conversation.waitForCommand("start", {
* otherwise: ctx => ctx.reply("Please send /start!")
* })
* // Type inference works for deep links:
* const args = ctx.match
* ```
*
* You can combine calls to `waitForCommand` with other filtered wait calls
* by chaining them. For instance, this can be used to only receive commands
* from text messages—not including channel posts.
*
* ```ts
* const ctx = await conversation.waitForCommand("start")
* .andFor("message")
* ```
*
* @param command The command to look for
* @param opts Optional options object
*/
waitForCommand(command: MaybeArray<StringWithCommandSuggestions>, opts?: OtherwiseOptions<C>): AndPromise<CommandContext<C>>;
/**
* Performs a filtered wait call that is defined by a reaction filter. In
* other words, this method waits for an update, and calls `skip` if the
* received context object does not contain the expected reaction update.
* This uses the same logic as `bot.reaction`.
*
* If a context object is discarded, you can perform any action by
* specifying `otherwise` in the options.
*
* ```ts
* const ctx = await conversation.waitForReaction('👍', {
* otherwise: ctx => ctx.reply("Please upvote a message!")
* })
* // Type inference works:
* const args = ctx.messageReaction
* ```
*
* You can combine calls to `waitForReaction` with other filtered wait calls
* by chaining them.
*
* ```ts
* const ctx = await conversation.waitForReaction('👍')
* .andFrom(ADMIN_USER_ID)
* ```
*
* @param reaction The reaction to look for
* @param opts Optional options object
*/
waitForReaction(reaction: MaybeArray<ReactionTypeEmoji["emoji"] | ReactionType>, opts?: OtherwiseOptions<C>): AndPromise<ReactionContext<C>>;
/**
* Performs a filtered wait call that is defined by a callback query filter.
* In other words, this method waits for an update, and calls `skip` if the
* received context object does not contain the expected callback query
* update. This uses the same logic as `bot.callbackQuery`.
*
* If a context object is discarded, you can perform any action by
* specifying `otherwise` in the options.
*
* ```ts
* const ctx = await conversation.waitForCallbackQuery(/button-\d+/, {
* otherwise: ctx => ctx.reply("Please click a button!")
* })
* // Type inference works:
* const data = ctx.callbackQuery.data
* ```
*
* You can combine calls to `waitForCallbackQuery` with other filtered wait
* calls by chaining them.
*
* ```ts
* const ctx = await conversation.waitForCallbackQuery('data')
* .andFrom(ADMIN_USER_ID)
* ```
*
* @param trigger The string to look for in the payload
* @param opts Optional options object
*/
waitForCallbackQuery(trigger: MaybeArray<string | RegExp>, opts?: OtherwiseOptions<C>): AndPromise<CallbackQueryContext<C>>;
/**
* Performs a filtered wait call that is defined by a game query filter. In
* other words, this method waits for an update, and calls `skip` if the
* received context object does not contain the expected game query update.
* This uses the same logic as `bot.gameQuery`.
*
* If a context object is discarded, you can perform any action by
* specifying `otherwise` in the options.
*
* ```ts
* const ctx = await conversation.waitForGameQuery(/game-\d+/, {
* otherwise: ctx => ctx.reply("Please play a game!")
* })
* // Type inference works:
* const data = ctx.callbackQuery.game_short_name
* ```
*
* You can combine calls to `waitForGameQuery` with other filtered wait
* calls by chaining them.
*
* ```ts
* const ctx = await conversation.waitForGameQuery('data')
* .andFrom(ADMIN_USER_ID)
* ```
*
* @param trigger The string to look for in the payload
* @param opts Optional options object
*/
waitForGameQuery(trigger: MaybeArray<string | RegExp>, opts?: OtherwiseOptions<C>): AndPromise<GameQueryContext<C>>;
/**
* Performs a filtered wait call that is defined by a user-specific filter.
* In other words, this method waits for an update, and calls `skip` if the
* received context object was not triggered by the given user.
*
* If a context object is discarded, you can perform any action by
* specifying `otherwise` in the options.
*
* ```ts
* const ctx = await conversation.waitFrom(targetUser, {
* otherwise: ctx => ctx.reply("I did not mean you!")
* })
* // Type inference works:
* const user = ctx.from.first_name
* ```
*
* You can combine calls to `waitFrom` with other filtered wait calls by
* chaining them.
*
* ```ts
* const ctx = await conversation.waitFrom(targetUser).andFor(":text")
* ```
*
* @param user The user or user identifer to look for
* @param opts Optional options object
*/
waitFrom(user: number | User, opts?: OtherwiseOptions<C>): AndPromise<C & {
from: User;
}>;
/**
* Performs a filtered wait call that is defined by a message reply. In
* other words, this method waits for an update, and calls `skip` if the
* received context object does not contain a reply to a given message.
*
* If a context object is discarded, you can perform any action by
* specifying `otherwise` in the options.
*
* ```ts
* const ctx = await conversation.waitForReplyTo(message, {
* otherwise: ctx => ctx.reply("Please reply to this message!", {
* reply_parameters: { message_id: message.message_id }
* })
* })
* // Type inference works:
* const id = ctx.msg.message_id
* ```
*
* You can combine calls to `waitForReplyTo` with other filtered wait calls
* by chaining them.
*
* ```ts
* const ctx = await conversation.waitForReplyTo(message).andFor(":text")
* ```
*
* @param message_id The message identifer or object to look for in a reply
* @param opts Optional options object
*/
waitForReplyTo(message_id: number | {
message_id: number;
}, opts?: OtherwiseOptions<C>): AndPromise<Filter<C, "message" | "channel_post">>;
/**
* Skips the current update. The current update is the update that was
* received in the last wait call.
*
* In a sense, this will undo receiving an update. The replay logs will be
* reset so it will look like the conversation had never received the update
* in the first place. Note, however, that any API calls performs between
* wait and skip are not going to be reversed. In particular, messages will
* not be unsent.
*
* By default, skipping an update drops it. This means that no other
* handlers (including downstream middleware) will run. However, if this
* conversation is marked as parallel, skip will behave differently and
* resume middleware execution by default. This is needed for other parallel
* conversations with the same or a different identifier to receive the
* update.
*
* This behavior can be overridden by passing `{ next: true }` or `{ next:
* false }` to skip.
*
* If several wait calls are used concurrently inside the same conversation,
* they will resolve one after another until one of them does not skip the
* update. The conversation will only skip an update when all concurrent
* wait calls skip the update. Specifying `next` for a skip call that is not
* the final skip call has no effect.
*
* @param options Optional options to control middleware resumption
*/
skip(options?: SkipOptions): Promise<never>;
/**
* Calls any exit handlers if installed, and then terminates the
* conversation immediately. This method never returns.
*
* By default, this will consume the update. Pass `{ next: true }` to make
* sure that downstream middleware is called.
*
* @param options Optional options to control middleware resumption
*/
halt(options?: HaltOptions): Promise<never>;
/**
* Creates a new checkpoint at the current point of the conversation.
*
* This checkpoint can be passed to `rewind` in order to go back in the
* conversation and resume it from an earlier point.
*
* ```ts
* const check = conversation.checkpoint();
*
* // Later:
* await conversation.rewind(check);
* ```
*/
checkpoint(): Checkpoint;
/**
* Rewinds the conversation to a previous point and continues execution from
* there. This point is specified by a checkpoint that can be created by
* calling {@link Conversation.checkpoint}.
*
* ```ts
* const check = conversation.checkpoint();
*
* // Later:
* await conversation.rewind(check);
* ```
*
* @param checkpoint A previously created checkpoint
*/
rewind(checkpoint: Checkpoint): Promise<never>;
/**
* Runs a function outside of the replay engine. This provides a safe way to
* perform side-effects such as database communication, disk operations,
* session access, file downloads, requests to external APIs, randomness,
* time-based functions, and more. **It requires any data obtained from the
* outside to be serializable.**
*
* Remember that a conversation function is not executed like a normal
* JavaScript function. Instead, it is often interrupted and replayed,
* sometimes many times for the same update. If this is not obvious to you,
* it means that you probably should read [the documentation of this
* plugin](https://grammy.dev/plugins/conversations) in order to avoid
* common pitfalls.
*
* For instance, if you want to access to your database, you only want to
* read or write data once, rather than doing it once per replay. `external`
* provides an escape hatch to this situation. You can wrap your database
* call inside `external` to mark it as something that performs
* side-effects. The replay engine inside the conversations plugin will then
* make sure to only execute this operation once. This looks as follows.
*
* ```ts
* // Read from database
* const data = await conversation.external(async () => {
* return await readFromDatabase()
* })
*
* // Write to database
* await conversation.external(async () => {
* await writeToDatabase(data)
* })
* ```
*
* When `external` is called, it returns whichever data the given callback
* function returns. Note that this data has to be persisted by the plugin,
* so you have to make sure that it can be serialized. The data will be
* stored in the storage backend you provided when installing the
* conversations plugin via `bot.use`. In particular, it does not work well
* to return objects created by an ORM, as these objects have functions
* installed on them which will be lost during serialization.
*
* As a rule of thumb, imagine that all data from `external` is passed
* through `JSON.parse(JSON.stringify(data))` (even though this is not what
* actually happens under the hood).
*
* The callback function passed to `external` receives the outside context
* object from the current middleware pass. This lets you access properties
* on the context object that are only present in the outside middleware
* system, but that have not been installed on the context objects inside a
* conversation. For example, you can access your session data this way.
*
* ```ts
* // Read from session
* const data = await conversation.external((ctx) => {
* return ctx.session.data
* })
*
* // Write to session
* await conversation.external((ctx) => {
* ctx.session.data = data
* })
* ```
*
* Note that while a call to `external` is running, you cannot do any of the
* following things.
*
* - start a concurrent call to `external` from the same conversation
* - start a nested call to `external` from the same conversation
* - start a Bot API call from the same conversation
*
* Naturally, it is possible to have several concurrent calls to `externals`
* if they happen in unrelated chats. This still means that you should keep
* the code inside `external` to a minimum and actually only perform the
* desired side-effect itself.
*
* If you want to return data from `external` that cannot be serialized, you
* can specify a custom serialization function. This allows you choose a
* different intermediate data representation during storage than what is
* present at runtime.
*
* ```ts
* // Read bigint from an API but persist it as a string
* const largeNumber: bigint = await conversation.external({
* task: () => fetchCoolBigIntFromTheInternet(),
* beforeStore: (largeNumber) => String(largeNumber),
* afterLoad: (str) => BigInt(str),
* })
* ```
*
* Note how we read a bigint from the internet, but we convert it to string
* during persistence. This now allows us to use a storage adapter that only
* handles strings but does not need to support the bigint type.
*
* @param op An operation to perform outside of the conversation
*/
external<R, I = any>(op: ExternalOp<OC, R, I>["task"] | ExternalOp<OC, R, I>): Promise<R>;
/**
* Takes `Date.now()` once when reached, and returns the same value during
* every replay. Prefer this over calling `Date.now()` directly.
*/
now(): Promise<number>;
/**
* Takes `Math.random()` once when reached, and returns the same value
* during every replay. Prefer this over calling `Math.random()` directly.
*/
random(): Promise<number>;
/**
* Calls `console.log` only the first time it is reached, but not during
* subsequent replays. Prefer this over calling `console.log` directly.
*/
log(...data: unknown[]): Promise<void>;
/**
* Calls `console.error` only the first time it is reached, but not during
* subsequent replays. Prefer this over calling `console.error` directly.
*/
error(...data: unknown[]): Promise<void>;
/**
* Creates a new conversational menu.
*
* A conversational menu is a an interactive inline keyboard that is sent to
* the user from within a conversation.
*
* ```ts
* const menu = conversation.menu()
* .text("Send message", ctx => ctx.reply("Hi!"))
* .text("Close", ctx => ctx.menu.close())
*
* await ctx.reply("Menu message", { reply_markup: menu })
* ```
*
* If a menu identifier is specified, conversational menus enable seamless
* navigation.
*
* ```ts
* const menu = conversation.menu("root")
* .submenu("Open submenu", ctx => ctx.editMessageText("submenu"))
* .text("Close", ctx => ctx.menu.close())
* conversation.menu("child", { parent: "root" })
* .back("Go back", ctx => ctx.editMessageText("Root menu"))
*
* await ctx.reply("Root menu", { reply_markup: menu })
* ```
*
* You can also interact with the conversation from inside button handlers.
*
* ```ts
* let name = ""
* const menu = conversation.menu()
* .text("Set name", async ctx => {
* await ctx.reply("What's your name?")
* name = await conversation.form.text()
* await ctx.editMessageText(name)
* })
* .text("Clear name", ctx => {
* name = ""
* await ctx.editMessageText("No name")
* })
*
* await ctx.reply("No name (yet)", { reply_markup: menu })
* ```
*
* More information about conversational menus can be found [in the
* documentation](https://grammy.dev/plugins/conversations).
*
* @param id Optional menu identifier
* @param options Optional menu options
*/
menu(id?: string, options?: Partial<ConversationMenuOptions<C>>): import("./menu.js").ConversationMenu<C>;
/**
* A namespace full of various utitilies for building forms.
*
* Typically, `wait` calls return context objects. Optionally, these context
* objects can be accepted or rejected based on validation, such as with
* `waitFor` which only returns context objects matching a given filter
* query.
*
* Forms add another level of convenience on top of this. They no longer
* require you to deal with context objects. Each form field performs both
* validation and selection. This means that it picks out certain property
* from the context object—such as the message text—and returns this
* property directly.
*
* As an example, here is how you can wait for a number using the form field
* `.number`.
*
* ```ts
* // Wait for a number
* const n = await conversation.form.number()
* // Send back its square
* await ctx.reply(`The square of ${n} is ${n * n}!`)
* ```
*
* There are many more form fields that let you wait for virtually any type
* of message content.
*
* All form fields give you the option to perform an action if the
* validation fails by accepting an `otherwise` function. This is similar to
* filtered wait calls.
*
* ```ts
* const text = await conversation.form.select(["Yes", "No"], {
* otherwise: ctx => ctx.reply("Please send Yes or No.")
* })
* ```
*
* In addition, all form fields give you the option to perform some action
* when a value is accepted. For example, this is how you can delete
* incoming messages.
*
* ```ts
* const text = await conversation.form.select(["Yes", "No"], {
* action: ctx => ctx.deleteMessage()
* })
* ```
*
* Note that either `otherwise` or `action` will be called, but never both
* for the same update.
*/
form: ConversationForm<C>;
}
/** A promise that also contains methods for chaining filtered wait calls */
export type AndPromise<C extends Context> = Promise<C> & AndExtension<C>;
/** A container for methods that filter wait calls */
export interface AndExtension<C extends Context> {
/**
* Filters down the wait call using another custom predicate function.
* Corresponds with {@link Conversation.waitUntil}.
*
* @param predicate An extra predicate function to check
* @param opts Optional options object
*/
and<D extends C>(predicate: (ctx: C) => ctx is D, opts?: AndOtherwiseOptions<C>): AndPromise<D>;
and(predicate: (ctx: C) => boolean | Promise<boolean>, opts?: AndOtherwiseOptions<C>): AndPromise<C>;
/**
* Filters down the wait call using another negated custom predicate
* function. Corresponds with {@link Conversation.waitUnless}.
*
* @param predicate An extra predicate function to check
* @param opts Optional options object
*/
unless(predicate: (ctx: C) => boolean | Promise<boolean>, opts?: AndOtherwiseOptions<C>): AndPromise<C>;
/**
* Filters down the wait call using another filter query. Corresponds with
* {@link Conversation.waitFor}.
*
* @param query An extra filter query to check
* @param opts Optional options object
*/
andFor<Q extends FilterQuery>(query: Q | Q[], opts?: AndOtherwiseOptions<C>): AndPromise<Filter<C, Q>>;
/**
* Filters down the wait call using another hears check. Corresponds with
* {@link Conversation.waitForHears}.
*
* @param trigger An extra text to look for
* @param opts Optional options object
*/
andForHears(trigger: MaybeArray<string | RegExp>, opts?: AndOtherwiseOptions<C>): AndPromise<HearsContext<C>>;
/**
* Filters down the wait call using another command check. Corresponds with
* {@link Conversation.waitForCommand}.
*
* @param command An extra command to look for
* @param opts Optional options object
*/
andForCommand(command: MaybeArray<StringWithCommandSuggestions>, opts?: AndOtherwiseOptions<C>): AndPromise<CommandContext<C>>;
/**
* Filters down the wait call using another reaction check. Corresponds with
* {@link Conversation.waitForReaction}.
*
* @param reaction An extra reaction to look for
* @param opts Optional options object
*/
andForReaction(reaction: MaybeArray<ReactionTypeEmoji["emoji"] | ReactionType>, opts?: AndOtherwiseOptions<C>): AndPromise<ReactionContext<C>>;
/**
* Filters down the wait call using another callback query check.
* Corresponds with {@link Conversation.waitForCallbackQuery}.
*
* @param trigger An extra callback query to look for
* @param opts Optional options object
*/
andForCallbackQuery(trigger: MaybeArray<string | RegExp>, opts?: AndOtherwiseOptions<C>): AndPromise<CallbackQueryContext<C>>;
/**
* Filters down the wait call using another game query check. Corresponds
* with {@link Conversation.waitForGameQuery}.
*
* @param trigger An extra game query to look for
* @param opts Optional options object
*/
andForGameQuery(trigger: MaybeArray<string | RegExp>, opts?: AndOtherwiseOptions<C>): AndPromise<GameQueryContext<C>>;
/**
* Filters down the wait call using another check for a user. Corresponds
* with {@link Conversation.waitFrom}.
*
* @param user An extra user to look for
* @param opts Optional options object
*/
andFrom(user: number | User, opts?: AndOtherwiseOptions<C>): AndPromise<C & {
from: User;
}>;
/**
* Filters down the wait call using another check for a reply. Corresponds
* with {@link Conversation.waitForReplyTo}.
*
* @param message_id An extra message to look for in a reply
* @param opts Optional options object
*/
andForReplyTo(message_id: number | {
message_id: number;
}, opts?: AndOtherwiseOptions<C>): AndPromise<Filter<C, "message" | "channel_post">>;
}
export {};