### Quickstart: Bot with Conversations Plugin Source: https://github.com/grammyjs/conversations/blob/main/README.md This snippet demonstrates how to install and set up the grammY conversations plugin. It defines a simple greeting conversation and integrates it into a bot. Use this to quickly get started with creating conversational features. ```typescript import { Bot, type Context } from "grammy"; import { type Conversation, type ConversationFlavor, conversations, createConversation, } from "@grammyjs/conversations"; type MyContext = ConversationFlavor; type MyConversationContext = Context; type MyConversation = Conversation; const bot = new Bot(""); /** Defines the conversation */ async function greeting( conversation: MyConversation, ctx: MyConversationContext, ) { await ctx.reply("Hi there! What is your name?"); const { message } = await conversation.wait(); await ctx.reply(`Welcome to the chat, ${message.text}!`); } bot.use(conversations()); bot.use(createConversation(greeting)); bot.command("enter", async (ctx) => { await ctx.reply("Entering conversation!"); // enter the function "greeting" you declared await ctx.conversation.enter("greeting"); }); bot.command("start", (ctx) => ctx.reply("Hi! Send /enter")); bot.use((ctx) => ctx.reply("What a nice update.")); bot.start(); ``` -------------------------------- ### Complete Minimal Bot Example with Conversations Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/setup.md A full, minimal example demonstrating the setup of the grammY conversations plugin, including context type definition, plugin installation, conversation creation, and starting the bot. ```typescript import { Bot, Context } from "grammy"; import { ConversationFlavor, Conversation, conversations, createConversation, } from "@grammyjs/conversations"; // 1. Define context type type MyContext = ConversationFlavor; // 2. Create bot const bot = new Bot(""); // 3. Install plugin (with in-memory storage) bot.use(conversations()); // 4. Define conversation async function welcome(conversation: Conversation, ctx: MyContext) { await ctx.reply("Welcome! What's your name?"); const { message } = await conversation.wait(); await ctx.reply(`Nice to meet you, ${message.text}!`); } // 5. Register conversation bot.use(createConversation(welcome)); // 6. Command to start conversation bot.command("start", async (ctx) => { await ctx.conversation.enter("welcome"); }); // 7. Start bot bot.start(); ``` -------------------------------- ### Example Key-Based Storage Configuration Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/storage.md An example of configuring key-based storage with a custom prefix and storage key derivation function. ```typescript const storage: ConversationKeyStorage = { type: "key", version: 1, prefix: "convo_", getStorageKey: ctx => `chat_${ctx.chatId}`, adapter: myFileSystemAdapter, }; ``` -------------------------------- ### Enter a Conversation via Command Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/setup.md Use a command handler to enter a specific conversation. This example shows how to start the 'greeting' conversation when the '/start' command is received. ```typescript bot.command("start", async (ctx) => { await ctx.conversation.enter("greeting"); }); bot.start(); ``` -------------------------------- ### Example Context-Based Storage Configuration Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/storage.md An example of context-based storage using a custom adapter that derives keys from chat and user IDs. ```typescript const storage: ConversationContextStorage = { type: "context", version: 1, adapter: { async read(ctx) { const key = `${ctx.chatId}_${ctx.from?.id}`; return await db.get(key); }, async write(ctx, state) { const key = `${ctx.chatId}_${ctx.from?.id}`; await db.set(key, state); }, async delete(ctx) { const key = `${ctx.chatId}_${ctx.from?.id}`; await db.delete(key); }, }, }; ``` -------------------------------- ### Complete Setup with Custom File Storage Adapter Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/storage.md Provides a full example of setting up grammY conversations with a custom file system storage adapter. This includes defining read, write, and delete operations for persisting conversation data to local files. ```typescript import { Bot, Context } from "grammy"; import { conversations, createConversation, ConversationFlavor, ConversationData, pinVersion } from "@grammyjs/conversations"; import { readFile, writeFile, mkdir, unlink } from "fs/promises"; import { join } from "path"; type MyContext = ConversationFlavor; const bot = new Bot(""); const dataDir = "./conversation_data"; const { versionify, unpack } = pinVersion(0); const fileAdapter = { async read(key: string) { try { const data = await readFile( join(dataDir, `${key}.json`), "utf-8" ); return JSON.parse(data) as any; } catch { return undefined; } }, async write(key: string, state: any) { await mkdir(dataDir, { recursive: true }); await writeFile( join(dataDir, `${key}.json`), JSON.stringify(state) ); }, async delete(key: string) { try { await unlink(join(dataDir, `${key}.json`)); } catch { // File already deleted } }, }; bot.use(conversations({ storage: { type: "key", version: 0, adapter: fileAdapter, }, })); async function myConversation(conversation: any, ctx: MyContext) { await ctx.reply("What's your name?"); const { message } = await conversation.wait(); await ctx.reply(`Welcome, ${message.text}! `); } bot.use(createConversation(myConversation)); bot.command("start", async (ctx) => { await ctx.conversation.enter("myConversation"); }); bot.start(); ``` -------------------------------- ### ReplayEngine Usage Example Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/engine.md Demonstrates how to create a new ReplayEngine instance and define its builder function, including interrupts and actions. ```typescript const engine = new ReplayEngine(async (controls) => { const value = await controls.interrupt("first"); console.log(value); const result = await controls.action(() => { return Math.random(); }, "random"); console.log(result); }); ``` -------------------------------- ### play() Method Example Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/engine.md Shows how to initiate a new execution using the play() method and handle the ReplayResult, including interrupted states. ```typescript const result = await engine.play(); if (result.type === "interrupted") { const state = result.state; const interrupts = result.interrupts; } ``` -------------------------------- ### Complete Interactive Counter Menu Example Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/menus.md An example demonstrating how to create an interactive counter menu within a conversation. It includes sending the menu, handling button presses, and updating the menu dynamically. ```typescript async function menuExample(conversation, ctx) { // Create root menu let count = 0; const menu = conversation.menu("counter") .text(() => `Count: ${count}`, ctx => { count++; ctx.menu.update(); }) .row() .text("Reset", ctx => { count = 0; ctx.menu.update(); }); // Send menu await ctx.reply("Interactive counter:", { reply_markup: menu }); // Keep waiting for interactions while (count < 10) { await conversation.wait(); } await ctx.reply("Reached 10!"); } ``` -------------------------------- ### Install Conversations Plugin Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/setup.md Install the conversations plugin into your bot instance. This enables conversation functionality. ```typescript import { conversations } from "@grammyjs/conversations"; bot.use(conversations()); ``` -------------------------------- ### Data Versioning Example Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/storage.md Demonstrates how to configure storage with different versions. Old conversations are discarded when the version number changes. ```typescript const v0Adapter = /* ... */; // Start: version 0 bot.use(conversations({ storage: { type: "key", version: 0, adapter: v0Adapter, }, })); // After code change: version 1 bot.use(conversations({ storage: { type: "key", version: 1, adapter: v0Adapter, }, })); ``` -------------------------------- ### Install and Use File System Adapter Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/storage.md Demonstrates how to install and use the file system storage adapter for grammY conversations. Ensure the adapter is imported and then passed to the conversations plugin configuration. ```typescript import { FileAdapter } from "@grammyjs/storage-file"; bot.use(conversations({ storage: { type: "key", adapter: new FileAdapter("./data"), }, })); ``` -------------------------------- ### open() Static Method Example Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/engine.md Illustrates creating an initial replay state using open() and then supplying a value to resolve the first interrupt. ```typescript const [state, interrupt] = ReplayEngine.open("user-input"); // Supply a value for the interrupt ReplayEngine.supply(state, interrupt, userMessage); // Replay will now resolve the interrupt with the supplied value const result = await engine.replay(state); ``` -------------------------------- ### Storage Key Formats Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/storage.md Examples of how to format storage keys, including using chat ID and custom prefixes or functions. ```typescript // Default: "123456789" for chat ID 123456789 const key = ctx.chatId.toString(); // Custom prefix const prefixedKey = "convo_" + ctx.chatId.toString(); // Custom function const key = ctx.from?.id + "_" + ctx.chatId; ``` -------------------------------- ### supply() Static Method Example Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/engine.md Shows how to use supply() to provide values for interrupts during replay, including resetting to a previous checkpoint. ```typescript const [state, int] = ReplayEngine.open("request"); // First time: supplies "hello" to the interrupt ReplayEngine.supply(state, int, "hello"); let result1 = await engine.replay(state); // Reset to before the supply ReplayEngine.reset(state, checkpoint); // Second time: supplies "goodbye" ReplayEngine.supply(state, int, "goodbye"); let result2 = await engine.replay(state); ``` -------------------------------- ### replay() Method Example Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/engine.md Demonstrates resuming execution with replay() after an interruption. It shows how to capture state, supply an interrupt value, and replay. ```typescript let state = (await engine.play() as Interrupted).state; // Supply an interrupt value const checkpoint = ReplayEngine.supply(state, 0, "hello"); // Replay with the new value const result = await engine.replay(state); ``` -------------------------------- ### Install grammY Conversations Plugin Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/setup.md Install the plugin using npm. Requires grammy version 1.20.1 or later and Node.js 12.20.0 or later. ```bash npm install @grammyjs/conversations ``` -------------------------------- ### Install Conversations Plugin Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/api-reference.md Installs the conversations plugin middleware on a grammY bot. Can be configured with custom storage and callbacks. ```typescript import { Bot, Context } from "grammy"; import { conversations, createConversation, ConversationFlavor } from "@grammyjs/conversations"; type MyContext = ConversationFlavor; const bot = new Bot(""); // Install with default options (in-memory storage) bot.use(conversations()); // Or with storage and callbacks bot.use(conversations({ storage: { type: "key", version: 0, adapter: customStorageAdapter, }, onEnter: (id, ctx) => console.log(`Entered: ${id}`), onExit: (id, ctx) => console.log(`Exited: ${id}`), })); ``` -------------------------------- ### Configuring File System Storage Adapter Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/README.md Sets up the Conversations plugin to use the file system for storing conversation state. Requires installing `@grammyjs/storage-file`. ```typescript bot.use(conversations({ storage: { type: "key", version: 0, adapter: new FileAdapter("./data"), }, })); ``` -------------------------------- ### Plugin Installation: conversations() Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/README.md Installs the conversations plugin middleware into your grammY bot. Options can be provided for configuration. ```typescript bot.use(conversations(options)); ``` -------------------------------- ### Low-Level Replay Usage Example Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/engine.md Demonstrates the low-level usage of `ReplayEngine` to manage conversational flows, including handling interrupts, performing actions, and replaying states. ```typescript const engine = new ReplayEngine(async (controls) => { // Get user input const msg = await controls.interrupt("user_message"); console.log("User said:", msg); // Perform database operation (side-effect) const stored = await controls.action(async () => { return await db.save(msg); }, "db_save"); console.log("Stored with ID:", stored.id); }); // First execution let result = await engine.play(); // Output: type=interrupted, interrupts=[0] if (result.type === "interrupted") { // Supply user message and resume ReplayEngine.supply(result.state, 0, "Hello!"); result = await engine.replay(result.state); // Output: // User said: Hello! // Stored with ID: 42 } // If we replay again, same state result = await engine.replay(result.state); // Output: // User said: Hello! // Stored with ID: 42 // (db_save not called again - replayed from log) ``` -------------------------------- ### Menu with Dynamic Content Example Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/README.md Demonstrates creating a menu where button text updates dynamically based on user selection. The menu re-renders when `ctx.menu.update()` is called. ```typescript async function menuExample(conversation, ctx) { let selected = ""; const menu = conversation.menu() .text(() => selected || "Choose", ctx => ctx.menu.update()) .row() .text("Option A", ctx => { selected = "Option A"; ctx.menu.update(); }) .text("Option B", ctx => { selected = "Option B"; ctx.menu.update(); }); await ctx.reply("Select:", { reply_markup: menu }); await conversation.wait(); } ``` -------------------------------- ### Custom Form Field Example Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/forms.md An example demonstrating how to use the `build` method to create a form field that only accepts even numbers. ```typescript const even = await conversation.form.build({ validate: (ctx) => { const text = ctx.message?.text; if (!text) return { ok: false }; const num = parseInt(text); if (num % 2 !== 0) return { ok: false }; return { ok: true, value: num }; }, otherwise: ctx => ctx.reply("Please send an even number!"), }); ``` -------------------------------- ### Install Default Plugins for All Conversations Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/setup.md Use this snippet to configure default plugins that will run on every context within conversations. Ensure the `conversations` plugin is imported and used. ```typescript bot.use(conversations({ plugins: [ // Plugins to run in all conversations async (ctx, next) => { console.log("User:", ctx.from?.first_name); await next(); }, ], })); ``` -------------------------------- ### File System Storage Adapter Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/storage.md An example implementation of VersionedStateStorage using the Node.js file system. Stores conversation data as JSON files. ```typescript import { readFile, writeFile, unlink } from "fs/promises"; import { join } from "path"; const fileAdapter: VersionedStateStorage = { directory: "./data", async read(key) { try { const data = await readFile( join(this.directory, `${key}.json`), "utf-8" ); return JSON.parse(data); } catch { return undefined; } }, async write(key, state) { await writeFile( join(this.directory, `${key}.json`), JSON.stringify(state) ); }, async delete(key) { await unlink(join(this.directory, `${key}.json`)); }, }; ``` -------------------------------- ### In-Memory Storage Adapter Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/storage.md An example implementation of VersionedStateStorage using a Map for in-memory storage. Suitable for development or temporary data. ```typescript const memoryAdapter: VersionedStateStorage = { store: new Map(), read(key) { return this.store.get(key); }, write(key, state) { this.store.set(key, state); }, delete(key) { this.store.delete(key); }, }; ``` -------------------------------- ### File System Storage Adapter for Conversations Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/setup.md Implement a custom file system adapter for persistent storage of conversation states. This example uses Node.js 'fs/promises' to read and write conversation data to files. ```typescript import { readFile, writeFile, mkdir } from "fs/promises"; import { join } from "path"; const dataDir = "./data"; const fileAdapter = { async read(key: string) { try { const content = await readFile( join(dataDir, `${key}.json`), "utf-8" ); return JSON.parse(content); } catch { return undefined; } }, async write(key: string, value: any) { await mkdir(dataDir, { recursive: true }); await writeFile( join(dataDir, `${key}.json`), JSON.stringify(value, null, 2) ); }, async delete(key: string) { try { await unlink(join(dataDir, `${key}.json`)); } catch { // Already deleted } }, }; bot.use(conversations({ storage: { type: "key", version: 1, adapter: fileAdapter, }, })); ``` -------------------------------- ### now() Method Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/conversation.md Gets the current timestamp, ensuring consistency across conversation replays. This is useful for time-sensitive operations. ```APIDOC ## now() Method Gets the current timestamp, consistent across replays. ```typescript async now(): Promise ``` ### Returns Current time in milliseconds (from `Date.now()`). ### Usage Example ```typescript const startTime = await conversation.now(); // ... wait for updates ... const endTime = await conversation.now(); console.log(`Elapsed: ${endTime - startTime}ms`); ``` ``` -------------------------------- ### conversations() Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/api-reference.md Installs the conversations plugin middleware on a grammY bot. It can be configured with options for storage, plugins, and callbacks for entering/exiting conversations. ```APIDOC ## conversations() ### Description Installs the conversations plugin middleware on a grammY bot. ### Method `bot.use()` ### Parameters #### Options - **options** (ConversationOptions) - Optional - Configuration options for the plugin. - **storage** (ConversationStorage) - Optional - Persistence layer for conversation state. Defaults to an in-memory map. - **plugins** (Middleware[] | ((conversation: Conversation) => Middleware[] | Promise[]>) ) - Optional - Default plugins installed for all conversations. Defaults to `[]`. - **onEnter** ((id: string, ctx: OC) => unknown | Promise) - Optional - Callback when entering a conversation. - **onExit** ((id: string, ctx: OC) => unknown | Promise) - Optional - Callback when exiting a conversation. ### Returns A middleware function that can be installed via `bot.use()`. ### Usage Example ```typescript import { Bot, Context } from "grammy"; import { conversations, createConversation, ConversationFlavor } from "@grammyjs/conversations"; type MyContext = ConversationFlavor; const bot = new Bot(""); // Install with default options (in-memory storage) bot.use(conversations()); // Or with storage and callbacks bot.use(conversations({ storage: { type: "key", version: 0, adapter: customStorageAdapter, }, onEnter: (id, ctx) => console.log(`Entered: ${id}`), onExit: (id, ctx) => console.log(`Exited: ${id}`), })); ``` ``` -------------------------------- ### Simple Form Collection Example Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/README.md Collects user input for name and age using `conversation.form.text` and `conversation.form.int`. Includes an `otherwise` handler for invalid input. ```typescript async function userForm(conversation, ctx) { await ctx.reply("Tell me about yourself"); const name = await conversation.form.text({ otherwise: ctx => ctx.reply("Please send text!"), }); const age = await conversation.form.int({ otherwise: ctx => ctx.reply("Please send a number!"), }); await ctx.reply(`${name} is ${age} years old`); } ``` -------------------------------- ### Official File System Storage Package for Conversations Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/setup.md Use the official '@grammyjs/storage-file' package for file system-based persistent storage. This simplifies the setup compared to a custom adapter. ```typescript import { FileAdapter } from "@grammyjs/storage-file"; bot.use(conversations({ storage: { type: "key", version: 1, adapter: new FileAdapter("./data"), }, })); ``` -------------------------------- ### play() Method Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/engine.md Begins a new execution from scratch. ```APIDOC ## play() Method Begins a new execution from scratch. ```typescript async play(): Promise ``` **Returns:** A `ReplayResult` indicating completion, error, or interruption **Example:** ```typescript const result = await engine.play(); if (result.type === "interrupted") { const state = result.state; const interrupts = result.interrupts; } ``` ``` -------------------------------- ### Conversation Control: Entering a Conversation Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/README.md Starts a specific conversation by its ID. Additional arguments can be passed to the conversation function. ```typescript ctx.conversation.enter(id, ...args) ``` -------------------------------- ### Simple Storage Configuration Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/storage.md For backwards compatibility, a raw VersionedStateStorage can be passed directly, which is implicitly treated as key-based storage. ```typescript const storage: VersionedStateStorage = { async read(key) { /* ... */ }, async write(key, state) { /* ... */ }, async delete(key) { /* ... */ }, }; bot.use(conversations({ storage })); // Automatically treated as { type: "key", adapter: storage } ``` -------------------------------- ### Check Active Conversations Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/setup.md Get a list of currently active conversations using `ctx.conversation.active()`. This can be used for status checks or debugging. ```typescript bot.command("status", (ctx) => { const active = ctx.conversation.active(); await ctx.reply(`Active: ${JSON.stringify(active)}`); }); ``` -------------------------------- ### audio() Source: https://github.com/grammyjs/conversations/blob/main/_autodocs/forms.md Waits for audio. ```APIDOC ## audio() ### Description Waits for audio. ### Method `async audio(options?: FormOptions): Promise