@gramio/scenes
Version:
Scenes plugin for GramIO
895 lines (886 loc) • 50.4 kB
text/typescript
import * as gramio from 'gramio';
import { Handler, Stringable, Context, Bot, ContextsMapping, MaybePromise, UpdateName, ContextType, CallbackData, ErrorDefinitions, DeriveDefinitions, EventComposer as EventComposer$1, AnyPlugin, MaybeArray, Next as Next$1, Plugin } from 'gramio';
import { Storage } from '@gramio/storage';
import * as _gramio_composer from '@gramio/composer';
import { Next, EventComposer, EventContextOf, DeriveHandler } from '@gramio/composer';
/** The Standard Typed interface. This is a base type extended by other specs. */
interface StandardTypedV1<Input = unknown, Output = Input> {
/** The Standard properties. */
readonly "~standard": StandardTypedV1.Props<Input, Output>;
}
declare namespace StandardTypedV1 {
/** The Standard Typed properties interface. */
interface Props<Input = unknown, Output = Input> {
/** The version number of the standard. */
readonly version: 1;
/** The vendor name of the schema library. */
readonly vendor: string;
/** Inferred types associated with the schema. */
readonly types?: Types<Input, Output> | undefined;
}
/** The Standard Typed types interface. */
interface Types<Input = unknown, Output = Input> {
/** The input type of the schema. */
readonly input: Input;
/** The output type of the schema. */
readonly output: Output;
}
/** Infers the input type of a Standard Typed. */
type InferInput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["input"];
/** Infers the output type of a Standard Typed. */
type InferOutput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["output"];
}
/** The Standard Schema interface. */
interface StandardSchemaV1<Input = unknown, Output = Input> {
/** The Standard Schema properties. */
readonly "~standard": StandardSchemaV1.Props<Input, Output>;
}
declare namespace StandardSchemaV1 {
/** The Standard Schema properties interface. */
interface Props<Input = unknown, Output = Input> extends StandardTypedV1.Props<Input, Output> {
/** Validates unknown input values. */
readonly validate: (value: unknown, options?: StandardSchemaV1.Options | undefined) => Result<Output> | Promise<Result<Output>>;
}
/** The result interface of the validate function. */
type Result<Output> = SuccessResult<Output> | FailureResult;
/** The result interface if validation succeeds. */
interface SuccessResult<Output> {
/** The typed output value. */
readonly value: Output;
/** A falsy value for `issues` indicates success. */
readonly issues?: undefined;
}
interface Options {
/** Explicit support for additional vendor-specific parameters, if needed. */
readonly libraryOptions?: Record<string, unknown> | undefined;
}
/** The result interface if validation fails. */
interface FailureResult {
/** The issues of failed validation. */
readonly issues: ReadonlyArray<Issue>;
}
/** The issue interface of the failure output. */
interface Issue {
/** The error message of the issue. */
readonly message: string;
/** The path of the issue, if any. */
readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined;
}
/** The path segment interface of the issue. */
interface PathSegment {
/** The key representing a path segment. */
readonly key: PropertyKey;
}
/** The Standard types interface. */
interface Types<Input = unknown, Output = Input> extends StandardTypedV1.Types<Input, Output> {
}
/** Infers the input type of a Standard. */
type InferInput<Schema extends StandardTypedV1> = StandardTypedV1.InferInput<Schema>;
/** Infers the output type of a Standard. */
type InferOutput<Schema extends StandardTypedV1> = StandardTypedV1.InferOutput<Schema>;
}
type SceneLifecycleHandler = (ctx: any) => unknown | Promise<unknown>;
/**
* Per-step record stored on a Scene's `~scene.steps` array.
*
* Each step is a sub-composer (StepComposer) plus lifecycle hooks
* registered via `c.enter()` / `c.exit()` / `c.fallback()` / `c.message()`.
*
* `composer` is typed as `unknown` here to avoid a circular import with
* `step-composer.ts`; consumers cast to the concrete StepComposer type at
* the use site.
*/
interface SceneStepEntry {
id: string | number;
composer: unknown;
enter?: Handler<any>;
exit?: Handler<any>;
fallback?: Handler<any>;
/**
* Sugar set by `c.message(text | factory)`. When present and the step's
* `enter` hook is not, the runtime sends this on first entry.
*/
message?: Stringable | ((ctx: any) => Stringable | Promise<Stringable>);
/**
* Event whitelist set by `c.events(["message"])` or step options.
* `undefined` ⇒ default (`"message" | "callback_query"`).
*/
events?: readonly string[];
}
/**
* Scene-specific state that lives alongside the Composer's `~` slot.
* Stored at `scene["~scene"]` to avoid colliding with composer internals
* and to keep augmentation of `@gramio/composer` unnecessary.
*/
interface SceneInternals<Params = unknown, State extends Record<string | number, any> = Record<string | number, any>, ExitData = unknown> {
steps: SceneStepEntry[];
stepsCount: number;
/** scene-level onEnter — single-arg, not middleware */
enter?: SceneLifecycleHandler;
/** scene-level onExit (lands in step 8) */
exit?: SceneLifecycleHandler;
isModule: boolean;
params: Params;
state: State;
exitData: ExitData;
}
type AnyBot$1 = Bot<any, any, any>;
type TelegramEventMap$1 = {
[K in keyof ContextsMapping<AnyBot$1>]: InstanceType<ContextsMapping<AnyBot$1>[K]>;
};
/**
* Base class for `Scene`. Produced by `createComposer` with the gramio method
* table (`.command/.callbackQuery/.hears/.on/.use/.derive/.guard/.branch/.when/
* .extend/...`) so a `Scene` instance has the full bot-level DSL out of the
* box. Scene-specific methods (`.params/.state/.exitData/.onEnter/.onExit/
* .step/.ask`) are added by the `Scene` subclass.
*
* Generic slots are left at default (`{}` / no derive accumulation) — Scene
* tracks its own type chain via the `Derives` generic on the subclass, and
* `.extend()` merges plugin/composer types into that chain.
*/
declare const SceneComposerBase: _gramio_composer.EventComposerConstructor<Context<AnyBot$1>, TelegramEventMap$1, {
reaction<TThis extends _gramio_composer.ComposerLike<TThis> & {
"~": {
macros: gramio.MacroDefinitions;
commandsMeta?: Map<string, unknown>;
Derives?: Record<string, object>;
};
chosenInlineResult(trigger: any, handler: any, macroOptions?: any): TThis;
}>(this: TThis, trigger: gramio.TelegramReactionTypeEmojiEmoji | gramio.TelegramReactionTypeEmojiEmoji[] | readonly gramio.TelegramReactionTypeEmojiEmoji[], handler: (context: gramio.MessageReactionContext<gramio.AnyBot> & _gramio_composer.EventContextOf<TThis, "message_reaction">) => unknown, macroOptions?: Record<string, unknown>): TThis;
callbackQuery<TThis extends _gramio_composer.ComposerLike<TThis> & {
"~": {
macros: gramio.MacroDefinitions;
commandsMeta?: Map<string, unknown>;
Derives?: Record<string, object>;
};
chosenInlineResult(trigger: any, handler: any, macroOptions?: any): TThis;
}, Trigger extends gramio.CallbackData | string | RegExp>(this: TThis, trigger: Trigger, handler: (context: gramio.CallbackQueryContext<gramio.AnyBot> & {
queryData: Trigger extends gramio.CallbackData ? ReturnType<Trigger["unpack"]> : Trigger extends RegExp ? RegExpMatchArray : never;
} & _gramio_composer.EventContextOf<TThis, "callback_query">) => unknown, macroOptions?: Record<string, unknown>): TThis;
chosenInlineResult<TThis extends _gramio_composer.ComposerLike<TThis> & {
"~": {
macros: gramio.MacroDefinitions;
commandsMeta?: Map<string, unknown>;
Derives?: Record<string, object>;
};
chosenInlineResult(trigger: any, handler: any, macroOptions?: any): TThis;
}>(this: TThis, trigger: RegExp | string | ((context: gramio.ChosenInlineResultContext<gramio.AnyBot>) => boolean), handler: (context: gramio.ChosenInlineResultContext<gramio.AnyBot> & {
args: RegExpMatchArray | null;
} & _gramio_composer.EventContextOf<TThis, "chosen_inline_result">) => unknown, macroOptions?: Record<string, unknown>): TThis;
inlineQuery<TThis extends _gramio_composer.ComposerLike<TThis> & {
"~": {
macros: gramio.MacroDefinitions;
commandsMeta?: Map<string, unknown>;
Derives?: Record<string, object>;
};
chosenInlineResult(trigger: any, handler: any, macroOptions?: any): TThis;
}>(this: TThis, triggerOrHandler: RegExp | string | ((context: gramio.InlineQueryContext<gramio.AnyBot>) => boolean) | ((context: gramio.InlineQueryContext<gramio.AnyBot> & {
args: RegExpMatchArray | null;
} & _gramio_composer.EventContextOf<TThis, "inline_query">) => unknown), maybeHandler?: (context: gramio.InlineQueryContext<gramio.AnyBot> & {
args: RegExpMatchArray | null;
} & _gramio_composer.EventContextOf<TThis, "inline_query">) => unknown, options?: {
onResult?: (context: gramio.ChosenInlineResultContext<gramio.AnyBot> & {
args: RegExpMatchArray | null;
} & _gramio_composer.EventContextOf<TThis, "chosen_inline_result">) => unknown;
} & Record<string, unknown>): TThis;
guestQuery<TThis extends _gramio_composer.ComposerLike<TThis> & {
"~": {
macros: gramio.MacroDefinitions;
commandsMeta?: Map<string, unknown>;
Derives?: Record<string, object>;
};
chosenInlineResult(trigger: any, handler: any, macroOptions?: any): TThis;
}>(this: TThis, triggerOrHandler: RegExp | string | ((context: gramio.MessageContext<gramio.AnyBot> & gramio.Require<gramio.MessageContext<gramio.AnyBot>, "from">) => boolean) | ((context: (gramio.MessageContext<gramio.AnyBot> & gramio.Require<gramio.MessageContext<gramio.AnyBot>, "from">) & {
args: RegExpMatchArray | null;
} & _gramio_composer.EventContextOf<TThis, "guest_message">) => unknown), maybeHandler?: (context: (gramio.MessageContext<gramio.AnyBot> & gramio.Require<gramio.MessageContext<gramio.AnyBot>, "from">) & {
args: RegExpMatchArray | null;
} & _gramio_composer.EventContextOf<TThis, "guest_message">) => unknown, macroOptions?: Record<string, unknown>): TThis;
hears<TThis extends _gramio_composer.ComposerLike<TThis> & {
"~": {
macros: gramio.MacroDefinitions;
commandsMeta?: Map<string, unknown>;
Derives?: Record<string, object>;
};
chosenInlineResult(trigger: any, handler: any, macroOptions?: any): TThis;
}>(this: TThis, trigger: RegExp | (string | string[] | readonly string[]) | ((context: gramio.MessageContext<gramio.AnyBot> & gramio.Require<gramio.MessageContext<gramio.AnyBot>, "from">) => boolean), handler: (context: (gramio.MessageContext<gramio.AnyBot> & gramio.Require<gramio.MessageContext<gramio.AnyBot>, "from">) & {
args: RegExpMatchArray | null;
} & _gramio_composer.EventContextOf<TThis, "message">) => unknown, macroOptions?: Record<string, unknown>): TThis;
command<TThis extends _gramio_composer.ComposerLike<TThis> & {
"~": {
macros: gramio.MacroDefinitions;
commandsMeta?: Map<string, unknown>;
Derives?: Record<string, object>;
};
chosenInlineResult(trigger: any, handler: any, macroOptions?: any): TThis;
}>(this: TThis, command: string | string[] | readonly string[], handlerOrMeta: ((context: (gramio.MessageContext<gramio.AnyBot> & gramio.Require<gramio.MessageContext<gramio.AnyBot>, "from">) & {
args: string | null;
} & _gramio_composer.EventContextOf<TThis, "message">) => unknown) | gramio.CommandMeta, handlerOrOptions?: ((context: (gramio.MessageContext<gramio.AnyBot> & gramio.Require<gramio.MessageContext<gramio.AnyBot>, "from">) & {
args: string | null;
} & _gramio_composer.EventContextOf<TThis, "message">) => unknown) | Record<string, unknown>, macroOptions?: Record<string, unknown>): TThis;
startParameter<TThis extends _gramio_composer.ComposerLike<TThis> & {
"~": {
macros: gramio.MacroDefinitions;
commandsMeta?: Map<string, unknown>;
Derives?: Record<string, object>;
};
chosenInlineResult(trigger: any, handler: any, macroOptions?: any): TThis;
}>(this: TThis, parameter: RegExp | (string | string[] | readonly string[]), handler: gramio.Handler<(gramio.MessageContext<gramio.AnyBot> & gramio.Require<gramio.MessageContext<gramio.AnyBot>, "from">) & {
rawStartPayload: string;
} & _gramio_composer.EventContextOf<TThis, "message">>, macroOptions?: Record<string, unknown>): TThis;
}>;
type Modify<Base, Mod> = Omit<Base, keyof Mod> & Mod;
type StateTypesDefault = Record<string | number, any>;
type UpdateData<T extends StateTypesDefault> = {};
type ScenesStorage = Storage<Record<`@gramio/scenes:${string | number}`, ScenesStorageData<unknown, unknown>>>;
interface ScenesOptions {
storage?: ScenesStorage;
/**
* Controls what happens to updates that arrive while the user is inside
* a scene but do not match the current step (wrong update type, or no
* step handler claims them).
*
* - `true` (default): non-matching updates fall through to the outer bot
* chain, so global handlers like `.command("cancel")` or `.on("message")`
* can still react. The scene's `firstTime` flag is preserved so the user
* does not lose their place.
* - `false`: scenes greedily consume every update for the active user.
* Legacy behavior — useful if you intentionally want to isolate the user
* from outer handlers while a scene is active.
*
* @default true
*/
passthrough?: boolean;
}
interface ParentSceneFrame {
name: string;
params: unknown;
state: unknown;
stepId: string | number;
previousStepId: string | number;
parentStack?: ParentSceneFrame[];
}
interface ScenesStorageData<Params = any, State = any> {
name: string;
params: Params;
state: State;
stepId: string | number;
previousStepId: string | number;
firstTime: boolean;
parentStack?: ParentSceneFrame[];
/**
* `true` once `scene.onEnter` has fired for this occupancy of the scene.
* Used to distinguish "scene first entry" (fire onEnter) from "step
* transition with firstTime=true" (don't re-fire onEnter). Defaults to
* `false` on initial entry and is set `true` by dispatchActive after
* onEnter runs.
*/
entered?: boolean;
}
interface SceneUpdateState {
/**
* @default sceneData.stepId + 1
*/
step?: string | number;
firstTime?: boolean;
}
/**
* Extracts the Params generic from a Scene type. Reads from the SCENE
* GENERIC, not from the runtime `~scene.params` carrier (which is typed
* `unknown` at the class field level and never round-trips the user's
* type back out). This is what lets `enter(scene)` reject a missing
* params arg and enforce the declared shape when one is required.
*/
type SceneParamsOf<S> = S extends Scene<infer P, any, any, any> ? P : never;
/**
* `enter(scene, params?)` typed via two overloads so each case is checked
* cleanly without relying on a conditional-rest-args dance (which expect-
* type's `toBeCallableWith` can't fully resolve under generic constraints):
*
* • If the Scene declares `Params = never` (never called `.params<T>()`),
* `enter(scene)` is valid with no second argument.
* • If the Scene declares params, `enter(scene, params)` is required and
* the params shape is enforced against the declared type.
*/
interface SceneEnterHandler {
<S extends Scene<never, any, any, any>>(scene: S): Promise<void>;
<S extends AnyScene>(scene: S, params: SceneParamsOf<S>): Promise<void>;
}
interface EnterExit {
enter: SceneEnterHandler;
exit: () => MaybePromise<boolean>;
}
type SceneStepReturn = {
id: string | number;
previousId: string | number;
firstTime: boolean;
go: (stepId: string | number, firstTime?: boolean) => Promise<void>;
next: () => Promise<void>;
previous: () => Promise<void>;
};
interface InActiveSceneHandlerReturn<Params, State extends StateTypesDefault, ExitData extends Record<string, unknown> = Record<string, unknown>> extends EnterExit {
state: State;
params: Params;
update: <T extends StateTypesDefault>(state: T, options?: SceneUpdateState) => Promise<UpdateData<T>>;
step: SceneStepReturn;
reenter: (params?: Params) => Promise<void>;
enterSub: SceneEnterHandler;
exitSub: (returnData?: ExitData) => Promise<void>;
}
interface InUnknownScene<Params, State extends StateTypesDefault, GlobalScene extends AnyScene | null = null> extends InActiveSceneHandlerReturn<Params, State> {
is<Scene extends AnyScene>(scene: Scene): this is InUnknownScene<Scene["~scene"]["params"], Scene["~scene"]["state"], Scene>;
}
interface PossibleInUnknownScene<Params, State extends StateTypesDefault, Scene extends AnyScene | null = null> extends EnterExit {
current: Scene extends AnyScene ? InActiveSceneHandlerReturn<Scene["~scene"]["params"], Partial<Scene["~scene"]["state"]>> : InUnknownScene<Params, State, Scene> | undefined;
}
type AnyBot = Bot<any, any, any>;
type TelegramEventMap = {
[K in keyof ContextsMapping<AnyBot>]: InstanceType<ContextsMapping<AnyBot>[K]>;
};
/**
* Default event union for step builders. Mirrors the union .ask() uses today
* (scene.ts:302) — most scene steps interact via text input or button presses.
* Both contexts have `.send()`, so `c.enter(ctx => ctx.send("…"))` typechecks.
*
* Narrow with `c.events([...])` (chained) or `step("name", { events: [...] }, ...)`
* (per-step option) when a step only accepts a subset.
*/
type DefaultStepEvents = "message" | "callback_query";
/**
* Step builder context for the default event union (message + callback_query),
* merged with anything `this` has accumulated (scene-level derives, step-level
* derives) via `ContextOf<TThis>` / `EventContextOf<TThis, E>`.
*
* `EventContextOf` picks up both the global TOut AND per-event TDerives, so
* derives registered with `.derive("message", ...)` are visible too.
*
* Both default-union contexts have `.send`, `.api`, `.from`, `.chat` — the
* common scene surface.
*/
type StepCtx<TThis, E extends UpdateName = DefaultStepEvents> = ContextType<AnyBot, E> & EventContextOf<TThis, E>;
declare const stepMethods: {
/**
* Runs once when the user lands on this step (`firstTime === true`).
* Replaces the `if (context.scene.step.firstTime) return ctx.send(...)`
* boilerplate from the legacy step API.
*/
enter<TThis, E extends UpdateName = DefaultStepEvents>(this: TThis, handler: (ctx: StepCtx<TThis, E>, next: Next) => unknown): TThis;
/**
* Runs when the user leaves this step (next/previous/go from inside it,
* or scene.exit/reenter while it was current). Per-step counterpart to
* scene.onExit. Useful for cleanup and analytics.
*/
exit<TThis, E extends UpdateName = DefaultStepEvents>(this: TThis, handler: (ctx: StepCtx<TThis, E>, next: Next) => unknown): TThis;
/**
* Catch-all for events that didn't match any `.command/.on/.callbackQuery/
* .hears/.reaction` handler inside this step. Alternative to a final
* wildcard `.on(events, ...)`.
*/
fallback<TThis, E extends UpdateName = DefaultStepEvents>(this: TThis, handler: (ctx: StepCtx<TThis, E>, next: Next) => unknown): TThis;
/**
* Sugar over `.enter(ctx => ctx.send(text))`. Accepts a literal Stringable
* or a factory that receives the entry context.
*/
message<TThis, E extends UpdateName = DefaultStepEvents>(this: TThis, text: Stringable | ((ctx: StepCtx<TThis, E>) => Stringable | Promise<Stringable>)): TThis;
/**
* Narrow the event whitelist for this step. Defaults to message +
* callback_query if not called. Mirrors `.on()`'s array-or-single shape.
*
* Type-only: returns `this` unchanged at the type level for v0. Manually
* annotate ctx if you need narrower types in lifecycle handlers. Full
* type narrowing is a follow-up.
*/
events<TThis, E extends UpdateName>(this: TThis, events: E | readonly E[]): TThis;
/**
* Type-only declaration of the state shape this step contributes. No-op at
* runtime — exists so builder steps can opt into state inference until the
* automatic builder→state inference lands in a follow-up.
*
* @example
* c.updates<{ name: string }>().on("message", ctx => ctx.scene.update({ name: ctx.text! }))
*/
/**
* @returns the same composer instance, with no type change. TThis is
* inferred from the binding; if you call it as `c.updates<T>()`, the
* return is typed as `c`.
*/
updates<_T, TThis = unknown>(this: TThis): TThis;
reaction<TThis extends _gramio_composer.ComposerLike<TThis> & {
"~": {
macros: gramio.MacroDefinitions;
commandsMeta?: Map<string, unknown>;
Derives?: Record<string, object>;
};
chosenInlineResult(trigger: any, handler: any, macroOptions?: any): TThis;
}>(this: TThis, trigger: gramio.TelegramReactionTypeEmojiEmoji | gramio.TelegramReactionTypeEmojiEmoji[] | readonly gramio.TelegramReactionTypeEmojiEmoji[], handler: (context: gramio.MessageReactionContext<gramio.AnyBot> & EventContextOf<TThis, "message_reaction">) => unknown, macroOptions?: Record<string, unknown>): TThis;
callbackQuery<TThis extends _gramio_composer.ComposerLike<TThis> & {
"~": {
macros: gramio.MacroDefinitions;
commandsMeta?: Map<string, unknown>;
Derives?: Record<string, object>;
};
chosenInlineResult(trigger: any, handler: any, macroOptions?: any): TThis;
}, Trigger extends CallbackData | string | RegExp>(this: TThis, trigger: Trigger, handler: (context: gramio.CallbackQueryContext<gramio.AnyBot> & {
queryData: Trigger extends CallbackData ? ReturnType<Trigger["unpack"]> : Trigger extends RegExp ? RegExpMatchArray : never;
} & EventContextOf<TThis, "callback_query">) => unknown, macroOptions?: Record<string, unknown>): TThis;
chosenInlineResult<TThis extends _gramio_composer.ComposerLike<TThis> & {
"~": {
macros: gramio.MacroDefinitions;
commandsMeta?: Map<string, unknown>;
Derives?: Record<string, object>;
};
chosenInlineResult(trigger: any, handler: any, macroOptions?: any): TThis;
}>(this: TThis, trigger: RegExp | string | ((context: gramio.ChosenInlineResultContext<gramio.AnyBot>) => boolean), handler: (context: gramio.ChosenInlineResultContext<gramio.AnyBot> & {
args: RegExpMatchArray | null;
} & EventContextOf<TThis, "chosen_inline_result">) => unknown, macroOptions?: Record<string, unknown>): TThis;
inlineQuery<TThis extends _gramio_composer.ComposerLike<TThis> & {
"~": {
macros: gramio.MacroDefinitions;
commandsMeta?: Map<string, unknown>;
Derives?: Record<string, object>;
};
chosenInlineResult(trigger: any, handler: any, macroOptions?: any): TThis;
}>(this: TThis, triggerOrHandler: RegExp | string | ((context: gramio.InlineQueryContext<gramio.AnyBot>) => boolean) | ((context: gramio.InlineQueryContext<gramio.AnyBot> & {
args: RegExpMatchArray | null;
} & EventContextOf<TThis, "inline_query">) => unknown), maybeHandler?: (context: gramio.InlineQueryContext<gramio.AnyBot> & {
args: RegExpMatchArray | null;
} & EventContextOf<TThis, "inline_query">) => unknown, options?: {
onResult?: (context: gramio.ChosenInlineResultContext<gramio.AnyBot> & {
args: RegExpMatchArray | null;
} & EventContextOf<TThis, "chosen_inline_result">) => unknown;
} & Record<string, unknown>): TThis;
guestQuery<TThis extends _gramio_composer.ComposerLike<TThis> & {
"~": {
macros: gramio.MacroDefinitions;
commandsMeta?: Map<string, unknown>;
Derives?: Record<string, object>;
};
chosenInlineResult(trigger: any, handler: any, macroOptions?: any): TThis;
}>(this: TThis, triggerOrHandler: RegExp | string | ((context: gramio.MessageContext<gramio.AnyBot> & gramio.Require<gramio.MessageContext<gramio.AnyBot>, "from">) => boolean) | ((context: (gramio.MessageContext<gramio.AnyBot> & gramio.Require<gramio.MessageContext<gramio.AnyBot>, "from">) & {
args: RegExpMatchArray | null;
} & EventContextOf<TThis, "guest_message">) => unknown), maybeHandler?: (context: (gramio.MessageContext<gramio.AnyBot> & gramio.Require<gramio.MessageContext<gramio.AnyBot>, "from">) & {
args: RegExpMatchArray | null;
} & EventContextOf<TThis, "guest_message">) => unknown, macroOptions?: Record<string, unknown>): TThis;
hears<TThis extends _gramio_composer.ComposerLike<TThis> & {
"~": {
macros: gramio.MacroDefinitions;
commandsMeta?: Map<string, unknown>;
Derives?: Record<string, object>;
};
chosenInlineResult(trigger: any, handler: any, macroOptions?: any): TThis;
}>(this: TThis, trigger: RegExp | (string | string[] | readonly string[]) | ((context: gramio.MessageContext<gramio.AnyBot> & gramio.Require<gramio.MessageContext<gramio.AnyBot>, "from">) => boolean), handler: (context: (gramio.MessageContext<gramio.AnyBot> & gramio.Require<gramio.MessageContext<gramio.AnyBot>, "from">) & {
args: RegExpMatchArray | null;
} & EventContextOf<TThis, "message">) => unknown, macroOptions?: Record<string, unknown>): TThis;
command<TThis extends _gramio_composer.ComposerLike<TThis> & {
"~": {
macros: gramio.MacroDefinitions;
commandsMeta?: Map<string, unknown>;
Derives?: Record<string, object>;
};
chosenInlineResult(trigger: any, handler: any, macroOptions?: any): TThis;
}>(this: TThis, command: string | string[] | readonly string[], handlerOrMeta: ((context: (gramio.MessageContext<gramio.AnyBot> & gramio.Require<gramio.MessageContext<gramio.AnyBot>, "from">) & {
args: string | null;
} & EventContextOf<TThis, "message">) => unknown) | gramio.CommandMeta, handlerOrOptions?: ((context: (gramio.MessageContext<gramio.AnyBot> & gramio.Require<gramio.MessageContext<gramio.AnyBot>, "from">) & {
args: string | null;
} & EventContextOf<TThis, "message">) => unknown) | Record<string, unknown>, macroOptions?: Record<string, unknown>): TThis;
startParameter<TThis extends _gramio_composer.ComposerLike<TThis> & {
"~": {
macros: gramio.MacroDefinitions;
commandsMeta?: Map<string, unknown>;
Derives?: Record<string, object>;
};
chosenInlineResult(trigger: any, handler: any, macroOptions?: any): TThis;
}>(this: TThis, parameter: RegExp | (string | string[] | readonly string[]), handler: Handler<(gramio.MessageContext<gramio.AnyBot> & gramio.Require<gramio.MessageContext<gramio.AnyBot>, "from">) & {
rawStartPayload: string;
} & EventContextOf<TThis, "message">>, macroOptions?: Record<string, unknown>): TThis;
};
/**
* StepComposer instance pre-seeded with the parent Scene's derives in TOut.
*
* This is the type you want at `c => c…` callsites — it carries `ctx.scene`,
* any scene-level `.derive(...)`-injected fields, and any `.extend(plugin)`
* derives all the way into the step's `.enter / .on / .command / ...`
* handlers. Without this threading, step ctx is plain
* `MessageContext | CallbackQueryContext` and `ctx.scene.update(...)` /
* `ctx.scene.exit()` would not type-check.
*
* The Scene's Derives generic looks like:
* `{ global: { scene: ... } & UserDerives; message: ...; callback_query: ... }`
*
* We pull `Derives["global"]` into TOut so it's visible on every step ctx,
* and pull the per-event slots into TDerives so `.on("message", ...)` /
* `.command(...)` handlers receive the right narrowed shape too.
*
* Generic order: the parent scene's `Derives` is what we need; we accept
* the whole Scene type and project just that slot to keep callers from
* having to extract by hand.
*/
type StepComposerFor<TSceneDerives extends {
global: object;
} = {
global: {};
}, AccState extends object = {}> = StepComposerStateTracked<EventComposer<Context<AnyBot>, TelegramEventMap, Context<AnyBot>, Context<AnyBot> & TSceneDerives["global"], {}, Omit<TSceneDerives, "global"> extends Record<string, object> ? Omit<TSceneDerives, "global"> : {}, typeof stepMethods> & typeof stepMethods, TSceneDerives, AccState>;
/**
* Extracts the state contribution from a handler's awaited return type.
*
* `ctx.scene.update({ k: v })` returns `Promise<UpdateData<{ k: v }>>`, so
* `update({k:v})` returns are picked up automatically. Returning
* `void`/`undefined`/`Promise<void>` from a handler contributes nothing
* (`{}`), so handlers that only send messages don't pollute the state.
*
* Mirrors `Awaited<ReturnType<Handler>>` extraction already done by the
* legacy `step(event, handler)` overload in scene.ts — this generalises
* the same trick to every event-handler method on the step builder.
*/
type ExtractUpdateState<R> = Awaited<R> extends UpdateData<infer T> ? T : {};
/**
* Re-typed view of a step composer where each event-handler method
* (`.on / .command / .callbackQuery / .hears / .enter / .exit / .fallback`)
* accumulates `Awaited<ReturnType<H>>` into a phantom `AccState` generic.
*
* The accumulated state is what `Scene.step(...)` reads off the builder's
* return type to widen the Scene's `State` generic — so that
* `ctx.scene.state.X` is properly typed in subsequent step handlers
* without any `.state<T>()` annotation.
*
* Conceptually: `c.on("message", ctx => ctx.scene.update({ name: ctx.text }))`
* threads `{ name: string }` into the step's `AccState`; on the next step's
* `ctx.scene.state` you see `{ name: string }` typed in.
*/
/**
* Mirror of gramio's `EventContextOf<TThis, E>` projected against a Scene's
* `Derives` slot — merges the scene-level global derives plus any per-event
* derives registered via `scene.derive("<event>", ...)` so that step handlers
* see the same shape gramio's bot-level handlers would.
*
* `Derives` slots default to `{}` (see `DeriveDefinitions` in gramio), so the
* `keyof` conditional safely degrades when an event has no entry.
*/
type StepEventCtx<E extends UpdateName, TSceneDerives extends {
global: object;
}> = ContextType<AnyBot, E> & TSceneDerives["global"] & (E extends keyof TSceneDerives ? TSceneDerives[E] : {});
type StepComposerStateTracked<TBase, TSceneDerives extends {
global: object;
}, AccState extends object> = Omit<TBase, "on" | "command" | "callbackQuery" | "hears" | "enter" | "exit" | "fallback"> & {
on<E extends UpdateName, H extends (ctx: StepEventCtx<E, TSceneDerives>, next: Next) => unknown>(event: E | readonly E[], handler: H): StepComposerStateTracked<TBase, TSceneDerives, AccState & ExtractUpdateState<ReturnType<H>>>;
command<H extends (ctx: StepEventCtx<"message", TSceneDerives> & {
args: string | null;
}) => unknown>(name: string | readonly string[], handler: H): StepComposerStateTracked<TBase, TSceneDerives, AccState & ExtractUpdateState<ReturnType<H>>>;
callbackQuery<Trigger extends CallbackData | string | RegExp, H extends (ctx: StepEventCtx<"callback_query", TSceneDerives> & {
queryData: Trigger extends CallbackData ? ReturnType<Trigger["unpack"]> : Trigger extends RegExp ? RegExpMatchArray : never;
}) => unknown>(trigger: Trigger, handler: H): StepComposerStateTracked<TBase, TSceneDerives, AccState & ExtractUpdateState<ReturnType<H>>>;
hears<H extends (ctx: StepEventCtx<"message", TSceneDerives> & {
args: RegExpMatchArray | null;
}) => unknown>(trigger: RegExp | string | readonly string[] | ((ctx: ContextType<AnyBot, "message">) => boolean), handler: H): StepComposerStateTracked<TBase, TSceneDerives, AccState & ExtractUpdateState<ReturnType<H>>>;
enter<E extends UpdateName = DefaultStepEvents, H extends (ctx: StepEventCtx<E, TSceneDerives>, next: Next) => unknown = (ctx: StepEventCtx<E, TSceneDerives>, next: Next) => unknown>(handler: H): StepComposerStateTracked<TBase, TSceneDerives, AccState & ExtractUpdateState<ReturnType<H>>>;
exit<E extends UpdateName = DefaultStepEvents, H extends (ctx: StepEventCtx<E, TSceneDerives>, next: Next) => unknown = (ctx: StepEventCtx<E, TSceneDerives>, next: Next) => unknown>(handler: H): StepComposerStateTracked<TBase, TSceneDerives, AccState & ExtractUpdateState<ReturnType<H>>>;
fallback<E extends UpdateName = DefaultStepEvents, H extends (ctx: StepEventCtx<E, TSceneDerives>, next: Next) => unknown = (ctx: StepEventCtx<E, TSceneDerives>, next: Next) => unknown>(handler: H): StepComposerStateTracked<TBase, TSceneDerives, AccState & ExtractUpdateState<ReturnType<H>>>;
};
/** Helper: extract the accumulated state generic from a tracked step composer. */
type ExtractStepState<T> = T extends StepComposerStateTracked<any, any, infer S> ? S : {};
type ContextWithFrom = Pick<ContextType<Bot, "message" | "callback_query">, "from">;
declare function getInActiveSceneHandler<Params, State extends StateTypesDefault, ExitData extends Record<string, unknown> = Record<string, unknown>>(context: ContextWithFrom & {
scene: InActiveSceneHandlerReturn<any, any>;
}, storage: Storage, sceneData: ScenesStorageData<Params, State>, scene: AnyScene, key: `@gramio/scenes:${string | number}`, allowedScenes: string[], allScenes: AnyScene[]): InActiveSceneHandlerReturn<Params, State, ExitData>;
type AnyScene = Scene<any, any, any, any>;
type StepHandler<T, Return = any> = (context: T, next: Next$1) => any;
type SceneDerivesDefinitions<Params, State extends StateTypesDefault, ExitData extends Record<string, unknown> = Record<string, unknown>> = DeriveDefinitions & {
global: {
scene: ReturnType<typeof getInActiveSceneHandler<Params, State, ExitData>>;
};
};
/**
* Scene IS an EventComposer. Inherits the full gramio DSL
* (`.command/.callbackQuery/.hears/.on/.use/.derive/.guard/.branch/.extend/...`)
* and adds scene-specific methods (`.params/.state/.exitData/.onEnter/.step/
* .ask`). Scene-specific data lives on `this["~scene"]` to avoid colliding
* with the composer's own `~` slot.
*/
declare class Scene<Params = never, Errors extends ErrorDefinitions = {}, State extends StateTypesDefault = Record<string, never>, Derives extends SceneDerivesDefinitions<Params, State, any> = SceneDerivesDefinitions<Params, State>> extends SceneComposerBase {
name: string;
stepsCount: number;
/**
* Override of the inherited composer's `~` slot to widen its `Out`
* (phantom TOut carrier) to include the scene's `Derives["global"]`.
* This is what makes `ctx.scene` visible inside scene-level event
* handlers (`scene.callbackQuery / .command / .hears / .on / …`):
* those methods type `ctx` via `EventContextOf<this, E>`, which reads
* `Out` from this slot. With the widening, `ctx.scene` is now present
* everywhere — both at the scene level AND inside step builders.
*/
"~": InstanceType<typeof SceneComposerBase>["~"] & {
Out: InstanceType<typeof SceneComposerBase>["~"]["Out"] & Derives["global"];
};
/** @internal — scene-specific state. Stored on a dedicated slot so the
* composer's own `~` slot remains untouched. Generics here propagate
* Scene's `Params` / `State` into the structural shape so that
* `Scene<{id}, ...>` is distinct from `Scene<never, ...>` at the type
* level (needed by `SceneEnterHandler` to differentiate the no-params
* and with-params overloads at call sites). */
"~scene": SceneInternals<Params, State>;
constructor(name?: string);
params<SceneParams>(): Scene<SceneParams, Errors, State, Modify<Derives, {
global: {
scene: Modify<Derives["global"]["scene"], {
params: SceneParams;
reenter: (params?: SceneParams) => Promise<void>;
}>;
};
}>>;
state<StateParams extends StateTypesDefault>(): Scene<Params, Errors, StateParams, Modify<Derives, {
global: Modify<Derives["global"], {
scene: Modify<Derives["global"]["scene"], {
state: StateParams;
}>;
}>;
}>>;
exitData<ExitData extends Record<string, unknown>>(): Scene<Params, Errors, State, Modify<Derives, {
global: Modify<Derives["global"], {
scene: Modify<Derives["global"]["scene"], {
exitSub: (returnData?: ExitData) => Promise<void>;
}>;
}>;
}>>;
/** Merge another Scene's middlewares + lifecycle hooks + step list. */
extend<UParams, UErrors extends ErrorDefinitions, UState extends StateTypesDefault, UDerives extends SceneDerivesDefinitions<UParams, UState, any>>(scene: Scene<UParams, UErrors, UState, UDerives>): Scene<Params, Errors & UErrors, Record<string, never> extends State ? UState : Record<string, never> extends UState ? State : State & UState, Derives & UDerives>;
extend<UExposed extends object, UDerives extends Record<string, object>>(composer: EventComposer$1<any, any, any, any, UExposed, UDerives, any>): Scene<Params, Errors, State, Derives & {
global: UExposed;
} & UDerives>;
extend<NewPlugin extends AnyPlugin>(plugin: NewPlugin): Scene<Params, Errors & NewPlugin["_"]["Errors"], State, Derives & NewPlugin["_"]["Derives"]>;
derive<D extends object>(handler: DeriveHandler<ContextType<Bot, "message"> & Derives["global"], D>): Scene<Params, Errors, State, Modify<Derives, {
global: Derives["global"] & D;
}>>;
derive<D extends object>(handler: DeriveHandler<ContextType<Bot, "message"> & Derives["global"], D>, options: {
as: "scoped" | "global";
}): Scene<Params, Errors, State, Modify<Derives, {
global: Derives["global"] & D;
}>>;
derive<E extends UpdateName, D extends object>(event: MaybeArray<E>, handler: DeriveHandler<ContextType<Bot, E> & Derives["global"], D>): Scene<Params, Errors, State, Derives & {
[K in E]: D;
}>;
/**
* Override of `.decorate` that preserves Scene<...>. Same reason as
* `.derive` above — the base method widens TOut and drops the subclass.
*/
decorate<D extends object>(values: D): Scene<Params, Errors, State, Modify<Derives, {
global: Derives["global"] & D;
}>>;
decorate<D extends object>(values: D, options: {
as: "scoped" | "global";
}): Scene<Params, Errors, State, Modify<Derives, {
global: Derives["global"] & D;
}>>;
/**
* Register a handler that runs once when the user enters the scene.
*
* Fires AFTER scene-level `.derive()` / `.decorate()` middleware has
* applied — so derived ctx fields (`ctx.user`, etc.) ARE available. Fires
* exactly once per scene occupancy: `step.go(...)` transitions don't
* re-trigger it.
*
* @example
* new Scene("checkout")
* .derive(async ctx => ({ user: await db.users.find(ctx.from!.id) }))
* .onEnter(ctx => analytics.track("checkout_start", { userId: ctx.user.id }))
* .step("review", c => c.message("Order looks good?").on("message", ...))
*/
onEnter(handler: (context: ContextType<Bot, "message"> & Derives["global"]) => unknown): this;
/**
* Register a handler that runs when the user leaves this scene — on
* `ctx.scene.exit()`, `ctx.scene.exitSub()` (the sub-scene exits), and
* `ctx.scene.reenter()` (the prior occupancy of this scene ends before
* re-entry). Symmetric to `.onEnter`. Useful for cleanup, analytics,
* "thanks for completing" messages.
*/
onExit(handler: (context: ContextType<Bot, "message"> & Derives["global"]) => unknown): this;
/**
* Builder, numeric step id (autoincrement).
*
* The builder's return type is inspected for any `Awaited<ReturnType<H>>`
* that contains `UpdateData<T>` — i.e., any handler returning
* `ctx.scene.update({…})`. Those T's are merged into Scene's `State`
* generic automatically, so subsequent steps see `ctx.scene.state.X`
* properly typed without any `.state<T>()` annotation.
*/
step<B extends (c: StepComposerFor<Derives>) => unknown, StepState extends object = ExtractStepState<ReturnType<B>>>(builder: B): Scene<Params, Errors, Record<string, never> extends State ? StepState : State & StepState, Modify<Derives, {
global: Modify<Derives["global"], {
scene: Modify<Derives["global"]["scene"], {
state: Record<string, never> extends State ? StepState : State & StepState;
}>;
}>;
}>>;
/**
* Legacy event-filtered step (single event name OR an array of events).
*
* Listed BEFORE the named-builder overload so TS's overload resolution
* tries this first. `T extends UpdateName` then either succeeds (real
* event name like `"message"`) and types `ctx` properly, OR fails so TS
* falls through to the named-builder overload. Result: `step("message",
* (ctx, next) => …)` types `ctx` as `MessageContext`, while
* `step("intro", (c) => …)` (with a name that's NOT a known event)
* cleanly resolves to the named-builder overload.
*/
step<T extends UpdateName, Handler extends StepHandler<ContextType<Bot, T> & Derives["global"] & Derives[T], any>, Return = Awaited<ReturnType<Handler>>>(updateName: MaybeArray<T>, handler: Handler): Scene<Params, Errors, Extract<Return, UpdateData<any>> extends UpdateData<infer Type> ? Record<string, never> extends State ? Type : State & Type : State, Modify<Derives, {
global: Modify<Derives["global"], {
scene: Modify<Derives["global"]["scene"], {
state: Extract<Return, UpdateData<any>> extends UpdateData<infer Type> ? Record<string, never> extends State ? Type : State & Type : State;
}>;
}>;
}>>;
/**
* Builder, named step id. Same state-inference behavior as the numeric
* form — any `update({…})` calls inside handlers widen `State`.
*/
step<B extends (c: StepComposerFor<Derives>) => unknown, StepState extends object = ExtractStepState<ReturnType<B>>>(name: string, builder: B): Scene<Params, Errors, Record<string, never> extends State ? StepState : State & StepState, Modify<Derives, {
global: Modify<Derives["global"], {
scene: Modify<Derives["global"]["scene"], {
state: Record<string, never> extends State ? StepState : State & StepState;
}>;
}>;
}>>;
/** @internal Register a builder-style step: creates a fresh StepComposer,
* runs the user's builder against it, and stores the entry on `~scene.steps`. */
private _registerBuilderStep;
/** @internal Register a legacy event-filtered step as a gated `.use()`
* middleware. Preserved for back-compat with the original `.step("message", ctx => ...)` API. */
private _registerLegacyEventStep;
ask<Key extends string, Schema extends StandardSchemaV1, Return extends StateTypesDefault = {
[key in Key]: StandardSchemaV1.InferOutput<Schema>;
}>(key: Key, validator: Schema, firstTimeMessage: Stringable, options?: {
/** Custom message when validation fails. Receives all issues from the validator. */
onInvalidInput?: (issues: readonly StandardSchemaV1.Issue[]) => Stringable;
}): Scene<Params, Errors, Record<string, never> extends State ? Return : State & Return, Modify<Derives, {
global: Modify<Derives["global"], {
scene: Modify<Derives["global"]["scene"], {
state: Record<string, never> extends State ? Return : State & Return;
}>;
}>;
}>>;
dispatch(context: Context<Bot> & {
[key: string]: unknown;
}, onNext?: () => unknown, passthrough?: Next$1,
/**
* Middleware fns that have already run for this update (e.g. derive/
* decorate pre-run so `onEnter` could see them in the legacy path) and
* must NOT run again here. They `Object.assign` onto the live ctx, so
* their effect persists — re-running would only re-fire side effects and
* double-count. Filtering by fn identity skips exactly those.
*/
skipFns?: ReadonlySet<unknown>): Promise<void>;
/**
* @internal Drop middlewares whose `plugin` is already in the bot's
* extended-set (`bot.extend(withUser)`) so a named plugin/composer shared
* between the bot chain and a scene runs once per update, not twice. Used by
* `dispatch` and by the onEnter setup pre-runs in `dispatchActive`.
*/
private _dedupeAgainstBot;
dispatchActive(context: Context<Bot> & {
[key: string]: unknown;
}, storage: Storage, key: string, data: ScenesStorageData<unknown, unknown>, passthrough?: Next$1): Promise<void>;
}
interface ScenesDerivesOptions<WithCurrentScene extends boolean = false> extends ScenesOptions {
withCurrentScene?: WithCurrentScene;
scenes?: AnyScene[];
/**
* You should use the same storage for scenes and scenesDerives
*/
storage: Storage;
}
declare function scenesDerives<WithCurrentScene extends boolean = false>(scenesOrOptions: AnyScene[] | ScenesDerivesOptions<WithCurrentScene>, optionsRaw?: ScenesDerivesOptions<WithCurrentScene>): Plugin<{}, gramio.DeriveDefinitions & {
message: {
scene: WithCurrentScene extends true ? PossibleInUnknownScene<any, any> : EnterExit;
};
channel_post: {
scene: WithCurrentScene extends true ? PossibleInUnknownScene<any, any> : EnterExit;
};
inline_query: {
scene: WithCurrentScene extends true ? PossibleInUnknownScene<any, any> : EnterExit;
};
chosen_inline_result: {
scene: WithCurrentScene extends true ? PossibleInUnknownScene<any, any> : EnterExit;
};
callback_query: {
scene: WithCurrentScene extends true ? PossibleInUnknownScene<any, any> : EnterExit;
};
shipping_query: {
scene: WithCurrentScene extends true ? PossibleInUnknownScene<any, any> : EnterExit;
};
pre_checkout_query: {
scene: WithCurrentScene extends true ? PossibleInUnknownScene<any, any> : EnterExit;
};
chat_join_request: {
scene: WithCurrentScene extends true ? PossibleInUnknownScene<any, any> : EnterExit;
};
new_chat_members: {
scene: WithCurrentScene extends true ? PossibleInUnknownScene<any, any> : EnterExit;
};
new_chat_title: {
scene: WithCurrentScene extends true ? PossibleInUnknownScene<any, any> : EnterExit;
};
new_chat_photo: {
scene: WithCurrentScene extends true ? PossibleInUnknownScene<any, any> : EnterExit;
};
delete_chat_photo: {
scene: WithCurrentScene extends true ? PossibleInUnknownScene<any, any> : EnterExit;
};
group_chat_created: {
scene: WithCurrentScene extends true ? PossibleInUnknownScene<any, any> : EnterExit;
};
message_auto_delete_timer_changed: {
scene: WithCurrentScene extends true ? PossibleInUnknownScene<any, any> : EnterExit;
};
migrate_to_chat_id: {
scene: WithCurrentScene extends true ? PossibleInUnknownScene<any, any> : EnterExit;
};
migrate_from_chat_id: {
scene: WithCurrentScene extends true ? PossibleInUnknownScene<any, any> : EnterExit;
};
pinned_message: {
scene: WithCurrentScene extends true ? PossibleInUnknownScene<any, any> : EnterExit;
};
invoice: {
scene: WithCurrentScene extends true ? PossibleInUnknownScene<any, any> : EnterExit;
};
successful_payment: {
scene: WithCurrentScene extends true ? PossibleInUnknownScene<any, any> : EnterExit;
};
users_shared: {
scene: WithCurrentScene extends true ? PossibleInUnknownScene<any, any> : EnterExit;
};
chat_shared: {
scene: WithCurrentScene extends true ? PossibleInUnknownScene<any, any> : EnterExit;
};
proximity_alert_triggered: {
scene: WithCurrentScene extends true ? PossibleInUnknownScene<any, any> : EnterExit;
};
video_chat_scheduled: {
scene: WithCurrentScene extends true ? PossibleInUnknownScene<any, any> : EnterExit;
};
video_chat_started: {
scene: WithCurrentScene extends true ? PossibleInUnknownScene<any, any> : EnterExit;
};
video_chat_ended: {
scene: WithCurrentScene extends true ? PossibleInUnknownScene<any, any> : EnterExit;
};
video_chat_participants_invited: {
scene: WithCurrentScene extends true ? PossibleInUnknownScene<any, any> : EnterExit;
};
web_app_data: {
scene: WithCurrentScene extends true ? PossibleInUnknownScene<any, any> : EnterExit;
};
location: {
scene: WithCurrentScene extends true ? PossibleInUnknownScene<any, any> : EnterExit;
};
passport_data: {
scene: WithCurrentScene extends true ? PossibleInUnknownScene<any, any> : EnterExit;
};
}, {}>;
declare function scenes(scenes: AnyScene[], options?: ScenesOptions): Plugin<{}, gramio.DeriveDefinitions & {
message: {
scene: Omit<EnterExit, "exit">;
};
callback_query: {
scene: Omit<EnterExit, "exit">;
};
}, {}>;
export { Scen