--- url: /plugins/official/auto-answer-callback-query.md --- # Auto answer callback query plugin
[![npm](https://img.shields.io/npm/v/@gramio/auto-answer-callback-query?logo=npm&style=flat&labelColor=000&color=3b82f6)](https://www.npmjs.org/package/@gramio/auto-answer-callback-query) [![JSR](https://jsr.io/badges/@gramio/auto-answer-callback-query)](https://jsr.io/@gramio/auto-answer-callback-query) [![JSR Score](https://jsr.io/badges/@gramio/auto-answer-callback-query/score)](https://jsr.io/@gramio/auto-answer-callback-query)
This plugin auto answer on `callback_query` events with `answerCallbackQuery` method if you haven't done it yet. ### Installation ::: code-group ```bash [npm] npm install @gramio/auto-answer-callback-query ``` ```bash [yarn] yarn add @gramio/auto-answer-callback-query ``` ```bash [pnpm] pnpm add @gramio/auto-answer-callback-query ``` ```bash [bun] bun install @gramio/auto-answer-callback-query ``` ::: ```ts import { Bot, InlineKeyboard } from "gramio"; import { autoAnswerCallbackQuery } from "@gramio/auto-answer-callback-query"; const bot = new Bot(process.env.BOT_TOKEN as string) .extend(autoAnswerCallbackQuery()) .command("start", (context) => context.send("Hello!", { reply_markup: new InlineKeyboard() .text("test", "test") .text("test2", "test2"), }) ) .callbackQuery("test", () => { // The plugin will call an answerCallbackQuery method since you didn't do it return context.send("Hii"); }) .callbackQuery("test2", (context) => { // you already answered so plugin won't try to answer return context.answer("HII"); }); ``` ### Params You can pass params for [answerCallbackQuery](https://core.telegram.org/bots/api#answercallbackquery) method ```ts bot.extend( autoAnswerCallbackQuery({ text: "Auto answer", show_alert: true, }) ); ``` > [!IMPORTANT] > This plugin hijack the `context.answerCallbackQuery` (`context.answer` too) method to determine if the callback query was already answered or not. Please avoid global usage of `bot.api.answerCallbackQuery` method in context because plugin can not work properly in this case. --- --- url: /plugins/official/auto-retry.md --- # Auto retry plugin
[![npm](https://img.shields.io/npm/v/@gramio/auto-retry?logo=npm&style=flat&labelColor=000&color=3b82f6)](https://www.npmjs.org/package/@gramio/auto-retry) [![JSR](https://jsr.io/badges/@gramio/auto-retry)](https://jsr.io/@gramio/auto-retry) [![JSR Score](https://jsr.io/badges/@gramio/auto-retry/score)](https://jsr.io/@gramio/auto-retry)
A plugin that catches errors with the `retry_after` field (**rate limit** errors), **waits** for the specified time and **repeats** the API request. ### Installation ::: code-group ```bash [npm] npm install @gramio/auto-retry ``` ```bash [yarn] yarn add @gramio/auto-retry ``` ```bash [pnpm] pnpm add @gramio/auto-retry ``` ```bash [bun] bun install @gramio/auto-retry ``` ::: ### Usage ```ts import { Bot } from "gramio"; import { autoRetry } from "@gramio/auto-retry"; const bot = new Bot(process.env.BOT_TOKEN as string) .extend(autoRetry()) .command("start", async (context) => { for (let index = 0; index < 100; index++) { await context.reply(`some ${index}`); } }) .onStart(console.log); bot.start(); ``` --- --- url: /plugins/official/autoload.md --- # Autoload Plugin
[![npm](https://img.shields.io/npm/v/@gramio/autoload?logo=npm&style=flat&labelColor=000&color=3b82f6)](https://www.npmjs.org/package/@gramio/autoload) [![JSR](https://jsr.io/badges/@gramio/autoload)](https://jsr.io/@gramio/autoload) [![JSR Score](https://jsr.io/badges/@gramio/autoload/score)](https://jsr.io/@gramio/autoload)
Autoload commands plugin for GramIO with [`Bun.build`](#bun-build-usage) support. ### Installation ::: code-group ```bash [npm] npm install @gramio/autoload ``` ```bash [yarn] yarn add @gramio/autoload ``` ```bash [pnpm] pnpm add @gramio/autoload ``` ```bash [bun] bun install @gramio/autoload ``` ::: ## Usage > [full example](https://github.com/gramiojs/autoload/tree/main/example) > [!IMPORTANT] > Please read about [Lazy-load plugins](https://gramio.dev/plugins/official/autoload.html) ## Register the plugin ```ts twoslash // index.ts import { Bot } from "gramio"; import { autoload } from "@gramio/autoload"; const bot = new Bot(process.env.BOT_TOKEN as string) .extend(await autoload()) .onStart(console.log); bot.start(); export type BotType = typeof bot; ``` ## Create command ```ts // commands/command.ts import type { BotType } from ".."; export default (bot: BotType) => bot.command("start", (context) => context.send("hello!")); ``` ## Options | Key | Type | Default | Description | | ----------------- | -------------------------------------------------------------------------------------------------- | -------------------------- | ------------------------------------------------------------------------- | | pattern? | string \| string[] | "\*\*\/\*.{ts,js,cjs,mjs}" | [Glob patterns]() | | path? | string | "./commands" | Path to the folder | | import? | string \| (file: any) => string | "default" | Import a specific `export` from a file | | failGlob? | boolean | true | Throws an error if no matches are found | | skipImportErrors? | boolean | false | Skip imports where needed `export` not defined | | onLoad? | (params: { absolute: string; relative: string }) => unknown | | Hook that is called when loading a file | | onFinish? | (paths: { absolute: string; relative: string }[]) => unknown; | | Hook that is called after loading all files | | fdir? | [Options](https://github.com/thecodrr/fdir/blob/HEAD/documentation.md#method-chaining-alternative) | | Options to configure [fdir](https://github.com/thecodrr/fdir) | | picomatch? | [PicomatchOptions](https://github.com/micromatch/picomatch?tab=readme-ov-file#picomatch-options) | | Options to configure [picomatch](https://www.npmjs.com/package/picomatch) | ### [Bun build](https://bun.sh/docs/bundler) usage You can use this plugin with [`Bun.build`](https://bun.sh/docs/bundler), thanks to [esbuild-plugin-autoload](https://github.com/kravetsone/esbuild-plugin-autoload)! ```ts // @filename: build.ts import { autoload } from "esbuild-plugin-autoload"; // default import also supported await Bun.build({ entrypoints: ["src/index.ts"], target: "bun", outdir: "out", plugins: [autoload("./src/commands")], }).then(console.log); ``` Then, build it with `bun build.ts` and run with `bun out/index.ts`. ### [Bun compile](https://bun.sh/docs/bundler/executables) usage You can bundle and then compile it into a [single executable binary file](https://bun.sh/docs/bundler/executables) ```ts import { autoload } from "esbuild-plugin-autoload"; // default import also supported await Bun.build({ entrypoints: ["src/index.ts"], target: "bun", outdir: "out", plugins: [autoload("./src/commands")], }).then(console.log); await Bun.$`bun build --compile out/index.js`; ``` > [!WARNING] > You cannot use it in `bun build --compile` mode without extra step ([Feature issue](https://github.com/oven-sh/bun/issues/11895)) [Read more](https://github.com/kravetsone/esbuild-plugin-autoload) --- --- url: /bot-class.md --- # Main bot class [`Bot`](https://jsr.io/@gramio/core/doc/~/Bot) - the main class of the framework. You use it to interact with the [Telegram Bot API](/bot-api). ## [Constructor](https://jsr.io/@gramio/core/doc/~/Bot#constructors) There are [two ways](https://jsr.io/@gramio/core/doc/~/Bot#constructors) to pass the token and parameters. 1. Pass the token as the first argument and (optionally) options ```ts const bot = new Bot(process.env.BOT_TOKEN, { api: { fetchOptions: { headers: { "X-Hi-Telegram": "10", }, }, }, }); ``` 2. Pass the options with the required `token` field ```ts const bot = new Bot({ token: process.env.BOT_TOKEN, api: { fetchOptions: { headers: { "X-Hi-Telegram": "10", }, }, }, }); ``` ### Bot info When the bot begins to listen for updates, `GramIO` retrieves information about the bot to verify if the **bot token is valid** and to utilize some bot metadata. For example, this metadata will be used to strip bot mentions in commands. If you set it up, `GramIO` will not send a `getMe` request on startup. ```ts const bot = new Bot(process.env.BOT_TOKEN, { info: process.env.NODE_ENV === "production" ? { id: 1, is_bot: true, first_name: "Bot example", username: "example_bot", // .. } : undefined }, }); ``` > [!IMPORTANT] > You should set this up when **horizontally scaling** your bot (because `rate limits` on `getMe` method and **faster start up time**) or working in **serverless** environments. ### Default plugins Some plugins are used by default, but you can disable them. ```ts const bot = new Bot(process.env.BOT_TOKEN, { plugins: { // disable formatting. All format`` will be text without formatting format: false, }, }); ``` ## API options ### fetchOptions Configure [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch) [parameters](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) ```ts const bot = new Bot(process.env.BOT_TOKEN, { api: { fetchOptions: { headers: { "X-Hi-Telegram": "10", }, }, }, }); ``` ### baseURL URL which will be used to send requests to. `"https://api.telegram.org/bot"` by default. ```ts const bot = new Bot(process.env.BOT_TOKEN, { api: { // random domain baseURL: "https://telegram.io/bot", }, }); ``` ### useTest Should we send requests to `test` data center? `false` by default. The test environment is completely separate from the main environment, so you will need to create a new user account and a new bot with `@BotFather`. [Documentation](https://core.telegram.org/bots/webapps#using-bots-in-the-test-environment) ```ts const bot = new Bot(process.env.BOT_TOKEN, { api: { useTest: true, }, }); ``` ### retryGetUpdatesWait Time in milliseconds before calling `getUpdates` again. `1000` by default. ```ts const bot = new Bot(process.env.BOT_TOKEN, { api: { retryGetUpdatesWait: 300, }, }); ``` ## Proxy support In GramIO, it is quite simple to set up a proxy for requests. ### Node.js ```ts import { ProxyAgent } from "undici"; const proxyAgent = new ProxyAgent("my.proxy.server"); const bot = new Bot(process.env.BOT_TOKEN, { api: { fetchOptions: { dispatcher: proxyAgent, }, }, }); ``` > [!WARNING] > Despite the fact that `undici` works under the hood of `Node.js`, you'll have to install it. Also make sure you don't have `"lib": ["DOM"]` in your `tsconfig.json`, otherwise you won't see the **dispatcher** property in the **types** (although `undici` will process it anyway). [Documentation](https://github.com/nodejs/undici/blob/e461407c63e1009215e13bbd392fe7919747ab3e/docs/api/ProxyAgent.md) ### Bun ```ts const bot = new Bot(process.env.BOT_TOKEN, { api: { fetchOptions: { proxy: "https://username:password@proxy.example.com:8080", }, }, }); ``` [Guide](https://bun.sh/guides/http/proxy) ### Deno ```ts const client = Deno.createHttpClient({ proxy: { url: "http://host:port/" }, }); const bot = new Bot(process.env.BOT_TOKEN, { api: { fetchOptions: { client, }, }, }); ``` > [!WARNING] > This API is **unstable**, so you should run it with `deno run index.ts --unstable` [Documentation](https://docs.deno.com/api/web/~/fetch#function_fetch_1) | [Deno.proxy](https://docs.deno.com/api/deno/~/Deno.Proxy) | [`HTTP_PROXY` environment variables](https://docs.deno.com/runtime/manual/basics/modules/proxies/) --- --- url: /triggers/callback-query.md --- # `callbackQuery` Method The `callbackQuery` method in GramIO is used to handle updates that occur when users interact with [inline keyboard](/keyboards/inline-keyboard) buttons in your Telegram bot. When a user clicks on a button with a callback data payload, Telegram sends a `callback_query` update to your bot. This method allows you to register a handler for these updates, enabling you to perform actions based on the user's interaction. ## Basic Usage ### Handling Callback Queries To use the `callbackQuery` method, you need to define a trigger and a handler. The trigger determines when the handler should be executed based on the callback data received, and the handler performs the desired action. ```ts twoslash import { CallbackData, Bot } from "gramio"; const bot = new Bot(""); // ---cut--- const someData = new CallbackData("example").number("id"); bot.callbackQuery(someData, (context) => { return context.send(`You clicked button with ID: ${context.queryData.id}`); // ^? }); ``` In this example: - `someData` is a `CallbackData` instance defining the schema for the callback data. - The `callbackQuery` method registers a handler that is triggered when the callback data matches `someData`. - Inside the handler, `context.queryData` provides type-safe access to the callback data. ### Trigger Types The `callbackQuery` method supports several types of triggers: - **String Trigger**: The handler is triggered if the callback data exactly matches the specified string. ```ts bot.callbackQuery("my_callback", (context) => { return context.editText("Button clicked!"); }); ``` - **RegExp Trigger**: The handler is triggered if the callback data matches the regular expression. ```ts bot.callbackQuery(/my_(.*)/, (context) => { const match = context.queryData; context.send(`Matched data: ${match[1]}`); }); ``` - **CallbackData Instance**: The handler is triggered if the callback data matches the `CallbackData` schema. ```ts twoslash import { CallbackData, Bot } from "gramio"; const bot = new Bot(""); // ---cut--- const someData = new CallbackData("example").number("id"); bot.callbackQuery(someData, (context) => { context.send(`Data ID: ${context.queryData.id}`); // ^? }); ``` ### Handling Callback Data When a callback query is received, the `context` object includes the following relevant properties: - `context.data`: The raw callback data payload. - `context.queryData`: The deserialized data, if a `CallbackData` instance was used for the trigger. ### Example Scenario Consider a scenario where you want to send a message with an inline keyboard and handle button clicks: ```ts const buttonData = new CallbackData("action").number("action_id"); bot.command("start", (context) => context.send("Choose an action:", { reply_markup: new InlineKeyboard().text( "Do Action 1", buttonData.pack({ action_id: 1 }) ), }) ).callbackQuery(buttonData, (context) => { context.send(`You selected action with ID: ${context.queryData.action_id}`); }); ``` In this example: 1. A `/start` command sends a message with an inline keyboard button. 2. The button's callback data is packed using `buttonData.pack()`. 3. The `callbackQuery` method listens for callback queries that match `buttonData`. 4. The handler responds with the ID of the selected action. --- --- url: /ru/triggers/callback-query.md --- # Метод `callbackQuery` Метод `callbackQuery` в GramIO используется для обработки обновлений, которые возникают, когда пользователи взаимодействуют с кнопками [инлайн-клавиатуры](/ru/keyboards/inline-keyboard) в вашем Telegram боте. Когда пользователь нажимает на кнопку с полезной нагрузкой (`callback_data`), Telegram отправляет обновление `callback_query` вашему боту. Этот метод позволяет зарегистрировать обработчик для таких обновлений, давая возможность выполнять действия на основе взаимодействия пользователя. ## Основное использование ### Обработка Callback Query Чтобы использовать метод `callbackQuery`, вам нужно определить триггер и обработчик. Триггер определяет, когда обработчик должен выполняться на основе полученных `callback_data`, а обработчик выполняет нужное действие. ```ts twoslash import { CallbackData, Bot } from "gramio"; const bot = new Bot(""); // ---cut--- const someData = new CallbackData("example").number("id"); bot.callbackQuery(someData, (context) => { return context.send(`Вы нажали кнопку с ID: ${context.queryData.id}`); // ^? }); ``` В этом примере: - `someData` - это экземпляр `CallbackData`, определяющий схему для callback data. - Метод `callbackQuery` регистрирует обработчик, который вызывается, когда callback data соответствуют `someData`. - Внутри обработчика `context.queryData` предоставляет типо-безопасный доступ к callback data. ### Типы триггеров Метод `callbackQuery` поддерживает несколько типов триггеров: - **Строковый триггер**: Обработчик вызывается, если `callback_data` точно соответствует указанной строке. ```ts bot.callbackQuery("my_callback", (context) => { return context.editText("Кнопка нажата!"); }); ``` - **RegExp триггер**: Обработчик вызывается, если `callback_data` соответствует регулярному выражению. ```ts bot.callbackQuery(/my_(.*)/, (context) => { const match = context.queryData; context.send(`Совпадающие данные: ${match[1]}`); }); ``` - **Экземпляр CallbackData**: Обработчик вызывается, если `callback_data` соответствуют схеме `CallbackData`. ```ts twoslash import { CallbackData, Bot } from "gramio"; const bot = new Bot(""); // ---cut--- const someData = new CallbackData("example").number("id"); bot.callbackQuery(someData, (context) => { context.send(`Data ID: ${context.queryData.id}`); // ^? }); ``` ### Обработка Callback Data Когда получен `callback_query`, объект `context` включает следующие релевантные свойства: - `context.data`: Сырая полезная нагрузка `callback_data`. - `context.queryData`: Десериализованные данные, если для триггера использовался экземпляр `CallbackData`. ### Пример сценария Рассмотрим сценарий, где вы хотите отправить сообщение с инлайн-клавиатурой и обработать нажатия на кнопки: ```ts const buttonData = new CallbackData("action").number("action_id"); bot.command("start", (context) => context.send("Выберите действие:", { reply_markup: new InlineKeyboard().text( "Выполнить действие 1", buttonData.pack({ action_id: 1 }) ), }) ).callbackQuery(buttonData, (context) => { context.send(`Вы выбрали действие с ID: ${context.queryData.action_id}`); }); ``` В этом примере: 1. Команда `/start` отправляет сообщение с кнопкой инлайн-клавиатуры. 2. `callback_data` кнопки упаковываются с помощью `buttonData.pack()`. 3. Метод `callbackQuery` слушает `callback_query`, которые соответствуют `buttonData`. 4. Обработчик отвечает ID выбранного действия. --- --- url: /triggers/chosen-inline-result.md --- # chosenInlineResult The `chosenInlineResult` method in GramIO allows your bot to handle updates when a user selects one of the results returned by an [inline query](/triggers/inline-query). This method is particularly useful when you need to perform additional actions after the user has chosen a specific result from the inline query suggestions. You should enable [collecting feedback](https://core.telegram.org/bots/inline#collecting-feedback) in [@BotFather](https://telegram.me/botfather). > To know which of the provided results your users are sending to their chat partners, send [@Botfather](https://telegram.me/botfather) the `/setinlinefeedback` command. With this enabled, you will receive updates on the results chosen by your users. > Please note that this can create load issues for popular bots – you may receive more results than actual requests due to caching (see the cache_time parameter in [answerInlineQuery](https://core.telegram.org/bots/api#answerinlinequery)). For these cases, we recommend adjusting the probability setting to receive 1/10, 1/100 or 1/1000 of the results. We recommend setting this to `100%` so that each click on an [inline query](/triggers/inline-query) result will produce this event. ## Basic Usage > [!WARNING] > You must specify the same matcher (String, Regex, Function) as in [InlineQuery](/triggers/inline-query) to get the results of clicking on this one, or use the `onResult` option in the [InlineQuery](/triggers/inline-query) trigger. ### Handling Chosen Inline Results The `chosenInlineResult` method registers a handler that is triggered whenever a user selects a result from the inline query response. You can define a trigger that determines when the handler should be invoked, similar to how you define triggers in the `inlineQuery` method. ```ts bot.chosenInlineResult(/search (.*)/i, async (context) => { const selectedResult = context.resultId; const queryParams = context.args; // You can edit messages only with InlineKeyboard if (queryParams && context.inlineMessageId) { await context.editText( `You selected a result with ID: ${selectedResult} for query: ${queryParams[1]}` ); } }); ``` In this example: - The bot listens for any result selection that matches the regular expression `search (.*)`. - If a result is selected that matches the trigger, the bot extracts the result ID and query parameters. - The bot then edits the message to indicate which result was selected. ### Trigger Types The `chosenInlineResult` method supports the same types of triggers as the `inlineQuery` method: - **String Trigger**: The handler is triggered if the `query` exactly matches the specified string. - **RegExp Trigger**: The handler is triggered if the `query` matches the regular expression. - **Function Trigger**: The handler is triggered based on a custom function that returns `true` or `false`. --- --- url: /ru/triggers/chosen-inline-result.md --- # chosenInlineResult Метод `chosenInlineResult` в GramIO позволяет вашему боту обрабатывать обновления, когда пользователь выбирает один из результатов, возвращенных [инлайн-запросом](/ru/triggers/inline-query). Этот метод особенно полезен, когда вам нужно выполнить дополнительные действия после того, как пользователь выбрал определенный результат из предложений инлайн-запроса. Вам следует включить [Feedback](https://core.telegram.org/bots/inline#collecting-feedback) в [@BotFather](https://telegram.me/botfather). > Чтобы узнать, какие из предоставленных результатов ваши пользователи отправляют своим собеседникам, отправьте [@Botfather](https://telegram.me/botfather) команду `/setinlinefeedback`. После включения этой функции вы будете получать обновления о результатах, выбранных вашими пользователями. > Обратите внимание, что это может создать проблемы с нагрузкой для популярных ботов – вы можете получить больше результатов, чем фактических запросов, из-за кэширования (см. параметр cache_time в [answerInlineQuery](https://core.telegram.org/bots/api#answerinlinequery)). Для таких случаев мы рекомендуем регулировать настройку вероятности, чтобы получать 1/10, 1/100 или 1/1000 часть результатов. Мы рекомендуем установить значение `100%` (каждый клик по результату [инлайн-запроса](/ru/triggers/inline-query) будет создавать это событие). ## Основное использование > [!WARNING] > Вы должны указать тот же тип триггера (String, Regex, Function), что и в [InlineQuery](/ru/triggers/inline-query), чтобы получить результаты нажатия на него, или использовать опцию `onResult` в триггере [InlineQuery](/ru/triggers/inline-query). ### Обработка выбранных инлайн-результатов Метод `chosenInlineResult` регистрирует обработчик, который вызывается всякий раз, когда пользователь выбирает результат из ответа на инлайн-запрос. Вы можете определить триггер, который определяет, когда должен быть вызван обработчик, аналогично тому, как вы определяете триггеры в методе `inlineQuery`. ```ts bot.chosenInlineResult(/search (.*)/i, async (context) => { const selectedResult = context.resultId; const queryParams = context.args; // Вы можете редактировать сообщения только с InlineKeyboard if (queryParams && context.inlineMessageId) { await context.editText( `Вы выбрали результат с ID: ${selectedResult} для запроса: ${queryParams[1]}` ); } }); ``` В этом примере: - Бот прослушивает любой выбор результата, соответствующий регулярному выражению `search (.*)`. - Если выбран результат, соответствующий триггеру, бот извлекает ID результата и параметры запроса. - Затем бот редактирует сообщение, чтобы указать, какой результат был выбран. ### Типы триггеров Метод `chosenInlineResult` поддерживает те же типы триггеров, что и метод `inlineQuery`: - **Строковый триггер**: Обработчик вызывается, если `query` точно соответствует указанной строке. - **RegExp триггер**: Обработчик вызывается, если `query` соответствует регулярному выражению. - **Функциональный триггер**: Обработчик вызывается на основе пользовательской функции, которая возвращает `true` или `false`. --- --- url: /triggers/command.md --- # Command The command method in GramIO allows you to create handlers for specific [bot commands](https://core.telegram.org/bots/features#commands). Commands are specific words or phrases that usually begin with a `/` and are used to trigger actions or responses from the bot. Telegram apps will: - **Highlight** commands in messages. When the user taps a highlighted command, that command is immediately sent again. - Suggest a **list of supported commands** with descriptions when the user enters a `/` (for this to work, you need to have provided a list of commands to [@BotFather](https://t.me/botfather) or via the [appropriate API method](https://core.telegram.org/bots/api#setmycommands)). Selecting a command from the list immediately sends it. - Show a [menu button](https://core.telegram.org/bots/features#menu-button) containing all or some of a bot’s commands (which you set via [@BotFather](https://t.me/botfather)). Commands must always start with the `/` symbol and contain **up to 32 characters**. They can use **Latin letters**, **numbers** and **underscores**, though simple lowercase text is recommended for a cleaner look. > [!IMPORTANT] > The command can also be triggered using the full command with the bot's username, such as `/start@name_bot`. ![](https://core.telegram.org/file/464001775/10227/HCr0XgSUHrg.119089/c17ff5d34fe528361e) ## Basic Usage ### Registering a Simple Command You can use the `command` method to make your bot respond to specific commands. Here’s a basic example: ```ts bot.command("start", (context) => { return context.send(`Hi!`); }); ``` > [!IMPORTANT] > You don't need to include a `/` when specifying the command name. If you turn it on, you will get an `error`. In this example, the bot listens for the `/start` command. When the command is received, the bot responds with a simple "Hi!" message. ### Example with Command Arguments Here’s an example that demonstrates how to use arguments with commands: ```ts bot.command("start", async (context) => { return context.send( `You entered the command /start with arguments: ${context.args}` ); }); ``` In this scenario, if a user sends `/start arg1 arg2`, the bot will respond with `You entered the command /start with arguments: arg1 arg2`. ### How `command` Works 1. **Command Recognition:** The bot checks the message for a command (e.g., `/start`). Commands are typically indicated by the [`bot_command` entity](https://core.telegram.org/bots/api#messageentity) in Telegram messages. 2. **Command Matching:** The bot compares the detected command against the command you specified in the `command` method. 3. **Handling Commands with Bot Username:** The command is also recognized if it includes the bot's username, such as `/start@name_bot`. 4. **Command Arguments:** If there are additional arguments following the command (e.g., `/start arg1 arg2`), these are passed to `context.args` for further processing. --- --- url: /plugins/how-to-write.md --- # How to Write a Plugin It happens that you are missing something... And plugins can help you with this! # Example ```ts twoslash import { Plugin, Bot } from "gramio"; export class PluginError extends Error { wow: "type" | "safe" = "type"; } const plugin = new Plugin("gramio-example") .error("PLUGIN", PluginError) .derive(() => { return { some: ["derived", "props"] as const, }; }); const bot = new Bot(process.env.BOT_TOKEN as string) .extend(plugin) .onError(({ context, kind, error }) => { if (context.is("message") && kind === "PLUGIN") { console.log(error.wow); // ^? } }) .use((context) => { console.log(context.some); // ^^^^ }); ``` ## Scaffolding the plugin This command will help you create a plugin with GramIO the easiest way. ::: code-group ```bash [npm] npm create gramio-plugin ./plugin ``` ```bash [yarn] yarn create gramio-plugin ./plugin ``` ```bash [pnpm] pnpm create gramio-plugin ./plugin ``` ```bash [bun] bun create gramio-plugin ./plugin ``` ::: #### Supported environment - Linters - - [Biome](https://biomejs.dev/) - - [ESLint](https://eslint.org/) with some plugins - [Storage](https://gramio.dev/storages/) - Others - - [Husky](https://typicode.github.io/husky/) (Git hooks) --- --- url: /recipes/docker.md --- # Docker --- --- url: /files/download.md --- # Download The standard file download path looks like this: - Call `getFile` with `file_id` - Extract `file_path` from response - Construct a link of the following type `https://api.telegram.org/file/bot/` - Send request and download the requested media - ? Maybe save file in FS ? but in our case it looks more convenient and simpler. ## Via context methods ```ts bot.on("message", async (context) => { if (!context.document) return; // download to ./file-name await context.download(context.document.fileName || "file-name"); // get ArrayBuffer const buffer = await context.download(); return context.send("Thank you!"); }); ``` > [!IMPORTANT] > > **One message** contain only **one attachment**. Therefore, to download an attachment, you can simply use the `context.download` method > But you can solve this using the [media-group](/plugins/official/media-group) plugin. ## Via bot instance method ```ts const chat = await bot.api.getChat({ chat_id: "@not_found" }); if (!chat.photo?.big_file_id) return; // download to ./not_found_chat_photo.png await bot.downloadFile(chat.photo.big_file_id, "not_found_chat_photo.png"); // get ArrayBuffer const buffer = await bot.downloadFile(chat.photo.big_file_id); ``` --- --- url: /keyboards/force-reply-keyboard.md --- # Force Reply Keyboard Upon receiving a message with this object, Telegram clients will display a reply interface to the user (act as if the user has selected the bot's message and tapped 'Reply'). This can be extremely useful if you want to create user-friendly step-by-step interfaces without having to sacrifice [privacy mode](https://core.telegram.org/bots/features#privacy-mode). See also [API Reference](https://jsr.io/@gramio/keyboards/doc/~/ForceReplyKeyboard). ## Import ### With GramIO ```ts twoslash import { ForceReplyKeyboard } from "gramio"; ``` ### Without GramIO ```ts twoslash import { ForceReplyKeyboard } from "@gramio/keyboards"; ``` ## Options ([Documentation](https://core.telegram.org/bots/api/#forcereply)) These parameters are responsible for the settings of the force reply keyboard ### selective Use this parameter if you want to force reply from specific users only. Targets: 1. users that are \@mentioned in the _text_ of the [Message](https://core.telegram.org/bots/api/#message) object. 2. if the bot's message is a reply to a message in the same chat and forum topic, sender of the original message. ```ts twoslash import { ForceReplyKeyboard } from "@gramio/keyboards"; // ---cut--- new ForceReplyKeyboard().selective(); // to enable new ForceReplyKeyboard().selective(false); // to disable ``` ### placeholder The placeholder to be shown in the input field when the reply is active, 1-64 characters. ```ts twoslash import { ForceReplyKeyboard } from "@gramio/keyboards"; // ---cut--- new ForceReplyKeyboard().placeholder("some text"); // to enable new ForceReplyKeyboard().placeholder(); // to disable ``` --- --- url: /ru/keyboards/force-reply-keyboard.md --- # Force Reply Keyboard При получении сообщения с этим объектом, клиенты Telegram отобразят интерфейс ответа пользователю (действуют так, как если бы пользователь выбрал сообщение бота и нажал 'Ответить'). Это может быть чрезвычайно полезно, если вы хотите создать удобные пошаговые интерфейсы без необходимости отказываться от [режима приватности](https://core.telegram.org/bots/features#privacy-mode). Смотрите также [API Reference](https://jsr.io/@gramio/keyboards/doc/~/ForceReplyKeyboard). ## Импорт ### С GramIO ```ts twoslash import { ForceReplyKeyboard } from "gramio"; ``` ### Без GramIO ```ts twoslash import { ForceReplyKeyboard } from "@gramio/keyboards"; ``` ## Параметры ([Документация](https://core.telegram.org/bots/api/#forcereply)) Эти параметры отвечают за настройки клавиатуры force reply ### selective Используйте этот параметр, если вы хотите принудительно получить ответ только от определенных пользователей. Цели: 1. пользователи, которые упоминаются в _тексте_ объекта [Message](https://core.telegram.org/bots/api/#message). 2. если сообщение бота является ответом на сообщение в том же чате и теме форума, отправитель исходного сообщения. ```ts twoslash import { ForceReplyKeyboard } from "@gramio/keyboards"; // ---cut--- new ForceReplyKeyboard().selective(); // для включения new ForceReplyKeyboard().selective(false); // для отключения ``` ### placeholder Заполнитель, отображаемый в поле ввода, когда ответ активен, 1-64 символа. ```ts twoslash import { ForceReplyKeyboard } from "@gramio/keyboards"; // ---cut--- new ForceReplyKeyboard().placeholder("какой-то текст"); // для включения new ForceReplyKeyboard().placeholder(); // для отключения ``` --- --- url: /formatting/index.md --- # Format messages [`@gramio/format`](https://github.com/gramiojs/format) is built-in GramIO package. You can also use it outside of this framework because it is framework-agnostic. See also [API Reference](https://jsr.io/@gramio/format/doc). ## Format Template literal that helps construct message entities for text formatting. Use if you want to strip all of the indentation from the beginning of each line. (like [common-tags#stripindents](https://www.npmjs.com/package/common-tags#stripindents) or [dedent](https://www.npmjs.com/package/dedent)) **NOTE**: for format with **arrays** use it with [`join`](#join) helper ```ts twoslash import { format, bold, link, italic, Bot } from "gramio"; const bot = new Bot(""); // ---cut--- format`some text`; // or format`${bold`Hmm...`} ${link( "GramIO", "https://github.com/gramiojs/gramio" )}?`; ``` Let's send something... ```ts twoslash import { format, bold, link, italic, spoiler, Bot } from "gramio"; const bot = new Bot(""); // ---cut--- bot.api.sendMessage({ chat_id: 12321, text: format`${bold`Hi!`} Can ${italic("you")} help ${spoiler`me`}? Can you give me a ${link("star", "https://github.com/gramiojs/gramio")}?`, }); ``` ![format](/formatting/format.png) ## FormatSaveIndents Template literal that helps construct message entities for text formatting. Use if you want to save all of the indentation. **NOTE**: for format with **arrays** use it with [`join`](#join) helper ```ts twoslash import { formatSaveIndents, bold, link, italic, spoiler, Bot } from "gramio"; const bot = new Bot(""); // ---cut--- bot.api.sendMessage({ chat_id: 12321, text: formatSaveIndents`${bold`Hi!`} Can ${italic("you")} help ${spoiler`me`}? Can you give me a ${link("star", "https://github.com/gramiojs/gramio")}?`, }); ``` ![format-save-indents](/formatting/format-save-indents.png) ## Entities ### **Bold** Format text as **bold**. Cannot be combined with `code` and `pre`. ```ts twoslash import { format, bold } from "@gramio/format"; // ---cut--- format`Format text as ${bold`bold`}`; ``` ![bold example](/formatting/bold.png) ### _Italic_ Format text as _italic_. Cannot be combined with `code` and `pre`. ```ts twoslash import { format, italic } from "@gramio/format"; // ---cut--- format`Format text as ${italic`italic`}`; ``` ![italic example](/formatting/italic.png) ### Underline Format text as underline. Cannot be combined with `code` and `pre`. ```ts twoslash import { format, underline } from "@gramio/format"; // ---cut--- format`Format text as ${underline`underline`}`; ``` ![underline example](/formatting/underline.png) ### ~~Strikethrough~~ Format text as ~~strikethrough~~. Cannot be combined with `code` and `pre`. ```ts twoslash import { format, strikethrough } from "@gramio/format"; // ---cut--- format`Format text as ${strikethrough`strikethrough`}`; ``` ![strikethrough example](/formatting/strikethrough.png) ### Spoiler Format text as spoiler. Cannot be combined with `code` and `pre`. ```ts twoslash import { format, spoiler } from "@gramio/format"; // ---cut--- format`Format text as ${spoiler`spoiler`}`; ``` ![spoiler example](/formatting/spoiler.png) > ### Blockquote Format text as blockquote. Cannot be nested. ```ts twoslash import { format, blockquote } from "@gramio/format"; // ---cut--- format`Format text as ${blockquote`blockquote`}`; ``` ![blockquote example](/formatting/blockquote.png) > ### Expandable blockquote Format text as expandable blockquote. Cannot be nested. ```ts twoslash import { format, expandableBlockquote } from "@gramio/format"; function loremIpsum(options: { count: number }): string { return ""; } // ---cut--- format`Format text as ${expandableBlockquote(loremIpsum({ count: 20 }))}`; ``` ![expandable blockquote example](/formatting/expandable_blockquote.png) ### `Code` Format text as `code`. Convenient for copied items. Cannot be combined with any other format. ```ts twoslash import { format, code } from "@gramio/format"; // ---cut--- format`Format text as ${code`code`}`; ``` ![code example](/formatting/code.png) ### `Pre` Format text as `pre`. Cannot be combined with any other format. ([Supported languages](https://github.com/TelegramMessenger/libprisma#supported-languages)) ```ts twoslash import { format, pre } from "@gramio/format"; // ---cut--- format`Format text as ${pre`pre`}`; // or with language format`Format text as ${pre(`console.log("pre with language")`, "js")}`; ``` ![pre example](/formatting/pre.png) ### [Link](https://github.com/gramiojs/gramio) Format text as [link](https://github.com/gramiojs/gramio). Cannot be combined with `code` and `pre`. ```ts twoslash import { format, link } from "@gramio/format"; // ---cut--- format`Format text as ${link("link", "https://github.com/gramiojs/gramio")}`; ``` ![link example](/formatting/link.png) ### [Mention](https://github.com/gramiojs/gramio) Format text as [mention](https://github.com/gramiojs/gramio). Cannot be combined with `code` and `pre`. ```ts twoslash import { format, mention } from "@gramio/format"; // ---cut--- format`format text as ${mention("mention", { id: 12312312, is_bot: false, first_name: "GramIO", })}`; ``` ![mention example](/formatting/mention.png) ### 🄲 🅄 🅂 🅃 🄾 🄼 ㅤ🄴 🄼 🄾 🄹 🄸 Insert custom emoji by their id. ```ts twoslash import { format, customEmoji } from "@gramio/format"; // ---cut--- format`text with emoji - ${customEmoji("⚔️", "5222106016283378623")}`; ``` > [!WARNING] > Custom emoji entities can only be used by bots that purchased additional usernames on [Fragment](https://fragment.com/). ## Helpers ### Join Helper for great work with formattable arrays. ([].join break styling) Separator by default is `, ` ```ts twoslash import { format, join, bold } from "@gramio/format"; // ---cut--- format`${join(["test", "other"], (x) => format`${bold(x)}`, "\n")}`; ``` --- --- url: /get-started.md --- # Get Started Create a new bot with GramIO in minutes. You should already have [Node.js](https://nodejs.org/), [Bun](https://bun.sh/) or [Deno](https://deno.com/) installed. ## Obtain your bot token First, create a bot and get a `token`. You can do this using the [@BotFather](https://t.me/BotFather) bot. Send him the `/newbot` command and follow the instructions until you receive a token like `110201543:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw`. ## Create your bot project There are two ways to create a bot project: ### Automatically The easiest way is to use `create-gramio`. ::: code-group ```bash [npm] npm create gramio bot-example ``` ```bash [yarn] yarn create gramio bot-example ``` ```bash [pnpm] pnpm create gramio bot-example ``` ```bash [bun] bunx create-gramio bot-example ``` ::: And then: ::: code-group ```bash [npm] cd bot-example npm run dev ``` ```bash [yarn] cd bot-example yarn dev ``` ```bash [pnpm] cd bot-example pnpm dev ``` ```bash [bun] cd bot-example bun dev ``` ::: #### Supported environment - ORM/Query builders - - [Prisma](https://www.prisma.io/) - - [Drizzle](https://orm.drizzle.team/) - Linters - - [Biome](https://biomejs.dev/) - - [ESLint](https://eslint.org/) with some plugins - Plugins - - [Scenes](https://gramio.dev/plugins/official/scenes.html) - - [Session](https://gramio.dev/plugins/official/session.html) - - [Autoload](https://gramio.dev/plugins/official/autoload.html) - - [Prompt](https://gramio.dev/plugins/official/prompt.html) - - [Auto-retry](https://gramio.dev/plugins/official/auto-retry.html) - - [Media-cache](https://gramio.dev/plugins/official/media-cache.html) - - [I18n](https://gramio.dev/plugins/official/i18n.html) - - [Media-group](https://gramio.dev/plugins/official/media-group.html) - Others - - [Dockerfile](https://www.docker.com/) + [docker-compose.yml](https://docs.docker.com/compose/) - - [Jobify](https://github.com/kravetsone/jobify) ([Bullmq](https://docs.bullmq.io/) wrapper) - - [Husky](https://typicode.github.io/husky/) (Git hooks) - - [Fluent2ts](https://github.com/kravetsone/fluent2ts) - - [GramIO storages](https://gramio.dev/storages/) - [Telegram apps](https://github.com/Telegram-Mini-Apps/telegram-apps/tree/master/packages/create-mini-app) - [Elysia](https://elysiajs.com/) (by [create-elysiajs](https://github.com/kravetsone/create-elysiajs)) > The environment can work `together` > > When you select [ESLint](https://eslint.org/) and [Drizzle](https://orm.drizzle.team/), you get [eslint-plugin-drizzle](https://orm.drizzle.team/docs/eslint-plugin) ## Manually To manually create a new bot with GramIO, install the package: ::: code-group ```bash [npm] npm install gramio ``` ```bash [yarn] yarn add gramio ``` ```bash [pnpm] pnpm add gramio ``` ```bash [bun] bun install gramio ``` ::: Setup TypeScript: ::: code-group ```bash [npm] npm install typescript -D npx tsc --init ``` ```bash [yarn] yarn add typescript -D yarn dlx tsc --init ``` ```bash [pnpm] pnpm add typescript -D pnpm exec tsc --init ``` ```bash [bun] bun install typescript -D bunx tsc --init ``` ::: create `src` folder with `index.ts` file and write something like: ::: code-group ```ts twoslash [Bun or Node.js] import { Bot } from "gramio"; const bot = new Bot("") // put you token here .command("start", (context) => context.send("Hi!")) .onStart(console.log); bot.start(); ``` ```ts [Deno] import { Bot } from "jsr:@gramio/core"; const bot = new Bot("") // put you token here .command("start", (context) => context.send("Hi!")) .onStart(console.log); bot.start(); ``` ::: and run the bot with: ::: code-group ```bash [tsx] npx tsx ./src/index.ts ``` ```bash [bun] bun ./src/index.ts ``` ```bash [deno] deno run --allow-net ./src/index.ts ``` ::: Done! 🎉 Now you can interact with your Telegram bot. --- --- url: /updates/graceful-shutdown.md --- # Graceful shutdown Graceful shutdown - pattern which helps to stop the application without losing data or killing running processes (for example, response to `/start`). > What are your last words? ### Signals - `SIGINT` (Signal Interrupt) This signal sended when user presses Ctrl+C. - `SIGTERM` (Signal Terminate) This signal is often sent by process managers (like Docker, Kubernetes, or systemd) when they need to terminate a process. so we want to handle both. ### Example Imagine you have a **bot** on **webhooks** with **analytics** usage. ```ts import { app } from "./webhook"; import { bot } from "./bot"; import { posthog } from "./analytics"; const signals = ["SIGINT", "SIGTERM"]; for (const signal of signals) { process.on(signal, async () => { console.log(`Received ${signal}. Initiating graceful shutdown...`); await app.stop(); await bot.stop(); await posthog.shutdown(); process.exit(0); }); } ``` For first, we should stop receiving updates - gracefully stop our backend API framework (for example, Elysia). For second, we should process all pending updates - gracefully stop our bot with `bot.stop` method. and last, we should call `posthog.shutdown()` which send captured analytics to our PostHog instance. then we exit our process with 0 status code and it all! this example shows how to gracefully shutdown your bot in right order for most applications. --- --- url: /index.md --- ## Get started This command will help you create a project with GramIO (and ORM, linters and plugins) the easiest way. ::: code-group ```bash [npm] npm create gramio@latest ./bot ``` ```bash [yarn] yarn create gramio@latest ./bot ``` ```bash [pnpm] pnpm create gramio@latest ./bot ``` ```bash [bun] bun create gramio@latest ./bot ``` ```bash [deno] TODO:// Deno is supported but not in scaffolding ``` ::: For more information, see the «[Get started](/get-started)» section. ### GramIO in action Example which uses some interesting features. ```ts twoslash // @filename: utils.ts export function findOrRegisterUser() { return {} as { id: number; name: string; balance: number }; } // @filename: index.ts // ---cut--- // @errors: 2339 import { Bot, format, bold, code } from "gramio"; import { findOrRegisterUser } from "./utils"; const bot = new Bot(process.env.BOT_TOKEN as string) .derive("message", async () => { const user = await findOrRegisterUser(); return { user, }; }) .on("message", (context) => { context.user; // ^? // // // // // // // return context.send(format` Hi, ${bold(context.user.name)}! You balance: ${code(context.user.balance)}`); }) .on("callback_query", (context) => { // // context.user; }); ``` --- --- url: /ru/index.md --- ## Начало работы Эта команда поможет вам создать проект с GramIO (включая ORM, линтеры и плагины) самым простым способом. ::: code-group ```bash [npm] npm create gramio@latest ./bot ``` ```bash [yarn] yarn create gramio@latest ./bot ``` ```bash [pnpm] pnpm create gramio@latest ./bot ``` ```bash [bun] bun create gramio@latest ./bot ``` ```bash [deno] TODO:// Deno поддерживается, но не в генераторе проектов ``` ::: Для получения дополнительной информации см. раздел «[Начало работы](/ru/get-started)». ### GramIO в действии Пример, использующий некоторые интересные функции. ```ts twoslash // @filename: utils.ts export function findOrRegisterUser() { return {} as { id: number; name: string; balance: number }; } // @filename: index.ts // ---cut--- // @errors: 2339 import { Bot, format, bold, code } from "gramio"; import { findOrRegisterUser } from "./utils"; const bot = new Bot(process.env.BOT_TOKEN as string) .derive("message", async () => { const user = await findOrRegisterUser(); return { user, }; }) .on("message", (context) => { context.user; // ^? // // // // // // // return context.send(format` Привет, ${bold(context.user.name)}! Ваш баланс: ${code(context.user.balance)}`); }) .on("callback_query", (context) => { // // context.user; }); ``` --- --- url: /plugins/index.md --- # Plugins Overview Please see our official plugins: - [Scenes](/plugins/official/scenes) - [I18n](/plugins/official/i18n) - [Autoload](/plugins/official/autoload) - [Auto answer callback query](/plugins/official/auto-answer-callback-query) - [Session](/plugins/official/session) - [Auto retry](/plugins/official/auto-retry) - [Media cache](/plugins/official/media-cache) - [Media group](/plugins/official/media-group) - [Prompt](/plugins/official/prompt) --- --- url: /triggers/reaction.md --- # reaction The `reaction` method is used to register a handler for changes in reactions on a message. This method allows your bot to respond when a user reacts to a message with an emoji. By specifying the trigger (which emoji to listen for) and the corresponding handler, you can customize how your bot responds to these interactions. ## Key Features ### Handling Reactions To use the `reaction` method, you define a trigger (or multiple triggers) and a handler. The trigger is the emoji (or emojis) that, when reacted to a message, will cause the handler to execute. ```ts bot.reaction("👍", async (context) => { await context.reply(`Thank you!`); }); ``` In this example: - The `reaction` method is called with the trigger `"👍"`, which is an emoji. - Whenever a user reacts to a message with a thumbs-up emoji (`👍`), the bot will execute the handler and reply with "Thank you!". ### Trigger Types The `trigger` parameter can be a single emoji or an array of emojis. The handler will execute if any of the specified emojis are used. ```ts bot.reaction(["👍", "❤️"], async (context) => { await context.reply(`Thanks for your reaction!`); }); ``` In this example: - The bot will respond if a user reacts with either a thumbs-up (`👍`) or a heart (`❤️`). --- --- url: /hooks/overview.md --- # Hooks The hook system allows us to hook on to the lifecycle of the API request/context and somehow impact it. Below are the hooks available in GramIO: - [onStart](/hooks/on-start) - called when the bot is started - [onStop](/hooks/on-stop) - called when the bot stops - [onError](/hooks/on-error) - called when an error occurs in the contexts - [preRequest](/hooks/pre-request) - called before sending a request to Telegram Bot API (allows us to impact the sent parameters) - [onResponse](/hooks/on-response) - called on API request response (only if success) - [onResponseError](/hooks/on-response-error) - called on API request response (only if errored) --- --- url: /rate-limits.md --- # Rate limits This is a guide that shows how to solve rate limit errors (`Error code 429, Error: Too many requests: retry later`) from Telegram Bot API. Generally speaking, if you avoid **broadcast-like** behavior in your bot, there is no need to worry about rate limits. But it's better to use the [auto-retry](/plugins/official/auto-retry) plugin just in case. if you reach these limits without broadcasting, then most likely something is wrong on your side. # How to Broadcast For a start, we can solve rate-limit errors when broadcasting without using **queues**. Let's use the [built-in `withRetries` function](/bot-api#Handling-Rate-Limit) which catches errors with the `retry_after` field (**rate limit** errors), **waits** for the specified time and **repeats** the API request. Next, we need to make a loop and adjust the **delay** so that we are least likely to fall for a `rate-limit` error, and if we catch it, we will wait for the specified time (and the [built-in `withRetries` function](/bot-api#Handling-Rate-Limit) will repeat it) ```ts twoslash // experimental API available since Node.js@16.14.0 import { scheduler } from "node:timers/promises"; import { Bot, TelegramError } from "gramio"; import { withRetries } from "gramio/utils"; const bot = new Bot(process.env.BOT_TOKEN as string); const chatIds: number[] = [ /** some chat ids */ ]; for (const chatId of chatIds) { const result = await withRetries(() => bot.api.sendMessage({ suppress: true, chat_id: chatId, text: "Hi!", }) ); await scheduler.wait(100); // Base delay between successful requests to avoid rate limits } ``` ## Queue Implementation (@gramio/broadcast) Persistent even when server restarts and ready to horizontal-scaling broadcasting sample. GramIO has a convenient library for broadcasting in its ecosystem - [@gramio/broadcast](https://github.com/gramiojs/broadcast) Pre-requirements: - [Redis](https://redis.io/) ```ts import { Bot, InlineKeyboard } from "gramio"; import Redis from "ioredis"; import { Broadcast } from "@gramio/broadcast"; const redis = new Redis({ maxRetriesPerRequest: null, }); const bot = new Bot(process.env.BOT_TOKEN as string); const broadcast = new Broadcast(redis).type("test", (chatId: number) => bot.api.sendMessage({ chat_id: chatId, text: "test", }) ); console.log("prepared to start"); const chatIds = [617580375]; await broadcast.start( "test", chatIds.map((x) => [x]) ); // graceful shutdown async function gracefulShutdown() { console.log(`Process ${process.pid} go to sleep`); await broadcast.job.queue.close(); console.log("closed"); process.exit(0); } process.on("SIGTERM", gracefulShutdown); process.on("SIGINT", gracefulShutdown); ``` This library provides a convenient interface for broadcasting without losing type safety. You create types of broadcasts and pass data to functions, then call `broadcast.start` with an array of arguments. ## Own Implementation Pre-requirements: - [Redis](https://redis.io/) - ioredis, bullmq, [jobify](https://github.com/kravetsone/jobify) // TODO: more text about it ```ts import { Worker } from "bullmq"; import { Bot, TelegramError } from "gramio"; import { Redis } from "ioredis"; import { initJobify } from "jobify"; const bot = new Bot(process.env.BOT_TOKEN as string); const redis = new Redis({ maxRetriesPerRequest: null, }); const defineJob = initJobify(redis); const text = "Hello, world!"; const sendMailing = defineJob("send-mailing") .input<{ chatId: number }>() .options({ limiter: { max: 20, duration: 1000, }, }) .action(async ({ data: { chatId } }) => { const response = await bot.api.sendMessage({ chat_id: chatId, suppress: true, text, }); if (response instanceof TelegramError) { if (response.payload?.retry_after) { await sendMailing.worker.rateLimit( response.payload.retry_after * 1000 ); // use this only if you did not use auto-retry // because it re-run this job again throw Worker.RateLimitError(); } else throw response; } }); const chats: number[] = []; // pick chats from database await sendMailing.addBulk( chats.map((x) => ({ name: "mailing", data: { chatId: x, }, })) ); ``` ### Read also - [So your bot is rate limited...](https://telegra.ph/So-your-bot-is-rate-limited-01-26) - [Broadcasting to Users](https://core.telegram.org/bots/faq#broadcasting-to-users) --- --- url: /updates/webhook.md --- # How to use webhook Telegram Bot API support two ways of getting updates: [long-polling](https://en.wikipedia.org/wiki/Push_technology#Long_polling) and [webhook](https://en.wikipedia.org/wiki/Webhook?useskin=vector). GramIO works well with both. Here is an example of using webhooks ## Supported frameworks - [Elysia](https://elysiajs.com/) - [Fastify](https://fastify.dev/) - [Hono](https://hono.dev/) - [Express](https://expressjs.com/) - [Koa](https://koajs.com/) - [node:http](https://nodejs.org/api/http.html) - [Bun.serve](https://bun.sh/docs/api/http) - [std/http (Deno.serve)](https://docs.deno.com/runtime/manual/runtime/http_server_apis#http-server-apis) - [And any other framework](#write-you-own-updates-handler) ## Example ```ts import { Bot, webhookHandler } from "gramio"; import Fastify from "fastify"; const bot = new Bot(process.env.BOT_TOKEN as string); const fastify = Fastify(); fastify.post("/telegram-webhook", webhookHandler(bot, "fastify")); fastify.listen({ port: 3445, host: "::" }); bot.on("message", (context) => { return context.send("Fastify!"); }); bot.start({ webhook: { url: "https://example.com:3445/telegram-webhook", }, }); ``` ## Use webhook only in production Instead of use a tunnels, you can just use polling in development environment! ```ts const bot = new Bot(process.env.BOT_TOKEN); await bot.start({ webhook: process.env.NODE_ENV === "production" ? { url: `${process.env.API_URL}/${process.env.BOT_TOKEN}`, } : undefined, }); ``` ## Local development with webhook For local development with webhook, we recommend using untun Logounjs/untun. **Untun** is a tool for tunnel your **local** HTTP(s) server to the world! > [!IMPORTANT] > Examples of starting with a specific framework are omitted. See [this example](#example). ### via API This method allows you to set a link to our tunnel directly in the script. Install package: ::: code-group ```bash [npm] npm install untun ``` ```bash [yarn] yarn add untun ``` ```bash [pnpm] pnpm install untun ``` ```bash [bun] bun install untun ``` ::: Start tunnel and set webhook: ```ts import { startTunnel } from "untun"; const tunnel = await startTunnel({ port: 3000 }); bot.start({ webhook: { url: await tunnel.getURL(), }, }); ``` ### via CLI We are listening to port `3000` locally. Therefore, we open the tunnel like this: ::: code-group ```bash [npm] npx untun@latest tunnel http://localhost:3000 ``` ```bash [yarn] yarn dlx untun@latest tunnel http://localhost:3000 ``` ```bash [pnpm] pnpm dlx untun@latest tunnel http://localhost:3000 ``` ```bash [bun] bunx untun@latest tunnel http://localhost:3000 ``` ::: ```bash ◐ Starting cloudflared tunnel to http://localhost:3000 ℹ Waiting for tunnel URL... ✔ Tunnel ready at https://unjs-is-awesome.trycloudflare.com ``` Now we use this link when installing the webhook: ```ts bot.start({ webhook: { url: "https://unjs-is-awesome.trycloudflare.com", }, }); ``` ## Write you own updates handler ```ts // a non-existing framework for the example import { App } from "some-http-framework"; import { Bot } from "gramio"; const bot = new Bot(process.env.BOT_TOKEN as string).on("message", (context) => context.send("Hello!") ); // init is required. It is used for lazy-load plugins, and also receives information about the bot. await bot.init(); const app = new App().post("/telegram", async (req) => { // req.body must be json equivalent to TelegramUpdate await bot.updates.handleUpdate(req.body); }); app.listen(80); ``` --- --- url: /plugins/official/i18n.md --- # I18n plugin
[![npm](https://img.shields.io/npm/v/@gramio/i18n?logo=npm&style=flat&labelColor=000&color=3b82f6)](https://www.npmjs.org/package/@gramio/i18n) [![JSR](https://jsr.io/badges/@gramio/i18n)](https://jsr.io/@gramio/i18n) [![JSR Score](https://jsr.io/badges/@gramio/i18n/score)](https://jsr.io/@gramio/i18n)
`i18n` plugin for [GramIO](https://gramio.dev/). This plugin provide internationalization for your bots with [Fluent](https://projectfluent.org/) syntax. ![example](https://github.com/gramiojs/i18n/assets/57632712/47e04c22-f442-4a5a-b8b9-15b8512f7c4b) You can [setup type-safety](#type-safety) for it. `i18n` plugin for [GramIO](https://gramio.dev/). This plugin provide good way to add internationalization for your bots! It can be used without GramIO, but it will always keep it in mind. > [!IMPORTANT] > Since `1.0.0`, we have two ways to write localization: [`I18n-in-TS`](#i18n-in-ts-syntax) and [`Fluent`](#fluent-syntax) ### Installation For [I18n-in-TS syntax](#i18n-in-ts-syntax) ::: code-group ```bash [npm] npm install @gramio/i18n ``` ```bash [bun] bun add @gramio/i18n ``` ```bash [yarn] yarn add @gramio/i18n ``` ```bash [pnpm] pnpm add @gramio/i18n ``` ::: For [Fluent syntax](#fluent-syntax) ::: code-group ```bash [npm] npm install @gramio/i18n @fluent/bundle ``` ```bash [bun] bun add @gramio/i18n @fluent/bundle ``` ```bash [yarn] yarn add @gramio/i18n @fluent/bundle ``` ```bash [pnpm] pnpm add @gramio/i18n @fluent/bundle ``` ::: ## I18n-in-TS syntax This syntax allows you to write localization without leaving `.ts` files and does not require code-generation for **type-safety**, as well as provides convenient integration with the Format API out of the box! ```ts twoslash import { format, Bot } from "gramio"; import { defineI18n, type LanguageMap, type ShouldFollowLanguage, } from "@gramio/i18n"; const en = { greeting: (name: string) => format`Hello, ${name}!`, and: { some: { nested: "Hi!!!", }, }, } satisfies LanguageMap; const ru = { greeting: (name: string) => format`Привет, ${name}!`, and: { some: { nested: "Hi!!!", }, }, } satisfies ShouldFollowLanguage; // Strict will show error on missing keys // satisfies ShouldFollowLanguageStrict; const i18n = defineI18n({ primaryLanguage: "en", languages: { en, ru, }, }); i18n.t("en", "greeting", "World"); // Hello, World! i18n.t("en", "and.some.nested"); // Hi!!! const bot = new Bot(process.env.BOT_TOKEN as string) .derive("message", (context) => { // u can take language from database or whatever u want and bind it to context without loosing type-safety return { t: i18n.buildT(context.from?.languageCode), }; }) .on("message", (context) => { return context.send( context.t("greeting", context.from?.firstName ?? "World") ); }); ``` ### Plurals ```ts import { pluralizeEnglish, pluralizeRussian } from "@gramio/i18n"; const count = 5; console.log(`You have ${count} ${pluralizeEnglish(count, "apple", "apples")}.`); // You have 5 apples. console.log( `У вас ${count} ${pluralizeRussian(count, "яблоко", "яблока", "яблок")}.` ); // У вас 5 яблок. ``` `ExtractLanguages` helps you extract languages types from i18n instance. ```ts type EnLocalization = ExtractLanguages["en"]; type EnLocalizationKeys = keyof ExtractLanguages["en"]; type EnGreetingArgs = ExtractArgsParams; ``` ## [Fluent](https://projectfluent.org/) syntax This plugin provide internationalization for your bots with [Fluent](https://projectfluent.org/) syntax. ![example](https://github.com/gramiojs/i18n/assets/57632712/47e04c22-f442-4a5a-b8b9-15b8512f7c4b) You can [setup type-safety](#type-safety) for it. ## Usage ### Create `locales` folder with `en.ftl` file ```ftl # Simple things are simple. hello-user = Hello, {$userName}! # Complex things are possible. shared-photos = {$userName} {$photoCount -> [one] added a new photo *[other] added {$photoCount} new photos } to {$userGender -> [male] his stream [female] her stream *[other] their stream }. ``` > [!IMPORTANT] > Fluent language support extensions for [VSCode](https://marketplace.visualstudio.com/items?itemName=macabeus.vscode-fluent) and [WebStorm](https://plugins.jetbrains.com/plugin/18416-fluent-language) ### Use plugin ```ts // @moduleResolution: NodeNext // @module: NodeNext // src/index.ts import { Bot } from "gramio"; import { i18n } from "@gramio/i18n/fluent"; const bot = new Bot(process.env.BOT_TOKEN as string) .extend(i18n()) .command("start", async (context) => { return context.send( context.t("shared-photos", { userName: "Anna", userGender: "female", photoCount: 3, }) ); }) .onError(console.error) .onStart(console.log); bot.start(); ``` ## Options | Key | Type | Default | Description | | -------------- | ------ | --------- | ----------------------------------------- | | defaultLocale? | string | "en" | Default locale | | directory? | string | "locales" | The path to the folder with `*.ftl` files | ##### Or provide an client ```ts twoslash import { Bot } from "gramio"; import { getFluentClient, i18n } from "@gramio/i18n/fluent"; // or getFluentClient() const client = getFluentClient({ defaultLocale: "en", directory: "locales", }); const bot = new Bot(process.env.BOT_TOKEN as string) .extend(i18n(client)) .command("start", async (context) => { return context.send(context.t("hello-user", { userName: "Anna" })); }); ``` > [!IMPORTANT] > See [Type-safety](#type-safety). You should provide generic to `getFluentClient` to get type-safety. ### Methods #### t Using this method, you can get the text in your chosen language. For example: ```ftl hello-user = Hello, {$userName}! ``` ```ts context.t("hello-user", { userName: "Anna" }); // Hello, Anna! ``` #### setLocale You can set user locale by `setLocale` method. > [!WARNING] > At the moment, there is no integration with sessions, and therefore, after the message, the language will again become the one that defaultLocale ```ts bot.command("start", async (context) => { context.setLocale("ru"); return context.send( context.t("shared-photos", { userName: "Anna", userGender: "female", photoCount: 3, }) ); }); ``` ## Type-safety You can use this plugin with [fluent2ts](https://github.com/kravetsone/fluent2ts) which code-generates typescript types from your `.ftl` files. See [usage](https://github.com/kravetsone/fluent2ts?tab=readme-ov-file#usage). Npm: ```bash [npm] npx fluent2ts ``` Bun: ```bash [bun] bunx fluent2ts ``` Yarn: ```bash [yarn] yarn dlx fluent2ts ``` Pnpm: ```bash [pnpm] pnpm exec fluent2ts ``` And so we have a generated `locales.types.ts` file in `src` folder that exports the `TypedFluentBundle` interface. We set this type as a **generic** for the `i18n` plugin. And now we have **type-safety**! ```ts twoslash // @filename: locales.types.ts import type { FluentBundle, FluentVariable, Message as FluentMessage, } from "@fluent/bundle"; export interface LocalesMap { "hello-user": never; "shared-photos": { userName: FluentVariable; photoCount: FluentVariable; userGender: FluentVariable; }; } export interface Message extends FluentMessage { id: Key; } export interface TypedFluentBundle extends FluentBundle { getMessage(key: Key): Message; formatPattern( key: Key, ...args: LocalesMap[Key] extends never ? [] : [args: LocalesMap[Key]] ): string; formatPattern( key: Key, args: LocalesMap[Key] extends never ? null : LocalesMap[Key], errors?: Error[] | null ): string; } // @filename: index.ts // @errors: 2554 // @moduleResolution: NodeNext // @module: NodeNext // ---cut--- import type { TypedFluentBundle } from "./locales.types.js"; import { Bot } from "gramio"; import { i18n } from "@gramio/i18n/fluent"; const bot = new Bot(process.env.BOT_TOKEN as string) // or .extend(i18n(createFluentClient())) .extend(i18n()) .command("start", async (context) => { const firstMsg = context.t("hello-user"); // // // const secondMsg = context.t("shared-photos", { // ^? userName: "Anna", userGender: "female", photoCount: 3, }); // // // // return context.send(secondMsg); }) .onError(console.error) .onStart(console.log); bot.start(); ``` --- --- url: /tma/init-data.md --- # Init data `@gramio/init-data` is a TypeScript library for securely validating, parsing, and generating Telegram Web App init data strings. It provides a set of utilities to help you work with the Telegram Mini App authentication init data, ensuring the integrity and authenticity of user data passed from the Telegram client to your backend. The library is framework-agnostic. Key features: - Validates the authenticity of Telegram Web App init data using your bot token - Parses and exposes structured user and chat data from the init data string - Generates valid init data strings for testing and documentation - Provides strong TypeScript types for all data structures and methods [Reference](https://jsr.io/@gramio/init-data@0.0.1/doc) ### Installation ::: code-group ```bash [npm] npm install @gramio/init-data ``` ```bash [yarn] yarn add @gramio/init-data ``` ```bash [pnpm] pnpm add @gramio/init-data ``` ```bash [bun] bun install @gramio/init-data ``` ::: # getBotTokenSecretKey Get a secret key from your Telegram bot token. This secret key is required to validate the hash of the init data string sent by the Telegram Web App. Always use this method to generate the secret key before validating or signing init data if you wants reach maximum performance. If you don't provide it to validate init data methods it will execute it on **every call**. ```ts import { getBotTokenSecretKey } from "@gramio/init-data"; const botToken = process.env.BOT_TOKEN as string; const secretKey = getBotTokenSecretKey(botToken); // use it later ``` # validateAndParseInitData Verifies the authenticity of the Telegram Web App init data string using the secret key or bot token. If the data is valid, it parses and returns the [WebAppInitData](https://core.telegram.org/bots/webapps#webappinitdata) object. If the data is invalid, it returns `false`. ```ts import { validateAndParseInitData, getBotTokenSecretKey, } from "@gramio/init-data"; const botToken = process.env.BOT_TOKEN as string; const secretKey = getBotTokenSecretKey(botToken); const initData = req.headers["x-init-data"] as string; const result = validateAndParseInitData(initData, secretKey); if (!result) { // Handle invalid or tampered data } // Access user and chat data const userId = result.user?.id; const chatId = result.chat?.id; ``` # validateInitData Checks the authenticity of the Telegram Web App init data string using the secret key or bot token. Returns `true` if the data is valid, otherwise returns `false`. This method only validates the data and does **not parse** it. Use this when you only need to check authenticity without extracting user or chat information. ```ts import { validateInitData, getBotTokenSecretKey } from "@gramio/init-data"; const botToken = process.env.BOT_TOKEN as string; const secretKey = getBotTokenSecretKey(botToken); const initData = req.headers["x-init-data"] as string; const isValid = validateInitData(initData, secretKey); if (!isValid) { // Handle invalid or tampered data } // If valid, you can safely trust the init data string ``` # parseInitData Parses the Telegram Web App init data string and returns the [WebAppInitData](https://core.telegram.org/bots/webapps#webappinitdata) object. This method does not perform any validation or authenticity checks. Use it only if you have already verified the data with `validateInitData` or `validateAndParseInitData`. ```ts import { parseInitData } from "@gramio/init-data"; const initData = req.headers["x-init-data"] as string; const parsed = parseInitData(initData); // Access user and chat data const userId = parsed.user?.id; const chatId = parsed.chat?.id; ``` # signInitData Generates a valid init data string from a data object and a secret key or bot token. This is useful for testing, documentation, or generating example values for API clients. ```ts import { signInitData, getBotTokenSecretKey, type WebAppUser, } from "@gramio/init-data"; const botToken = process.env.BOT_TOKEN as string; const secretKey = getBotTokenSecretKey(botToken); const data = { user: { id: 1, first_name: "durov", username: "durov", }, } satisfies WebAppUser; const signedInitData = signInitData(data, secretKey); // Use signedInitData as a valid init data string for testing ``` # Example: Elysia integration You can use `@gramio/init-data` in any backend framework. Here is a bonus example of integrating it with Elysia for type-safe authentication: ```ts twoslash; import { validateAndParseInitData, signInitData, getBotTokenSecretKey, } from "@gramio/init-data"; import { Elysia, t } from "elysia"; const secretKey = getBotTokenSecretKey(process.env.BOT_TOKEN as string); export const authElysia = new Elysia({ name: "auth", }) .guard({ headers: t.Object({ "x-init-data": t.String({ examples: [ signInitData( { user: { id: 1, first_name: "durov", username: "durov", }, }, secretKey ), ], }), }), response: { 401: t.Literal("UNAUTHORIZED"), }, }) .resolve(({ headers, error }) => { const result = validateAndParseInitData( headers["x-init-data"], secretKey ); if (!result || !result.user) return error("Unauthorized", "UNAUTHORIZED"); return { tgId: result.user.id, user: result.user, }; }) .as("plugin"); const app = new Elysia() .get("hello", "world") .use(authElysia) .get("/user", ({ user }) => { user; // ^? }); ``` --- --- url: /keyboards/inline-keyboard.md --- # Inline Keyboard Inline Keyboard is attached to the message. Represents an [inline keyboard](https://core.telegram.org/bots/features#inline-keyboards) that appears right next to the message it belongs to. See also [API Reference](https://jsr.io/@gramio/keyboards/doc/~/InlineKeyboard) and [how to answer to clicks](/triggers/callback-query). ## Import ### With GramIO ```ts twoslash import { InlineKeyboard } from "gramio"; ``` ### Without GramIO ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; ``` ## Buttons ([Documentation](https://core.telegram.org/bots/api/#inlinekeyboardbutton)) Buttons are methods that assemble a inline keyboard for you. ### text Text button with data to be sent in a [callback query](https://core.telegram.org/bots/api/#callbackquery) to the bot when button is pressed, 1-64 bytes. ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- new InlineKeyboard().text("some text", "payload"); // or new InlineKeyboard().text("some text", { json: "payload", }); // it uses JSON.stringify ``` ### url HTTP or tg:// URL to be opened when the button is pressed. Links `tg://user?id=` can be used to mention a user by their identifier without using a username, if this is allowed by their privacy settings. ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- new InlineKeyboard().url("GitHub", "https://github.com/gramiojs/gramio"); ``` ### webApp Description of the [Web App](https://core.telegram.org/bots/webapps) that will be launched when the user presses the button. The Web App will be able to send an arbitrary message on behalf of the user using the method [answerWebAppQuery](https://core.telegram.org/bots/api/#answerwebappquery). Available only in private chats between a user and the bot. ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- new InlineKeyboard().webApp("some text", "https://..."); ``` ### copy Type of button that copies the specified text to the clipboard. ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- new InlineKeyboard().copy( "Copy me", "Welcome to Gboard clipboard, any text you copy will be saved here. Tap on a clip to paste it in the text box. Use the edit icon to pin, add or delete clips. Touch and hold a clip to pin it. Unpinned clips will be deleted after 1 hour." ); ``` ### login This inline keyboard button used to automatically authorize a user. Serves as a great replacement for the [Telegram Login Widget](https://core.telegram.org/widgets/login) when the user is coming from Telegram. All the user needs to do is tap/click a button and confirm that they want to log in: Telegram apps support these buttons as of [version 5.7](https://telegram.org/blog/privacy-discussions-web-bots#meet-seamless-web-bots). Sample bot: [@discussbot](https://t.me/discussbot) ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- new InlineKeyboard().login("some text", "https://..."); // or new InlineKeyboard().login("some text", { url: "https://...", request_write_access: true, }); ``` More about options in [documentation](https://core.telegram.org/bots/api/#loginurl) ### pay Send a [Pay button](https://core.telegram.org/bots/api/#payments). ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- new InlineKeyboard().pay("5 coins"); ``` > [!WARNING] > This type of button **must** always be the first button in the first row and can only be used in invoice messages. ### switchToChat Pressing the button will prompt the user to select one of their chats, open that chat and insert the bot's username and the specified inline query in the input field. By default empty, in which case just the bot's username will be inserted. ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- new InlineKeyboard().switchToChat("Select chat"); // or new InlineKeyboard().switchToChat("Select chat", "InlineQuery"); ``` ### switchToChosenChat Pressing the button will prompt the user to select one of their chats of the specified type, open that chat and insert the bot's username and the specified inline query in the input field. ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- new InlineKeyboard().switchToChosenChat("Select chat"); // or new InlineKeyboard().switchToChosenChat("Select chat", "InlineQuery"); // or new InlineKeyboard().switchToChosenChat("Select chat", { query: "InlineQuery", allow_channel_chats: true, allow_group_chats: true, allow_bot_chats: true, allow_user_chats: true, }); ``` More about options in [documentation](https://core.telegram.org/bots/api/#switchinlinequerychosenchat) ### switchToCurrentChat Pressing the button will insert the bot's username and the specified inline query in the current chat's input field. May be empty, in which case only the bot's username will be inserted. This offers a quick way for the user to open your bot in inline mode in the same chat - good for selecting something from multiple options. ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- new InlineKeyboard().switchToChosenChat("Open Inline mod"); // or new InlineKeyboard().switchToChosenChat("Open Inline mod", "InlineQuery"); ``` ### game Description of the game that will be launched when the user presses the button. ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- new InlineKeyboard().game("text", {}); // ??? no params... ``` > [!WARNING] > This type of button **must** always be the first button in the first row. ## Helpers Methods that help you build a keyboard. ### row Adds a `line break`. Call this method to make sure that the next added buttons will be on a new row. ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- new InlineKeyboard() .text("first row", "payload") .row() .text("second row", "payload"); ``` ### columns Allows you to limit the number of columns in the keyboard. ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- new InlineKeyboard() .columns(1) .text("first row", "payload") .text("second row", "payload") .text("third row", "payload"); ``` ### wrap A custom handler that controls row wrapping. ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- new InlineKeyboard() .wrap(({ button }) => button.callback_data === "2") .text("first row", "1") .text("first row", "1") .text("second row", "2"); ``` handler is ```ts (options: { button: T; index: number; row: T[]; rowIndex: number }) => boolean; ``` ### pattern An array with the number of columns per row. Allows you to set a «template». ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- new InlineKeyboard() .pattern([1, 3, 2]) .text("1", "payload") .text("2", "payload") .text("2", "payload") .text("2", "payload") .text("3", "payload") .text("3", "payload"); ``` ### filter A handler that helps filter keyboard buttons. ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- new InlineKeyboard() .filter(({ button }) => button.callback_data !== "hidden") .text("button", "pass") .text("button", "hidden") .text("button", "pass"); ``` ### add Allows you to add multiple buttons in _raw_ format. ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- const labels = ["some", "buttons"]; new InlineKeyboard() .add({ text: "raw button", callback_data: "payload" }) .add(InlineKeyboard.text("raw button by InlineKeyboard.text", "payload")) .add(...labels.map((x) => InlineKeyboard.text(x, `${x}payload`))); ``` ### addIf Allows you to dynamically substitute buttons depending on something. ```ts twoslash // @noErrors import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- const labels = ["some", "buttons"]; const isAdmin = true; new InlineKeyboard() .addIf(1 === 2, { text: "raw button", callback_data: "payload" }) .addIf( isAdmin, InlineKeyboard.text("raw button by InlineKeyboard.text", "payload") ) .addIf( ({ index, rowIndex }) => rowIndex === index, ...labels.map((x) => InlineKeyboard.text(x, `${x}payload`)) ); ``` ### matrix Allows you to create a button matrix. ```ts twoslash // @noErrors import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- import { randomInt } from "node:crypto"; const bomb = [randomInt(0, 9), randomInt(0, 9)] as const; new InlineKeyboard().matrix(10, 10, ({ rowIndex, index }) => InlineKeyboard.text( rowIndex === bomb[0] && index === bomb[1] ? "💣" : "ㅤ", "payload" ) ); ``` The result is keyboard with a bomb on a random button handler is ```ts (options: { index: number; rowIndex: number }) => T; ``` ### combine Allows you to combine keyboards. Only keyboards are combined. You need to call the `.row()` method to line-break after combine. ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- new InlineKeyboard() .combine(new InlineKeyboard().text("first row", "payload")) .row() .combine( new InlineKeyboard() .text("second row", "payload") .row() .text("third row", "payload") ); ``` --- --- url: /triggers/inline-query.md --- # inlineQuery The `inlineQuery` method in GramIO allows your bot to respond to inline queries sent by users. An inline query is a special type of message where users can search for content from your bot by typing in the message input field without directly interacting with the bot. ![feature preview](https://core.telegram.org/file/464001466/10e4a/r4FKyQ7gw5g.134366/f2606a53d683374703) [Telegram documentation](https://core.telegram.org/bots/inline) > [!WARNING] > You should enable this options via [@BotFather](https://telegram.me/botfather). Send the `/setinline` command, select the bot and provide the placeholder text that the user will see in the input field after typing your bot's name. ## Basic Usage ### Responding to an Inline Query with a Regular Expression You can make your bot listen for specific inline queries that match a regular expression and respond with results. Here's an example: ```ts bot.inlineQuery(/find (.*)/i, async (context) => { if (context.args) { await context.answer([ InlineQueryResult.article( "id-1", `Result for ${context.args.at(1)}`, InputMessageContent.text( `This is a message result for ${context.args.at(1)} query` ) ), ]); } }); ``` In this example: - The bot listens for inline queries that match the pattern `search (.*)`. - If a match is found, the bot extracts the search term and returns a list of inline query results. ### Parameters - **`trigger`**: The condition that the inline query must meet. This can be a regular expression, a string, or a custom function. - **Regular Expression**: Matches queries that fit a specific pattern. - **String**: Matches an exact query string. - **Function**: Evaluates the query based on custom logic. Should return true to match. - **`handler`**: The function that processes the inline query. It receives the `context` object, which includes details about the query and provides methods to respond. - **`options`**: Additional options for handling the result selection. - **`onResult`**: A function that is triggered when the user selects an inline query result. It can be used to modify or update the message after selection. Use [ChosenInlineResult](/triggers/chosen-inline-result) under the hood. > [!IMPORTANT] > You can modify only messages which contains InlineKeyboard. More about [ChosenInlineResult](/triggers/chosen-inline-result). ### How `inlineQuery` Works 1. **Query Matching**: When a user types an inline query, the `inlineQuery` method checks if the query matches the provided `trigger`. This can be a direct match (string), a pattern match (regular expression), or a condition check (function). 2. **Handling the Query**: If the query matches, the `handler` function is called. Inside this function, you can access the query parameters, generate results, and send them back to the user using `context.answer()`. 3. **Responding to Result Selection**: If the `onResult` option is provided, the bot listens for when a user selects one of the inline query results. The provided function can then modify the message, for example, by editing the text. ### Example: Custom Inline Query Handler Here's a more detailed example that demonstrates using both the inline query trigger and handling result selection: ```ts bot.inlineQuery( /find (.*)/i, async (context) => { if (context.args) { await context.answer( [ InlineQueryResult.article( "result-1", `Result for ${context.args[1]}`, InputMessageContent.text( `You searched for: ${context.args[1]}` ), { reply_markup: new InlineKeyboard().text( "Get Details", "details-callback" ), } ), ], { cache_time: 0, } ); } }, { onResult: (context) => context.editText("You selected a result!"), } ); ``` In this example: - The bot listens for inline queries that start with `find` followed by a search term. - The bot responds with an inline query result that includes a button labeled "Get Details." - When the user selects this result, the bot edits the message to indicate that a selection was made. --- --- url: /keyboards/overview.md --- # Overview [`@gramio/keyboards`](https://github.com/gramiojs/keyboards) is built-in GramIO package. You can also use it outside of this framework because it is framework-agnostic. See also [API Reference](https://jsr.io/@gramio/keyboards/doc). ### Installation (not required for GramIO users) ::: code-group ```bash [npm] npm install @gramio/keyboards ``` ```bash [yarn] yarn add @gramio/keyboards ``` ```bash [pnpm] pnpm add @gramio/keyboards ``` ```bash [bun] bun install @gramio/keyboards ``` ::: ## Usage ::: code-group ```ts twoslash [with GramIO] import { Keyboard } from "gramio"; const keyboard = new Keyboard().text("first row").row().text("second row"); ``` ```ts twoslash [without GramIO] import { Keyboard } from "@gramio/keyboards"; const keyboard = new Keyboard() .text("first row") .row() .text("second row") .build(); ``` ::: ### Send via [GramIO](https://gramio.dev/) ```ts import { Bot, Keyboard } from "gramio"; // import from GramIO package!! const bot = new Bot(process.env.BOT_TOKEN as string); const data = ["Apple", "Realme", "Tesla", "Xiaomi"]; bot.on("message", (ctx) => { return ctx.send("test", { reply_markup: new Keyboard() .columns(1) .text("simple keyboard") .add(...data.map((x) => Keyboard.text(x))) .filter(({ button }) => button.text !== "Tesla"), }); }); bot.start(); ``` ### Send via [Grammy](https://grammy.dev/) ```ts import { Keyboard } from "@gramio/keyboards"; import { Bot } from "grammy"; const bot = new Bot(process.env.BOT_TOKEN as string); const data = ["Apple", "Realme", "Tesla", "Xiaomi"]; bot.on("message", (ctx) => { return ctx.reply("test", { reply_markup: new Keyboard() .columns(1) .text("simple keyboard") .add(...data.map((x) => Keyboard.text(x))) .filter(({ button }) => button.text !== "Tesla") .build(), }); }); bot.start(); ``` ### Send via [Telegraf](https://github.com/telegraf/telegraf) > [!WARNING] > The `Telegraf` does not support the latest version of Bot API ```ts import { Keyboard } from "@gramio/keyboards"; import { Telegraf } from "telegraf"; const bot = new Telegraf(process.env.BOT_TOKEN as string); const data = ["Apple", "Realme", "Tesla", "Xiaomi"]; bot.on("message", (ctx) => { return ctx.reply("test", { reply_markup: new Keyboard() .columns(1) .text("simple keyboard") .add(...data.map((x) => Keyboard.text(x))) .filter(({ button }) => button.text !== "Tesla") .build(), }); }); bot.launch(); ``` ### Send via [node-telegram-bot-api](https://www.npmjs.com/package/node-telegram-bot-api) > [!WARNING] > The `node-telegram-bot-api` does not support the latest version of Bot API and the types are badly written, so the types may not match ```ts import { Keyboard } from "@gramio/keyboards"; import TelegramBot from "node-telegram-bot-api"; const bot = new TelegramBot(process.env.TOKEN as string, { polling: true }); const data = ["Apple", "Realme", "Tesla", "Xiaomi"]; bot.on("message", (msg) => { return bot.sendMessage(msg.chat.id, "test", { reply_markup: new Keyboard() .columns(1) .text("simple keyboard") .add(...data.map((x) => Keyboard.text(x))) .filter(({ button }) => button.text !== "Tesla") .build(), }); }); ``` ### Send via [puregram](https://puregram.cool/) > [!WARNING] > The `puregram` does not support the latest version of Bot API ```ts import { Telegram } from "puregram"; import { Keyboard } from "@gramio/keyboards"; const bot = new Telegram({ token: process.env.TOKEN as string, }); const data = ["Apple", "Realme", "Tesla", "Xiaomi"]; bot.on("message", (ctx) => { return ctx.send("test", { reply_markup: new Keyboard() .columns(1) .text("simple keyboard") .add(...data.map((x) => Keyboard.text(x))) .filter(({ button }) => button.text !== "Tesla") .build(), }); }); bot.updates.startPolling(); ``` #### Result ```json { "keyboard": [ [ { "text": "simple keyboard" } ], [ { "text": "Apple" } ], [ { "text": "Realme" } ], [ { "text": "Xiaomi" } ] ], "one_time_keyboard": false, "is_persistent": false, "selective": false, "resize_keyboard": true } ``` ![image](https://github.com/gramiojs/keyboards/assets/57632712/e65e2b0a-40f0-43ae-9887-04360e6dbeab) --- --- url: /plugins/lazy-load.md --- # Lazy-Loading Plugins Plugins can be **lazy-loaded** if they are in an **asynchronous** function. Such plugins are always connected at the very **last** moment (during the **.start** call). If you want to call it **earlier**, put **await** in front of it. ## Example ```ts const bot = new Bot(process.env.BOT_TOKEN as string) .extend(autoload()) // autoload is async .command("start", () => { // this command registered BEFORE than autoload }); bot.start(); // autoload is loaded here ``` You can fix that with **await**. ```ts const bot = new Bot(process.env.BOT_TOKEN as string) .extend(await autoload()) // autoload is async but we await it .command("start", () => { // this command registered AFTER than autoload }); bot.start(); ``` for now it works as expected! --- --- url: /plugins/official/media-cache.md --- # Media cache plugin
[![npm](https://img.shields.io/npm/v/@gramio/media-cache?logo=npm&style=flat&labelColor=000&color=3b82f6)](https://www.npmjs.org/package/@gramio/media-cache) [![JSR](https://jsr.io/badges/@gramio/media-cache)](https://jsr.io/@gramio/media-cache) [![JSR Score](https://jsr.io/badges/@gramio/media-cache/score)](https://jsr.io/@gramio/media-cache)
`Media cache` plugin for [GramIO](https://gramio.dev/). This plugin caches the sent `file_id`'s and prevents files from being uploaded again. Currently, **sendMediaGroup** is not cached. ## Usage ```ts import { Bot } from "gramio"; import { mediaCache } from "@gramio/media-cache"; const bot = new Bot(process.env.BOT_TOKEN as string) .extend(mediaCache()) .command("start", async (context) => { return context.sendDocument( await MediaUpload.url( "https://raw.githubusercontent.com/gramiojs/types/main/README.md" ) ); }) .onStart(console.log); bot.start(); ``` --- --- url: /plugins/official/media-group.md --- # Media group plugin
[![npm](https://img.shields.io/npm/v/@gramio/media-group?logo=npm&style=flat&labelColor=000&color=3b82f6)](https://www.npmjs.org/package/@gramio/media-group) [![JSR](https://jsr.io/badges/@gramio/media-group)](https://jsr.io/@gramio/media-group) [![JSR Score](https://jsr.io/badges/@gramio/media-group/score)](https://jsr.io/@gramio/media-group)
This plugin collects `mediaGroup` from messages (**1** attachment = **1** message) using a **delay** if `mediaGroupId` is in the **MessageContext**, pass only **first** message further down the middleware chain with the `mediaGroup` key, which contains an **array** of messages of this **mediaGroup** (it also contains the **first** message). ```ts import { Bot } from "gramio"; import { mediaGroup } from "@gramio/media-group"; const bot = new Bot(process.env.BOT_TOKEN as string) .extend(mediaGroup()) .on("message", async (context) => { if (!context.mediaGroup) return; return context.send( `Caption from the first message - ${context.caption}. MediaGroup contains ${context.mediaGroup.length} attachments` ); }) .onStart(({ info }) => console.log(`✨ Bot ${info.username} was started!`)); bot.start(); ``` ### Installation ::: code-group ```bash [npm] npm install @gramio/media-group ``` ```bash [yarn] yarn add @gramio/media-group ``` ```bash [pnpm] pnpm add @gramio/media-group ``` ```bash [bun] bun install @gramio/media-group ``` ::: ### Setup You can change the duration of the delay in milliseconds by simply passing it like this: ```typescript const bot = new Bot(process.env.BOT_TOKEN as string) .extend(mediaGroup(1000)) // wait 1 second for message with mediaGroupId (refreshed after new message with it) .on("message", async (context) => { if (!context.mediaGroup) return; return context.send( `Caption from the first message - ${context.caption}. MediaGroup contains ${context.mediaGroup.length} attachments` ); }) .onStart(({ info }) => console.log(`✨ Bot ${info.username} was started!`)); bot.start(); ``` By default it `150 ms`. --- --- url: /files/media-input.md --- # Media Input Class-helper with static methods that represents the content of a media message to be sent. [API Reference](https://jsr.io/@gramio/files/doc) [Documentation](https://core.telegram.org/bots/api/#inputmedia) ## document Represents a general file to be sent. ```ts twoslash // @errors: 2345 import { BotLike, MessageContext } from "gramio"; import { MediaInput, MediaUpload } from "@gramio/files"; const ctx = {} as InstanceType>; // ---cut--- // ctx from bot.on("message", (ctx) => ...) ctx.sendMediaGroup([ MediaInput.document( await MediaUpload.url( "https://raw.githubusercontent.com/gramiojs/types/main/README.md" ) ), MediaInput.document(await MediaUpload.path("./package.json")), ]); ``` [Documentation](https://core.telegram.org/bots/api/#inputmediadocument) ## audio Represents an audio file to be treated as music to be sent. ```ts twoslash // @errors: 2345 import { BotLike, MessageContext } from "gramio"; import { MediaInput, MediaUpload } from "@gramio/files"; const ctx = {} as InstanceType>; // ---cut--- // ctx from bot.on("message", (ctx) => ...) ctx.sendMediaGroup([ MediaInput.audio(await MediaUpload.url("https://.../music.mp3")), MediaInput.audio(await MediaUpload.path("./music.mp3")), ]); ``` [Documentation](https://core.telegram.org/bots/api/#inputmediaaudio) ## photo Represents a photo to be sent. ```ts twoslash // @errors: 2345 import { BotLike, MessageContext } from "gramio"; import { MediaInput, MediaUpload } from "@gramio/files"; const ctx = {} as InstanceType>; // ---cut--- // ctx from bot.on("message", (ctx) => ...) ctx.sendMediaGroup([ MediaInput.photo( await MediaUpload.url( "https://w7.pngwing.com/pngs/140/552/png-transparent-kitten-if-cats-could-talk-the-meaning-of-meow-pet-sitting-dog-pet-dog-mammal-animals-cat-like-mammal.png" ), { has_spoiler: true, caption: "MaybeCat" } ), MediaInput.photo(await MediaUpload.path("./no-cat.png")), ]); ``` [Documentation](https://core.telegram.org/bots/api/#inputmediaphoto) ## video Represents a video to be sent. ```ts twoslash // @noErrors import { BotLike, MessageContext } from "gramio"; import { MediaInput, MediaUpload } from "@gramio/files"; const ctx = {} as InstanceType>; // ---cut--- // ctx from bot.on("message", (ctx) => ...) ctx.sendMediaGroup([ MediaInput.video(await MediaUpload.url("https://.../video.mp4"), { has_spoiler: true, thumbnail: MediaUpload.buffer(/**file buffer */), }), MediaInput.photo(await MediaUpload.path("./cat-walk.mp4")), ]); ``` [Documentation](https://core.telegram.org/bots/api/#inputmediavideo) ## animation Represents an animation file (GIF or H.264/MPEG-4 AVC video without sound) to be sent. ```ts twoslash // @noErrors import { MediaInput, MediaUpload } from "@gramio/files"; // ---cut--- MediaInput.animation( await MediaUpload.url( "https://media1.tenor.com/m/47qpxBq_Tw0AAAAd/cat-cat-meme.gif" ) ); ``` [Documentation](https://core.telegram.org/bots/api/#inputmediaanimation) --- --- url: /triggers/hears.md --- # Hears The `hears` method in GramIO allows you to set up responses to specific messages. You can define what messages your bot should listen for and how it should handle when those messages are detected. The method is flexible and can work with regular expressions, exact strings, or custom functions. ## Basic Usage ### Using a `Regular Expression` matcher You can make your bot listen for messages that match a specific pattern using regular expressions. This is useful when you want your bot to respond to messages that follow a certain structure. ```ts bot.hears(/hello (.*)/i, async (context) => { if (context.args) { await context.send(`Hello, ${context.args[1]}!`); } }); ``` In this example, the bot listens for messages that start with "hello" followed by any text. When such a message is received, the bot extracts the text after "hello" and sends a personalized greeting. ### Using a `String` matcher If you want your bot to respond to a specific word or phrase, you can use a string trigger. ```ts bot.hears("start", async (context) => { await context.send("Welcome! Type 'help' to see available commands."); }); ``` In this case, the bot listens for the exact word "start" and replies with a welcome message. ### Using a `Function` matcher For more advanced scenarios, you can use a function to define the trigger. This function checks the message context and decides whether to trigger the response. ```ts bot.hears( (context) => context.user.role === "admin", async (context) => { await context.send("Hello, Admin! How can I assist you?"); } ); ``` Here, the bot checks if the user is an admin. If so, it sends a special message for admins. --- --- url: /hooks/on-error.md --- # onError (Error Handling) Errors can occur in middleware, and we need to handle them. That's exactly what the `onError` hook is designed for. ```ts twoslash import { Bot } from "gramio"; const bot = new Bot(""); // ---cut--- bot.on("message", () => { bot.api.sendMessage({ chat_id: "@not_found", text: "Chat not exists....", }); }); bot.onError(({ context, kind, error }) => { if (context.is("message")) return context.send(`${kind}: ${error.message}`); }); ``` ### Add hook only to specified contexts ```ts bot.onError("message", ({ context, kind, error }) => { return context.send(`${kind}: ${error.message}`); }); // or array bot.onError(["message", "message_reaction"], ({ context, kind, error }) => { return context.send(`${kind}: ${error.message}`); }); ``` ## Error kinds ### Custom You can catch an error of a certain class inherited from Error. ```ts twoslash import { Bot, format, bold } from "gramio"; // ---cut--- export class NoRights extends Error { needRole: "admin" | "moderator"; constructor(role: "admin" | "moderator") { super(); this.needRole = role; } } const bot = new Bot(process.env.BOT_TOKEN as string) .error("NO_RIGHTS", NoRights) .onError("message", ({ context, kind, error }) => { if (kind === "NO_RIGHTS") return context.send( format`You don't have enough rights! You need to have a «${bold( error.needRole // ^^^^^^^^ )}» role.` ); }); bot.on("message", (context) => { if (context.text === "ban") throw new NoRights("admin"); }); ``` > [!IMPORTANT] > We recommend following the **convention** of naming error kinds in **SCREAMING_SNAKE_CASE** ### Telegram This error is the result of a failed request to the Telegram Bot API. ```ts twoslash import { Bot } from "gramio"; const bot = new Bot(""); // ---cut--- bot.onError(({ context, kind, error }) => { if (kind === "TELEGRAM" && error.method === "sendMessage") { error.params; // is sendMessage params } }); ``` ### Unknown This error is any unknown error, whether it's your custom class or just a standard Error. ```ts twoslash import { Bot } from "gramio"; const bot = new Bot(""); // ---cut--- bot.onError(({ context, kind, error }) => { if (kind === "UNKNOWN") { console.log(error.message); } }); ``` --- --- url: /hooks/on-response.md --- # onResponse This hook is called `after receiving a successful response` from the Telegram Bot API. ## Parameters Object with: - method - API method name - params - API method parameters - response - response data ## Example ```ts twoslash import { Bot } from "gramio"; const bot = new Bot(process.env.BOT_TOKEN as string).onResponse((context) => { console.log("Response for", context.method, context.response); }); ``` ### Add hook only to specified API methods ```ts bot.onResponse("sendMessage", (context) => { console.log("Response for sendMessage", context.response); }); // or array bot.onResponse(["sendMessage", "sendPhoto"], (context) => { console.log("Response for", context.method, context.response); }); ``` --- --- url: /hooks/on-response-error.md --- # onResponseError This hook is called `after receiving an error response` from the Telegram Bot API. ## Parameters [TelegramError](https://jsr.io/@gramio/core@latest/doc/~/TelegramError) ## Example ```ts twoslash import { Bot } from "gramio"; const bot = new Bot(process.env.BOT_TOKEN as string).onResponseError( (context) => { console.log("Error for", context.method, context.message); } ); ``` ### Add hook only to specified API methods ```ts bot.onResponseError("sendMessage", (context) => { console.log("Error for sendMessage", context.message); }); // or array bot.onResponseError(["sendMessage", "sendPhoto"], (context) => { console.log("Error for", context.method, context.message); }); ``` --- --- url: /hooks/on-start.md --- # onStart This hook is called when the bot `starts`. ## Parameters ```ts { plugins: string[]; info: TelegramUser; updatesFrom: "webhook" | "long-polling"; } ``` - plugins - list of plugins - info - bot account info - updatesFrom - webhook/polling ## Example ```ts twoslash import { Bot } from "gramio"; const bot = new Bot(process.env.BOT_TOKEN as string).onStart( ({ plugins, info, updatesFrom }) => { console.log(`plugin list - ${plugins.join(", ")}`); console.log(`bot username @${info.username}`); console.log(`updates from ${updatesFrom}`); } ); bot.start(); ``` --- --- url: /hooks/on-stop.md --- # onStop This hook is called when the bot is stopped. ## Parameters ```ts { plugins: string[]; info: TelegramUser; } ``` - plugins - list of plugins - info - bot account info ## Example ```ts twoslash // @noErrors import { Bot } from "gramio"; const bot = new Bot(process.env.BOT_TOKEN as string).onStop( ({ plugins, info }) => { console.log(`plugin list - ${plugins.join(", ")}`); console.log(`bot username @${info.username}`); } ); bot.start(); bot.stop(); ``` --- --- url: /hooks/pre-request.md --- # preRequest This hook is called before sending a request to the Telegram Bot API (allowing you to modify the parameters being sent). ## Parameters - method - API method name - params - API method parameters > [!IMPORTANT] > You must return the context from the hook handler! ## Example ```ts twoslash import { Bot } from "gramio"; const bot = new Bot(process.env.BOT_TOKEN as string).preRequest((context) => { if (context.method === "sendMessage") { context.params.text = "changed parameters"; } return context; }); bot.start(); ``` ### Add hook only to specified API methods ```ts bot.preRequest("sendMessage", (context) => { context.params.text = "modified text"; return context; }); // or array bot.preRequest(["sendMessage", "sendPhoto"], (context) => { if (context.method === "sendMessage") { context.params.text = "modified text"; } else { context.params.caption = "this is a photo caption from sendPhoto method"; } return context; }); ``` --- --- url: /plugins/official/prompt.md --- # Prompt Plugin
[![npm](https://img.shields.io/npm/v/@gramio/prompt?logo=npm&style=flat&labelColor=000&color=3b82f6)](https://www.npmjs.org/package/@gramio/prompt) [![JSR](https://jsr.io/badges/@gramio/prompt)](https://jsr.io/@gramio/prompt) [![JSR Score](https://jsr.io/badges/@gramio/prompt/score)](https://jsr.io/@gramio/prompt)
A plugin that provides [Prompt](#prompt) and [Wait](#wait) methods ### Installation ::: code-group ```bash [npm] npm install @gramio/prompt ``` ```bash [yarn] yarn add @gramio/prompt ``` ```bash [pnpm] pnpm add @gramio/prompt ``` ```bash [bun] bun install @gramio/prompt ``` ::: ## Usage ```ts import { Bot, format, bold } from "gramio"; import { prompt } from "@gramio/prompt"; const bot = new Bot(process.env.BOT_TOKEN as string) .extend(prompt()) .command("start", async (context) => { const answer = await context.prompt( "message", format`What's your ${bold`name`}?` ); return context.send(`✨ Your name is ${answer.text}`); }) .onStart(console.log); bot.start(); ``` ## Prompt ### Prompt with text + params ```ts const answer = await context.prompt("What's your name?"); // or with SendMessageParams const answer = await context.prompt("True or false?", { reply_markup: new Keyboard().text("true").row().text("false"), }); ``` answer is `MessageContext` or `CallbackQueryContext` ### Prompt with text + params and the specified event ```ts const answer = await context.prompt("message", "What's your name?"); const answer = await context.prompt("callback_query", "True or false?", { reply_markup: new InlineKeyboard() .text("true", "true") .row() .text("false", "false"), }); ``` answer is `CallbackQueryContext` ### Validation You can define a handler in params to validate the user's answer. If handler returns false, the message will be repeated. ```ts const answer = await context.prompt( "message", "Enter a string that contains russian letter", { validate: (context) => /[а-яА-Я]/.test(context.text), //... and some SendMessageParams } ); ``` ### Transform ```ts const name = await context.prompt( "message", format`What's your ${bold`name`}?`, { transform: (context) => context.text || context.caption || "", } ); ``` name is `string` ## Wait ### Wait for the next event from the user ```ts const answer = await context.wait(); ``` answer is `MessageContext` or `CallbackQueryContext` ### Wait for the next event from the user ignoring events not listed ```ts const answer = await context.wait("message"); ``` answer is `CallbackQueryContext` ### Wait for the next event from the user ignoring non validated answers You can define a handler in params to validate the user's answer. If handler return `false`, the **message** will be ignored ```ts const answer = await context.wait((context) => /[а-яА-Я]/.test(context.text)); // or combine with event const answer = await context.wait("message", (context) => /[а-яА-Я]/.test(context.text) ); ``` ### Wait for the next event from the user ignoring non validated answers with transformer You can define a handler in params to **transform** the user's answer. ```ts const answer = await context.wait((context) => /[а-яА-Я]/.test(context.text)); // or combine with event const answer = await context.wait("message", { validate: (context) => /[а-яА-Я]/.test(context.text), transform: (context) => c.text || "", }); ``` answer is `string` --- --- url: /keyboards/remove-keyboard.md --- # Remove Keyboard Upon receiving a message with this object, Telegram clients will remove the current custom keyboard and display the default letter-keyboard. By default, custom keyboards are displayed until a new keyboard is sent by a bot. An exception is made for one-time keyboards that are hidden immediately after the user presses a button (see [ReplyKeyboardMarkup](https://core.telegram.org/bots/api/#replykeyboardmarkup)). See also [API Reference](https://jsr.io/@gramio/keyboards/doc/~/RemoveKeyboard). ## Import ### With GramIO ```ts twoslash import { RemoveKeyboard } from "gramio"; ``` ### Without GramIO ```ts twoslash import { RemoveKeyboard } from "@gramio/keyboards"; ``` ## Options ([Documentation](https://core.telegram.org/bots/api/#replykeyboardremove)) These parameters are responsible for the settings of the removal buttons ### selective Use this parameter if you want to remove the keyboard for specific users only. Targets: 1. users that are \@mentioned in the _text_ of the [Message](https://core.telegram.org/bots/api/#message) object. 2. if the bot's message is a reply to a message in the same chat and forum topic, sender of the original message. Example: A user votes in a poll, bot returns confirmation message in reply to the vote and removes the keyboard for that user, while still showing the keyboard with poll options to users who haven't voted yet. ```ts twoslash import { RemoveKeyboard } from "@gramio/keyboards"; // ---cut--- new RemoveKeyboard().selective(); // to enable new RemoveKeyboard().selective(false); // to disable ``` --- --- url: /keyboards/keyboard.md --- # Keyboard This keyboard is shown under the input field and also known as Reply/Custom Keyboard. Represents a [custom keyboard](https://core.telegram.org/bots/features#keyboards) with reply options (see [Introduction to bots](https://core.telegram.org/bots/features#keyboards) for details and examples). See also [API Reference](https://jsr.io/@gramio/keyboards/doc/~/Keyboard) ## Import ### With GramIO ```ts twoslash import { Keyboard } from "gramio"; ``` ### Without GramIO ```ts twoslash import { Keyboard } from "@gramio/keyboards"; ``` ## Buttons ([Documentation](https://core.telegram.org/bots/api/#keyboardbutton)) Buttons are methods that assemble a keyboard for you. ### text Text button. It will be sent as a message when the button is pressed ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard().text("some button text"); ``` ### requestUsers Request users button. Pressing the button will open a list of suitable users. Identifiers of selected users will be sent to the bot in a `users_shared` service message. Available in private chats only. `Second parameter` is signed 32-bit identifier of the request that will be received back in the [UsersShared](https://core.telegram.org/bots/api/#usersshared) object. Must be unique within the message ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard().requestUsers("some button text", 228, { user_is_premium: true, }); ``` More about options in [documentation](https://core.telegram.org/bots/api/#keyboardbuttonrequestusers) ### requestChats Request users button. Pressing the button will open a list of suitable chats. Tapping on a chat will send its identifier to the bot in a `chat_shared` service message. Available in private chats only.. Available in private chats only. `Second parameter` is signed 32-bit identifier of the request, which will be received back in the [ChatShared](https://core.telegram.org/bots/api/#chatshared) object. Must be unique within the message ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard().requestChat("gramio", 255, { chat_is_forum: true, }); ``` > [!WARNING] > By default, the `chat_is_channel` parameter is false! More about options in [documentation](https://core.telegram.org/bots/api/#keyboardbuttonrequestchat) ### requestPoll Request poll button. Pressing the button will open a list of suitable users. Identifiers of selected users will be sent to the bot in a `users_shared` service message. Available in private chats only. ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard().requestPoll("some button text", "quiz"); ``` More about options in [documentation](https://core.telegram.org/bots/api/#keyboardbuttonpolltype) ### requestLocation Request user's location button. The user's current location will be sent when the button is pressed. Available in private chats only. ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard().requestLocation("some button text"); ``` ### requestContact Request contact button. The user's phone number will be sent as a contact when the button is pressed. Available in private chats only. ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard().requestContact("some button text"); ``` ### webApp webApp button. Described [Web App](https://core.telegram.org/bots/webapps) will be launched when the button is pressed. The Web App will be able to send a `web_app_data` service message. Available in private chats only. ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard().webApp("some button text", "https://..."); ``` ## Options ([Documentation](https://core.telegram.org/bots/api/#replykeyboardmarkup)) These parameters are responsible for the settings of the buttons ### resized Requests clients to resize the keyboard vertically for optimal fit (e.g., make the keyboard smaller if there are just two rows of buttons). If `false`, in which case the custom keyboard is always of the same height as the app's standard keyboard. Defaults to `true`. ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard().text("some text").resized(); // to enable new Keyboard().text("some text").resized(false); // to disable ``` > [!WARNING] > The buttons are resized by default! To cancel this, use `.resized(false)` ### oneTime Requests clients to hide the keyboard as soon as it's been used. The keyboard will still be available, but clients will automatically display the usual letter-keyboard in the chat - the user can press a special button in the input field to see the custom keyboard again. Defaults to `false`. ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard().text("some text").oneTime(); // to enable new Keyboard().text("some text").oneTime(false); // to disable ``` ### persistent Requests clients to always show the keyboard when the regular keyboard is hidden. Defaults to `false`, in which case the custom keyboard can be hidden and opened with a keyboard icon. Defaults to `false`. ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard().text("some text").persistent(); // to enable new Keyboard().text("some text").persistent(false); // to disable ``` ### persistent Use this parameter if you want to show the keyboard to specific users only. Targets: 1. users that are \@mentioned in the _text_ of the [Message](https://core.telegram.org/bots/api/#message) object. 2. if the bot's message is a reply to a message in the same chat and forum topic, sender of the original message. _Example:_ A user requests to change the bot's language, bot replies to the request with a keyboard to select the new language. Other users in the group don't see the keyboard. Defaults to `false`. ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard().text("some text").selective(); // to enable new Keyboard().text("some text").selective(false); // to disable ``` ### placeholder The placeholder to be shown in the input field when the keyboard is active. 1-64 characters. Defaults to `not to be`. ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard().text("some text").placeholder("some text"); // to enable new Keyboard().text("some text").placeholder(); // to disable ``` ## Helpers Methods that help you build a keyboard. ### row Adds a `line break`. Call this method to make sure that the next added buttons will be on a new row. ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard().text("first row").row().text("second row"); ``` ### columns Allows you to limit the number of columns in the keyboard. ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard() .columns(1) .text("first row") .text("second row") .text("third row"); ``` ### wrap A custom handler that controls row wrapping. ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard() .wrap(({ button }) => button.text === "second row") .text("first row") .text("first row") .text("second row"); ``` handler is ```ts (options: { button: T; index: number; row: T[]; rowIndex: number }) => boolean; ``` ### pattern An array with the number of columns per row. Allows you to set a «template». ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard() .pattern([1, 3, 2]) .text("1") .text("2") .text("2") .text("2") .text("3") .text("3"); ``` ### filter A handler that helps filter keyboard buttons. ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard() .filter(({ button }) => button.text !== "hidden") .text("pass") .text("hidden") .text("pass"); ``` ### add Allows you to add multiple buttons in _raw_ format. ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- const labels = ["some", "buttons"]; new Keyboard() .add({ text: "raw button" }) .add(Keyboard.text("raw button by Keyboard.text")) .add(...labels.map((x) => Keyboard.text(x))); ``` ### addIf Allows you to dynamically substitute buttons depending on something. ```ts twoslash // @noErrors import { Keyboard } from "@gramio/keyboards"; // ---cut--- const labels = ["some", "buttons"]; const isAdmin = true; new Keyboard() .addIf(1 === 2, { text: "raw button" }) .addIf(isAdmin, Keyboard.text("raw button by Keyboard.text")) .addIf( ({ index, rowIndex }) => rowIndex === index, ...labels.map((x) => Keyboard.text(x)) ); ``` handler is ```ts (options: { rowIndex: number; index: number }) => boolean; ``` ### matrix Allows you to create a button matrix. ```ts twoslash // @noErrors import { Keyboard } from "@gramio/keyboards"; // TODO: remove no errors // ---cut--- import { randomInt } from "node:crypto"; const bomb = [randomInt(0, 9), randomInt(0, 9)] as const; new Keyboard().matrix(10, 10, ({ rowIndex, index }) => Keyboard.text(rowIndex === bomb[0] && index === bomb[1] ? "💣" : "ㅤ") ); ``` The result is keyboard with a bomb on a random button handler is ```ts (options: { index: number; rowIndex: number }) => T; ``` ### combine Allows you to combine keyboards. Only keyboards are combined. You need to call the `.row()` method to line-break after combine. ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard() .combine(new Keyboard().text("first")) .row() .combine(new Keyboard().text("second").row().text("third")); ``` --- --- url: /plugins/official/scenes.md --- # @gramio/scenes
[![npm](https://img.shields.io/npm/v/@gramio/scenes?logo=npm&style=flat&labelColor=000&color=3b82f6)](https://www.npmjs.org/package/@gramio/scenes) [![JSR](https://jsr.io/badges/@gramio/scenes)](https://jsr.io/@gramio/scenes) [![JSR Score](https://jsr.io/badges/@gramio/scenes/score)](https://jsr.io/@gramio/scenes)
The API can be changed a little, but we already use it in production environment. # Usage ```ts twoslash import { Bot } from "gramio"; import { scenes, Scene } from "@gramio/scenes"; export const greetingScene = new Scene("greeting") .params<{ test: boolean }>() .step("message", (context) => { if (context.scene.step.firstTime) return context.send("Hi! What's your name?"); if (!context.text) return context.send("Please write your name"); return context.scene.update({ name: context.text, }); }) .step("message", (context) => { if (context.scene.step.firstTime) return context.send("How old are you?"); const age = Number(context.text); if (!age || Number.isNaN(age) || age < 0) return context.send("Please write your age correctly"); return context.scene.update({ age, }); }) .step("message", async (context) => { await context.send( `Nice to meet you! I now know that your name is ${ context.scene.state.name } and you are ${context.scene.state.age} years old. ${ context.scene.params.test ? "Also you have test param!" : "" // ^? }` ); return context.scene.exit(); }); const bot = new Bot(process.env.TOKEN as string) .extend(scenes([greetingScene])) .command("start", async (context) => { return context.scene.enter(greetingScene, { test: true, }); }); ``` ### Share state between steps ```ts twoslash import { Scene } from "@gramio/scenes"; const testScene = new Scene("test") .step("message", async (context) => { if (context.scene.step.firstTime || context.text !== "1") return context.send("1"); return context.scene.update({ messageId: context.id, some: "hii!" as const, }); }) .step("message", async (context) => { if (context.scene.step.firstTime || context.text !== "2") return context.send("2"); console.log(context.scene.state.messageId); // ^? }); ``` ## on This method allows you to register event handlers for a scene. ```ts const guessRandomNumberScene = new Scene("guess-random-number") .params<{ randomNumber: number }>() .on("message", async (context, next) => { // This check is needed so the handler does not trigger on firstTime because context will be the same as previous step if (context.scene.step.firstTime) return next(); return await Promise.all([context.delete(), next()]); }) .step(["message", "callback_query"], async (context) => { if (context.scene.step.firstTime) return context.send("Try to guess a number from 1 to 10"); if (!context.is("message")) return context.answer("Please write a message with a number"); const number = Number(context.text); if ( Number.isNaN(number) || number !== context.scene.params.randomNumber ) return; // The handler above will delete the user's message return Promise.all([ context.send( format( `Congratulations! You guessed the number ${bold( context.scene.params.randomNumber )}!` ) ), context.scene.exit(), ]); }); ``` Keep in mind that a handler is registered only for all subsequent steps (or .on handlers) after it is declared. ```ts new Scene("test") .on(...) // Called for all steps .step(...) .on(...) // Called only after the 2nd step is reached .step(...) ``` ## Storage usage ```ts import { redisStorage } from "@gramio/storage-redis"; const bot = new Bot(process.env.TOKEN as string) .extend( scenes([testScene], { storage: redisStorage(), }) ) .command("start", async (context) => { return context.scene.enter(someScene, { test: true, }); }); ``` [Read more about storages](/storages/) ## Scene context ### update `update` is a function that updates the data in the scene and preserves their types. ```ts twoslash import { Scene } from "@gramio/scenes"; const testScene = new Scene("test") .step("message", async (context) => { if (context.scene.step.firstTime) return context.send("First message"); if (!context.text) return context.delete(); return context.scene.update({ message: context.text, }); }) .step("message", async (context) => { return context.send(context.scene.state.message); }); ``` ### state `state` is an object that contains all the data that has been "collected" in this scene. (see the example above) ### params `params` is an object that contains all the data that was passed to the scene on entry. ```ts twoslash import { Bot } from "gramio"; import { scenes, Scene } from "@gramio/scenes"; const testScene = new Scene("test") // here we specify the type of scene parameters .params<{ test: boolean }>() .step("message", async (context) => { return context.send(context.scene.params.test); }); const bot = new Bot(process.env.TOKEN as string) .extend(scenes([testScene])) .command("start", async (context) => { return context.scene.enter(testScene, { test: true, }); }); ``` ### reenter `reenter` is a function that allows you to re-enter the scene at the first step, losing [state](#state). ```ts const testScene = new Scene("test") .on("message", async (context, next) => { if (context.text === "/start") return context.scene.reenter(); return next(); }) .step("message", async (context) => { if (context.scene.step.firstTime) return context.send("Hi!"); return context.send("Bye!"); }); ``` ### Step context All information about the current scene step is stored in `context.scene.step`. #### firstTime `firstTime` is a flag that indicates whether the current step execution is the first. ```ts const testScene = new Scene("test").step("message", async (context) => { if (context.scene.step.firstTime) return context.send("First message"); if (context.text !== "next") return context.send("Subsequent messages until 'next' is written"); return Promise.all([context.send("Last message"), context.scene.exit()]); }); ``` #### next `next` is a function that passes control to the next scene step. ```ts const testScene = new Scene("test").step("message", async (context) => { if (context.scene.step.firstTime) return context.send("First message"); return context.scene.next(); }); ``` #### previous `previous` is a function that passes control to the previous scene step. ```ts const testScene = new Scene("test").step("message", async (context) => { if (context.scene.step.firstTime) return context.send("First message"); return context.scene.previous(); }); ``` #### go `go` is a function that passes control to a specific scene step. ```ts const testScene = new Scene("test").step("message", async (context) => { if (context.scene.step.firstTime) return context.send("First message"); return context.scene.go(5); }); ``` #### id `id` is the identifier of the scene step. ```ts const testScene = new Scene("test").step("message", async (context) => { if (context.scene.step.firstTime) return context.send(`Step ${context.scene.step.id}`); return context.scene.exit(); }); ``` #### previousId `previousId` is the identifier of the previous scene step. The id of the last step is saved when calling [#go](#go), [#previous](#previous), [#next](#next). ```ts const testScene = new Scene("test") .step("message", async (context) => { if (context.scene.step.firstTime) return context.send( `from step ${context.scene.step.previousId} to step ${context.scene.step.id}` ); return context.scene.exit(); }) .step("message", async (context) => { return context.scene.step.go(1); }); ``` ## Scenes derives Sometimes you want to control scenes before the plugin executes scene steps but after the scene is fetched from storage. By default, the `scenes()` function derives what is needed for the next middlewares if the user is not in a scene. With `scenesDerives()` you can get it earlier and manage scene data. ```ts twoslash import { Scene } from "@gramio/scenes"; const testScene = new Scene("test").state<{ simple: string; example: number[]; }>(); // ---cut--- import { scenes, scenesDerives, type AnyScene } from "@gramio/scenes"; import { Bot } from "gramio"; import { redisStorage } from "@gramio/storage-redis"; const storage = redisStorage(); const scenesList: AnyScene[] = [testScene]; const bot = new Bot(process.env.TOKEN as string) .extend( scenesDerives(scenesList, { withCurrentScene: true, storage, }) ) .on("message", (context, next) => { if (context.text === "/start" && context.scene.current) { if (context.scene.current.is(testScene)) { console.log(context.scene.current.state); // ^? return context.scene.current.step.previous(); } else return context.scene.current.reenter(); } return next(); }) .extend( scenes(scenesList, { storage, }) ) .command("start", async (context) => { return context.scene.enter(testScene, { test: true, }); }); ``` > [!IMPORTANT] > The same **storage** and **list of scenes** should be shared across `scenes()` and `scenesDerives()` options. By default, when registering the `scenes()` plugin, `inMemoryStorage` is used. So if you need to use `scenesDerives()` to manage scenes, you must declare `inMemoryStorage` yourself and explicitly specify it in both `scenesDerives()` and `scenes()` options. ```ts import { inMemoryStorage } from "@gramio/storage"; const storage = inMemoryStorage(); // Stores in process memory and will be erased on restart const bot = new Bot(process.env.TOKEN as string) .extend(scenes([testScene], { storage })) // ... .extend(scenesDerives([testScene], { storage })); ``` > [!IMPORTANT] > Be careful. The first step of the scene should also include the event from which you entered the scene. (For example, if you enter via InlineButton click — `callback_query`) ### step This function defines a scene step. It is executed only when the current scene step id matches the registered step order. ```ts const testScene = new Scene("test") // For a single event .step("message", async (context) => { if (context.scene.step.firstTime) return context.send("First message"); return context.scene.exit(); }) // For specific events .step(["message", "callback_query"], async (context) => { if (context.scene.step.firstTime) return context.send("Second message after the user's message"); if (context.is("callback_query")) return context.answer("You pressed the button"); return context.scene.exit(); }) // For all events .step((context) => { console.log(context); return context.scene.exit(); }); ``` > [!NOTE] > If the user triggers an event that is not registered in the step, it will be ignored by the step (but any event handlers registered for it will still be called). --- --- url: /files/overview.md --- # Overview [`@gramio/files`](https://github.com/gramiojs/files) is built-in GramIO package. You can also use it outside of this framework because it is framework-agnostic. ## Usage ```ts twoslash // @errors: 2345 import { Bot, MediaInput, MediaUpload, InlineKeyboard } from "gramio"; const bot = new Bot(process.env.BOT_BOT_TOKEN as string) .on("message", async (ctx) => { ctx.sendMediaGroup([ MediaInput.document( await MediaUpload.url( "https://raw.githubusercontent.com/gramiojs/types/main/README.md" ) ), MediaInput.document(await MediaUpload.path("./package.json")), ]); }) .onStart(console.log); bot.start(); ``` ## Sending files There are three ways to send files (photos, stickers, audio, media, etc.): 1. If the file is already stored somewhere on the Telegram servers, you don't need to reupload it: each file object has a file_id field, simply pass this file_id as a parameter instead of uploading. There are no limits for files sent this way. > [!TIP] > You may find it useful to use the [media-cache](/ru/plugins/official/media-cache) plugin to cache `file_id` on the framework side. 2. Provide Telegram with an HTTP URL for the file to be sent. Telegram will download and send the file. 5 MB max size for photos and 20 MB max for other types of content. 3. Post the file using multipart/form-data in the usual way that files are uploaded via the browser. 10 MB max size for photos, 50 MB for other files. ### Sending by `file_id` - It is not possible to change the file type when resending by file_id. I.e. a video can't be sent as a photo, a photo can't be sent as a document, etc. - It is not possible to resend thumbnails. - Resending a photo by file_id will send all of its sizes. - file_id is unique for each individual bot and can't be transferred from one bot to another. - file_id uniquely identifies a file, but a file can have different valid file_ids even for the same bot. To send a file by `file_id` with GramIO, you can put it `as is`. ```ts twoslash import { BotLike, MessageContext } from "gramio"; const fileId = "" as string; const ctx = {} as InstanceType>; // ---cut--- ctx.sendPhoto(fileId); ``` ### Sending by `URL` - When sending by URL the target file must have the correct MIME type (e.g., audio/mpeg for sendAudio, etc.). - In sendDocument, sending by URL will currently only work for GIF, PDF and ZIP files. - To use sendVoice, the file must have the type audio/ogg and be no more than 1MB in size. 1-20MB voice notes will be sent as files. To send a file by `URL` with GramIO, you can put it `as is`. ```ts twoslash import { BotLike, MessageContext } from "gramio"; const ctx = {} as InstanceType>; // ---cut--- ctx.sendPhoto("https://.../cute-cat.png"); ``` ### File uploading To upload and send file you can use [`Media Upload`](/files/media-upload.html) Class-helper and GramIO will do all the work for you. ```ts twoslash // @errors: 2345 import { MediaUpload } from "@gramio/files"; import { BotLike, MessageContext } from "gramio"; const ctx = {} as InstanceType>; // ---cut--- ctx.sendPhoto(await MediaUpload.path("../cute-cat.png")); ``` #### Use [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File) In fact, GramIO can accept Web API's [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File) and [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob). So you can upload files even like this: ```ts import { Elysia } from "elysia"; import { bot } from "./bot"; new Elysia().post( "/", ({ body: { file } }) => { bot.api.sendPhoto({ chat_id: 1, photo: file, }); }, { body: t.Object({ file: t.File({ type: "image", maxSize: "2m", }), // Elysia validates and accepts only the File type }), } ); ``` Or, for example, with [Bun native File reader](https://bun.sh/docs/api/file-io#reading-files-bun-file) ```ts bot.api.sendDocument({ chat_id: 1, document: Bun.file("README.md"), }); ``` --- --- url: /plugins/official/session.md --- # Session Plugi
[![npm](https://img.shields.io/npm/v/@gramio/session?logo=npm&style=flat&labelColor=000&color=3b82f6)](https://www.npmjs.org/package/@gramio/session) [![JSR](https://jsr.io/badges/@gramio/session)](https://jsr.io/@gramio/session) [![JSR Score](https://jsr.io/badges/@gramio/session/score)](https://jsr.io/@gramio/session)
Session plugin for GramIO. **Currently not optimized and WIP.** ### Installation ::: code-group ```bash [npm] npm install @gramio/session ``` ```bash [yarn] yarn add @gramio/session ``` ```bash [pnpm] pnpm add @gramio/session ``` ```bash [bun] bun install @gramio/session ``` ::: ## Usage ```ts twoslash import { Bot } from "gramio"; import { session } from "@gramio/session"; const bot = new Bot(process.env.BOT_TOKEN as string) .extend( session({ key: "sessionKey", initial: () => ({ apple: 1 }), }) ) .on("message", (context) => { context.send(`🍏 apple count is ${++context.sessionKey.apple}`); // ^? }) .onStart(console.log); bot.start(); ``` You can use this plugin with any storage ([Read more](/storages/index)) ### Redis example [More info](https://github.com/gramiojs/storages/tree/master/packages/redis) ```ts import { Bot } from "gramio"; import { session } from "@gramio/session"; import { redisStorage } from "@gramio/storage-redis"; const bot = new Bot(process.env.BOT_TOKEN as string) .extend( session({ key: "sessionKey", initial: () => ({ apple: 1 }), storage: redisStorage(), }) ) .on("message", (context) => { context.send(`🍏 apple count is ${++context.sessionKey.apple}`); }) .onStart(console.log); bot.start(); ``` ### TypeScript To **type** a session data, you need to specify the type as the `ReturnType` of the initial function. ```ts interface MySessionData { apple: number; some?: "maybe-empty"; } bot.extend( session({ key: "sessionKey", initial: (): MySessionData => ({ apple: 1 }), }) ); ``` --- --- url: /awesome.md --- # Something about GramIO ### Bots - ... - Your bot --- --- url: /storages/index.md --- # Storages These are some kind of data stores. Their main use is in plugins (for example, [sessions](/plugins/official/session)). ## Adapters GramIO has many ready-made adapters, but you can also write your own! - [In Memory](#in-memory) (`@gramio/storage`) - [Redis](#redis) (`@gramio/storage-redis`) ## How to write my own storage adapters It is very simple to write your adapter! It is enough to return the object with the required methods and use the methods of the solution you have chosen for the adapter. (for example, `ioredis`) ```ts import type { Storage } from "@gramio/storage"; import ThirdPartyStorage, { type ThirdPartyStorageOptions } from "some-library"; export interface MyOwnStorageOptions extends ThirdPartyStorageOptions { /** add new property to options */ some?: number; } export function myOwnStorage(options: MyOwnStorageOptions = {}): Storage { const storage = new ThirdPartyStorage(options); return { async get(key) { const data = await storage.get(key); return data ? JSON.parse(data) : undefined; }, async has(key) { return storage.has(key); }, async set(key, value) { await storage.set(key, JSON.stringify(value)); }, async delete(key) { return storage.delete(key); }, }; } ``` > [!IMPORTANT] > If you want to publish your adapter, we recommend that you follow the **convention** and name it starting with `gramio-storage` and add `gramio` + `gramio-storage` keywords in your **package.json** ## How to use storage adapters in my own plugin It is also very easy to work with storage adapters in your plugin! Everything we need is already in `@gramio/storage`. ```ts import { Plugin } from "gramio"; import { type Storage, inMemoryStorage } from "@gramio/storage"; export interface MyOwnPluginOptions { storage?: Storage; } export function myOwnPlugin(options: MyOwnPluginOptions = {}) { // use in memory storage by default const storage = options.storage ?? inMemoryStorage(); return new Plugin("gramio-example"); } ``` > [!IMPORTANT] > You can scaffold this example by [create-gramio-plugin](/plugins/how-to-write.html#scaffolding-the-plugin) ## List ## In Memory
[![npm](https://img.shields.io/npm/v/@gramio/storage?logo=npm&style=flat&labelColor=000&color=3b82f6)](https://www.npmjs.org/package/@gramio/storage) [![npm downloads](https://img.shields.io/npm/dw/@gramio/storage?logo=npm&style=flat&labelColor=000&color=3b82f6)](https://www.npmjs.org/package/@gramio/storage) [![JSR](https://jsr.io/badges/@gramio/storage)](https://jsr.io/@gramio/storage) [![JSR Score](https://jsr.io/badges/@gramio/storage/score)](https://jsr.io/@gramio/storage)
##### Installation ::: code-group ```bash [npm] npm install @gramio/storage ``` ```bash [yarn] yarn add @gramio/storage ``` ```bash [pnpm] pnpm add @gramio/storage ``` ```bash [bun] bun install @gramio/storage ``` ::: ##### Usage 1. With default [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) ```ts twoslash import { inMemoryStorage } from "@gramio/storage"; const storage = inMemoryStorage(); ``` 2. Provide your own [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) ```ts twoslash import { inMemoryStorage, type InMemoryStorageMap } from "@gramio/storage"; const map: InMemoryStorageMap = new Map(); const storage = inMemoryStorage(map); ``` ## Redis
[![npm](https://img.shields.io/npm/v/@gramio/storage-redis?logo=npm&style=flat&labelColor=000&color=3b82f6)](https://www.npmjs.org/package/@gramio/storage-redis) [![npm downloads](https://img.shields.io/npm/dw/@gramio/storage-redis?logo=npm&style=flat&labelColor=000&color=3b82f6)](https://www.npmjs.org/package/@gramio/storage-redis) [![JSR](https://jsr.io/badges/@gramio/storage-redis)](https://jsr.io/@gramio/storage-redis) [![JSR Score](https://jsr.io/badges/@gramio/storage-redis/score)](https://jsr.io/@gramio/storage-redis)
##### Installation ::: code-group ```bash [npm] npm install @gramio/storage-redis ``` ```bash [yarn] yarn add @gramio/storage-redis ``` ```bash [pnpm] pnpm add @gramio/storage-redis ``` ```bash [bun] bun install @gramio/storage-redis ``` ::: ##### Usage 1. Provide ioredis options to the `redisStorage` function ```ts twoslash import { redisStorage } from "@gramio/storage-redis"; const storage = redisStorage({ host: process.env.REDIS_HOST, }); ``` 2. Provide ioredis instance to the `redisStorage` function ```ts twoslash import { redisStorage } from "@gramio/storage-redis"; import { Redis } from "ioredis"; const redis = new Redis({ host: process.env.REDIS_HOST, }); const storage = redisStorage(redis); ``` ##### Tips - You can set the `DEBUG` env to `ioredis:*` to print debug info: ```bash DEBUG=ioredis:* npm run start ``` and it will looks like this: ```bash ioredis:redis write command[::1:6379]: 0 -> get([ '@gramio/scenes:617580375' ]) +187ms ioredis:redis write command[::1:6379]: 0 -> set([ '@gramio/scenes:617580375', '{"name":"scene-name","state":{},"stepId":0,"previousStepId":0,"firstTime":false}' ]) +1ms ``` - For inspecting which data is stored in Redis, we recommend you to use GUI clients like [AnotherRedisDesktopManager](https://github.com/qishibo/AnotherRedisDesktopManager). AnotherRedisDesktopManager --- --- url: /bot-api.md --- # Bot API > [Telegram Bot API documentation](https://core.telegram.org/bots/api) [Bot API](https://core.telegram.org/bots/api) is a high-level HTTP interface to the Telegram API that makes it easy to develop bots. Under the hood, [Bot API](https://core.telegram.org/bots/api) uses TDLib (which in turn uses MTProto API). While the [Bot API](https://core.telegram.org/bots/api) can only be used to work with bot accounts, the MTProto API can be used to work with both bot and user accounts. If you need to work with MTProto, we recommend you the MtCute Logomtcute library. ## Calling the Bot API You can call Telegram Bot API methods via `bot.api` or via the shorthand methods of contexts (for example, `context.send` is `sendMessage` shorthand) ```ts twoslash import { Bot } from "gramio"; const bot = new Bot(""); // ---cut--- const response = await bot.api.sendMessage({ // ^? // // chat_id: "@gramio_forum", text: "some text", }); ``` ```ts twoslash import { Bot } from "gramio"; const bot = new Bot(""); // ---cut--- bot.on("message", (context) => context.send("This message will be sent to the current chat") ); ``` ### Suppressing errors It can be convenient to handle an error on the spot without using **try/catch** blocks. That's why the `suppress` argument was created, which you can use in **any** API method. ```ts twoslash import { Bot, TelegramError } from "gramio"; const bot = new Bot(""); // ---cut--- const response = await bot.api.sendMessage({ // ^? // // suppress: true, chat_id: "@not_found", text: "Suppressed method", }); if (response instanceof TelegramError) console.error("sendMessage returns an error..."); else console.log("Message has been sent successfully"); ``` ## Handling Rate Limits Built-in utility for 429 errors: ```ts twoslash import { Bot } from "gramio"; const bot = new Bot(""); // ---cut--- import { withRetries } from "gramio/utils"; const response = await withRetries(() => bot.api.sendMessage({ chat_id: "@gramio_forum", text: "message text", }) ); response; // ^? ``` `withRetries` handles both thrown and returned errors with automatic retry logic. ### Types GramIO re-exports [@gramio/types](https://www.npmjs.com/package/@gramio/types) (Code-generated and Auto-published Telegram Bot API types). [Read more](/types/index.html) ```ts twoslash import type { APIMethodParams, APIMethodReturn } from "gramio"; type SendMessageParams = APIMethodParams<"sendMessage">; // ^? type SendMessageParams = SendMessageParams // type GetMeReturn = APIMethodReturn<"getMe">; // ^? type GetMeReturn = TelegramUser // ``` For example you can use it in your custom function. ```ts twoslash import type { APIMethodParams } from "gramio"; // ---cut--- function myCustomSend(params: APIMethodParams<"sendMessage">) { params; // ^? } ``` #### Types for suppressed method ```ts twoslash import type { SuppressedAPIMethodParams, SuppressedAPIMethodReturn, } from "gramio"; type SendMessageParams = SuppressedAPIMethodParams<"sendMessage">; // ^? type SendMessageParams = SendMessageParams // type GetMeReturn = SuppressedAPIMethodReturn<"getMe">; // ^? type GetMeReturn = TelegramUser // ``` ### Debugging In order to debug which requests GramIO sends, you can set the environment variable to `DEBUG=gramio:api:*` ```bash npx cross-env DEBUG=gramio:api:* node index.js ``` And you will see something like in the console: ```bash gramio:api:getUpdates options: {"method":"POST","headers":{"Content-Type":"application/json"},"body":"{\"offset\":0,\"suppress\":true}"} +0ms gramio:api:getUpdates response: {"ok":true,"result":[]} +49ms ``` also if you use [Bun](https://bun.sh) you can use `BUN_CONFIG_VERBOSE_FETCH` environment variable to log network requests. [Read more](https://bun.sh/docs/runtime/debugger#debugging-network-requests). ```sh BUN_CONFIG_VERBOSE_FETCH=curl bun src/index.ts ``` And logs will looks like: ```curl [fetch] > HTTP/1.1 POST https://example.com/ [fetch] > content-type: application/json [fetch] > Connection: keep-alive [fetch] > User-Agent: Bun/1.1.14 [fetch] > Accept: */* [fetch] > Host: example.com [fetch] > Accept-Encoding: gzip, deflate, br [fetch] > Content-Length: 13 [fetch] < 200 OK [fetch] < Accept-Ranges: bytes [fetch] < Cache-Control: max-age=604800 [fetch] < Content-Type: text/html; charset=UTF-8 [fetch] < Date: Tue, 18 Jun 2024 05:12:07 GMT [fetch] < Etag: "3147526947" [fetch] < Expires: Tue, 25 Jun 2024 05:12:07 GMT [fetch] < Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT [fetch] < Server: EOS (vny/044F) [fetch] < Content-Length: 1256 ``` ### Bun startup optimization: --fetch-preconnect If you are running your bot with Bun, you can use the CLI flag `--fetch-preconnect=` to speed up the first network request to Telegram servers. This flag tells Bun to start establishing the connection (DNS, TCP, TLS) to the specified host before your code runs, so the first API call is faster. Example: ```bash bun --fetch-preconnect=https://api.telegram.org:443/ ./src/bot.ts ``` This is especially useful for bots where the first thing you do is call the Telegram API. With this flag, Bun will "warm up" the connection at startup, so your bot will be ready to send requests with less delay. The overall startup time may increase slightly, but the time to first successful API call will decrease. In most cases this is not an issue, but the benefit is less noticeable if the first request is sent immediately after the process starts. See more in the [Bun documentation](https://bun.sh/docs/api/fetch#preconnect-to-a-host). --- --- url: /ru/bot-api.md --- # Bot API > [Документация Telegram Bot API](https://core.telegram.org/bots/api) [Bot API](https://core.telegram.org/bots/api) - это высокоуровневый HTTP-интерфейс к Telegram API, который упрощает разработку ботов. Под капотом [Bot API](https://core.telegram.org/bots/api) использует TDLib (который, в свою очередь, использует MTProto API). В то время как [Bot API](https://core.telegram.org/bots/api) может использоваться только для работы с аккаунтами ботов, MTProto API может использоваться для работы как с ботами, так и с аккаунтами пользователей. Если вам необходимо работать с MTProto, мы рекомендуем библиотеку MtCute Logomtcute. ## Вызов Bot API Вы можете вызывать методы Telegram Bot API через `bot.api` или через сокращенные методы контекстов (например, `context.send` - это сокращение для `sendMessage`) ```ts twoslash import { Bot } from "gramio"; const bot = new Bot(""); // ---cut--- const response = await bot.api.sendMessage({ // ^? // // chat_id: "@gramio_forum", text: "текст сообщения", }); ``` ```ts twoslash import { Bot } from "gramio"; const bot = new Bot(""); // ---cut--- bot.on("message", (context) => context.send("Это сообщение будет отправлено в текущий чат") ); ``` ### Подавление ошибок Бывает удобно обрабатывать ошибку на месте без использования блоков **try/catch**. Для этого был создан аргумент `suppress`, который можно использовать в **любом** методе API. ```ts twoslash import { Bot, TelegramError } from "gramio"; const bot = new Bot(""); // ---cut--- const response = await bot.api.sendMessage({ // ^? // // suppress: true, chat_id: "@not_found", text: "Метод с подавлением ошибок", }); if (response instanceof TelegramError) console.error("sendMessage вернул ошибку..."); else console.log("Сообщение успешно отправлено"); ``` ### Обработка Rate-Limit По умолчанию, GramIO не обрабатывает 429 (rate limit) ошибки, но предоставляет утилиту для их обработки. ```ts twoslash import { Bot } from "gramio"; const bot = new Bot(""); // ---cut--- import { withRetries } from "gramio/utils"; const response = await withRetries(() => bot.api.sendMessage({ chat_id: "@gramio_forum", text: "текст сообщения", }) ); response; // ^? ``` `withRetries` будет ждать необходимое время указанное в `retryDelay` у ошибки `TelegramError` и только после успешного выполнения запроса, вернет результат. Этот метод ловит **выброшенную** или **возвращенную** ошибку `TelegramError` так что вы можете использовать и с `context.*` или своими обёртками. Подробнее о [rate limits](/ru/rate-limits) ### Типы GramIO реэкспортирует [@gramio/types](https://www.npmjs.com/package/@gramio/types) (кодо-генерируемые и автоматически публикуемые типы Telegram Bot API). [Подробнее](/ru/types/index.html) ```ts twoslash import type { APIMethodParams, APIMethodReturn } from "gramio"; type SendMessageParams = APIMethodParams<"sendMessage">; // ^? type SendMessageParams = SendMessageParams // type GetMeReturn = APIMethodReturn<"getMe">; // ^? type GetMeReturn = TelegramUser // ``` Например, вы можете использовать их в вашей собственной функции. ```ts twoslash import type { APIMethodParams } from "gramio"; // ---cut--- function myCustomSend(params: APIMethodParams<"sendMessage">) { params; // ^? } ``` #### Типы для методов с подавлением ошибок ```ts twoslash import type { SuppressedAPIMethodParams, SuppressedAPIMethodReturn, } from "gramio"; type SendMessageParams = SuppressedAPIMethodParams<"sendMessage">; // ^? type SendMessageParams = SendMessageParams // type GetMeReturn = SuppressedAPIMethodReturn<"getMe">; // ^? type GetMeReturn = TelegramUser // ``` ### Отладка Для отладки запросов, которые отправляет GramIO, вы можете установить переменную окружения `DEBUG=gramio:api:*` ```bash npx cross-env DEBUG=gramio:api:* node index.js ``` И вы увидите что-то подобное в консоли: ```bash gramio:api:getUpdates options: {"method":"POST","headers":{"Content-Type":"application/json"},"body":"{\"offset\":0,\"suppress\":true}"} +0ms gramio:api:getUpdates response: {"ok":true,"result":[]} +49ms ``` Также, если вы используете [Bun](https://bun.sh), вы можете использовать переменную окружения `BUN_CONFIG_VERBOSE_FETCH` для логирования сетевых запросов. [Подробнее](https://bun.sh/docs/runtime/debugger#debugging-network-requests). ```sh BUN_CONFIG_VERBOSE_FETCH=curl bun src/index.ts ``` И логи будут выглядеть так: ```curl [fetch] > HTTP/1.1 POST https://example.com/ [fetch] > content-type: application/json [fetch] > Connection: keep-alive [fetch] > User-Agent: Bun/1.1.14 [fetch] > Accept: */* [fetch] > Host: example.com [fetch] > Accept-Encoding: gzip, deflate, br [fetch] > Content-Length: 13 [fetch] < 200 OK [fetch] < Accept-Ranges: bytes [fetch] < Cache-Control: max-age=604800 [fetch] < Content-Type: text/html; charset=UTF-8 [fetch] < Date: Tue, 18 Jun 2024 05:12:07 GMT [fetch] < Etag: "3147526947" [fetch] < Expires: Tue, 25 Jun 2024 05:12:07 GMT [fetch] < Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT [fetch] < Server: EOS (vny/044F) [fetch] < Content-Length: 1256 ``` ### Оптимизация старта вместе с Bun: --fetch-preconnect Если вы запускаете своего бота с помощью Bun, вы можете использовать CLI-флаг `--fetch-preconnect=`, чтобы ускорить первый сетевой запрос к серверам Telegram. Этот флаг сообщает Bun начать устанавливать соединение (DNS, TCP, TLS) с указанным хостом до запуска вашего кода, чтобы первый API-запрос был быстрее. Пример: ```bash bun --fetch-preconnect=https://api.telegram.org:443/ ./src/bot.ts ``` Это особенно полезно для ботов, где первое действие — обращение к Telegram API. С этим флагом Bun "разогреет" соединение на этапе старта, и ваш бот будет готов отправлять запросы с меньшей задержкой. Общее время старта может немного увеличиться, но время до первого успешного API-запроса уменьшится. В основном это не мешает, но меньше ощущается когда первый запрос отправляется сразу после запуска процесса. Подробнее в [документации Bun](https://bun.sh/docs/api/fetch#preconnect-to-a-host). --- --- url: /types/index.md --- # Telegram Bot API types > Code-generated and Auto-published Telegram Bot API types [API Types References](https://jsr.io/@gramio/types/doc) ### Versioning 7.7.x types is for 7.7 Telegram Bot API ## Usage as an [NPM package](https://www.npmjs.com/package/@gramio/types) ```ts twoslash import type { APIMethods, APIMethodReturn } from "@gramio/types"; type SendMessageReturn = Awaited>; // ^? type SendMessageReturn = TelegramMessage type GetMeReturn = APIMethodReturn<"getMe">; // ^? type GetMeReturn = TelegramUser ``` ### Auto-update package This library is updated automatically to the latest version of the Telegram Bot API in case of changes thanks to CI CD! If the github action failed, there are no changes in the bot api ## Imports - `index` - exports everything in the section - `methods` - exports `APIMethods` which describes the api functions - `objects` - exports objects with the `Telegram` prefix (for example [Update](https://core.telegram.org/bots/api/#update)) - `params` - exports params that are used in `methods` ### Write you own type-safe Telegram Bot API wrapper ```ts twoslash import type { APIMethods, APIMethodParams, TelegramAPIResponse, } from "@gramio/types"; const TBA_BASE_URL = "https://api.telegram.org/bot"; const TOKEN = ""; const api = new Proxy({} as APIMethods, { get: (_target: APIMethods, method: T) => async (params: APIMethodParams) => { const response = await fetch(`${TBA_BASE_URL}${TOKEN}/${method}`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(params), }); const data = (await response.json()) as TelegramAPIResponse; if (!data.ok) throw new Error(`Some error occurred in ${method}`); return data.result; }, }); api.sendMessage({ chat_id: 1, text: "message", }); ``` #### Usage with [`@gramio/keyboards`](https://github.com/gramiojs/keyboards) ```typescript twoslash import type { APIMethods, APIMethodParams, TelegramAPIResponse, } from "@gramio/types"; const TBA_BASE_URL = "https://api.telegram.org/bot"; const TOKEN = ""; const api = new Proxy({} as APIMethods, { get: (_target: APIMethods, method: T) => async (params: APIMethodParams) => { const response = await fetch(`${TBA_BASE_URL}${TOKEN}/${method}`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(params), }); const data = (await response.json()) as TelegramAPIResponse; if (!data.ok) throw new Error(`Some error occurred in ${method}`); return data.result; }, }); // ---cut--- import { Keyboard } from "@gramio/keyboards"; // the code from the example above api.sendMessage({ chat_id: 1, text: "message with keyboard", reply_markup: new Keyboard().text("button text"), }); ``` ### With file upload support See [files/usage-without-gramio](/files/usage-without-gramio) ## Generate types manually Prerequire - [`rust`](https://www.rust-lang.org/) 1. Clone [this repo](https://github.com/gramiojs/types) and open it ```bash git clone https://github.com/gramiojs/types.git ``` 2. Clone [repo](https://github.com/ark0f/tg-bot-api) with Telegram Bot API schema generator ```bash git clone https://github.com/ark0f/tg-bot-api.git ``` 3. Run the JSON schema generator in the `cloned` folder ```bash cd tg-bot-api && cargo run --package gh-pages-generator --bin gh-pages-generator -- dev && cd .. ``` 4. Run types code-generation from the `root` of the project ```bash bun generate ``` or, if you don't use `bun`, use `tsx` ```bash npx tsx src/index.ts ``` 5. Profit! Check out the types of Telegram Bot API in `out` folder! --- --- url: /tma/index.md --- # Telegram Mini Apps (Web app) Guide is WIP. For start, we recommend [`Telegram apps (tma.js)`](https://docs.telegram-mini-apps.com/). [Telegram documentation](https://core.telegram.org/bots/webapps) | [Figma UI Kit](https://www.figma.com/file/AwAi6qE11mQllHa1sOROYp/Telegram-Mini-Apps-Library?type=design&node-id=26%3A1081&mode=design&t=Sck9CgzgyKz3iIFt-1) | [Telegram Developers Community](https://t.me/devs) ## Scaffold monorepo With [create-gramio](https://github.com/gramiojs/create-gramio) you can easily start developing Telegram mini app in monorepo. You can start a project with [tma.js](https://docs.telegram-mini-apps.com/), [Elysiajs](https://elysiajs.com/) and GramIO in a minute! ::: code-group ```bash [npm] npm create gramio@latest ./bot ``` ```bash [yarn] yarn create gramio@latest ./bot ``` ```bash [pnpm] pnpm create gramio@latest ./bot ``` ```bash [bun] bun create gramio@latest ./bot ``` ::: and choose the type of project you need! For example, this is what a monorepo created using [create-gramio](https://github.com/gramiojs/create-gramio) looks like ```tree ├── apps │ ├── bot │ ├── mini-app │ └── server └── packages └── db ``` ### Scaffold via [`Telegram apps (tma.js)`](https://docs.telegram-mini-apps.com/) ::: code-group ```bash [npm] npm create @telegram-apps/mini-app@latest ./bot ``` ```bash [yarn] yarn create @telegram-apps/mini-app@latest ./bot ``` ```bash [pnpm] pnpm create @telegram-apps/mini-app@latest ./bot ``` ```bash [bun] bun create @telegram-apps/mini-app@latest ./bot ``` ::: This command will help you scaffold a project with a template that matches you from the list: - TypeScript - - [React](https://github.com/Telegram-Mini-Apps/reactjs-template) - - [Solid](https://github.com/Telegram-Mini-Apps/solidjs-template) - - [Next](https://github.com/Telegram-Mini-Apps/nextjs-template) - JavaScript - - [React](https://github.com/Telegram-Mini-Apps/reactjs-js-template) - - [Solid](https://github.com/Telegram-Mini-Apps/solidjs-js-template) > [!WARNING] > At the moment, `create-gramio`'s monorepo support may not be ideal (not the most convenient out of the box). because it's difficult to support these two creation options at the same time. ## HTTPS on localhost BotFather only accepts **http://** links and getting into the **test environment** can be problematic, so let's figure out how to work with **https://** and **localhost**. 1. First you need to install [mkcert](https://github.com/FiloSottile/mkcert): ::: code-group ```bash [Windows] choco install mkcert # or with Scoop scoop bucket add extras scoop install mkcert ``` ```bash [macOS] brew install mkcert brew install nss # if you use Firefox ``` ```bash [Linux] sudo apt install libnss3-tools brew install mkcert ``` ::: 2. Then you need to create a local certificate for the custom hostname and instal it: ```bash mkcert mini-app.local mkcert --install ``` 3. Add the custom hostname to your hosts file: ::: code-group ```powershell [Windows (Open terminal as an administrator)] echo "127.0.0.1 mini-app.local" >> C:\Windows\System32\drivers\etc\hosts ``` ```bash [macOS | Linux] sudo echo "127.0.0.1 mini-app.local" >> /etc/hosts ``` ::: 4. Configure it in `vite.config.ts` ```ts import fs from "node:fs"; import { defineConfig } from "vite"; export default defineConfig({ server: { port: 443, host: "0.0.0.0", hmr: { host: "mini-app.local", port: 443, }, https: { key: fs.readFileSync("./mini-app.local-key.pem"), cert: fs.readFileSync("./mini-app.local.pem"), }, }, }); ``` 🔥 Now you don't need any tunnels and you are happy to develop with HMR in the telegram production environment! ![](/tma-https-on-localhost.png) --- --- url: /ru/tma/index.md --- # Telegram Mini Apps (Web app) > 🚧 Это руководство в процессе разработки 🚧 Данное руководство поможет вам начать разработку Telegram Mini Apps. Для более подробного руководства рекомендуем обратиться к [Telegram apps (tma.js)](https://docs.telegram-mini-apps.com/). Дополнительные ресурсы: - [Документация Telegram](https://core.telegram.org/bots/webapps) - [Figma UI Kit](https://www.figma.com/file/AwAi6qE11mQllHa1sOROYp/Telegram-Mini-Apps-Library?type=design&node-id=26%3A1081&mode=design&t=Sck9CgzgyKz3iIFt-1) - [Сообщество разработчиков Telegram](https://t.me/devs) ## Создание проекта с помощью create-gramio [create-gramio](https://github.com/gramiojs/create-gramio) — это мощный инструмент для создания проектов Telegram Mini App. Он поддерживает различные конфигурации проектов и легко интегрируется с GramIO. Вы можете быстро настроить: - Отдельный Telegram бот - Монорепозиторий с Mini App + Bot - Полноценный монорепозиторий с Mini App + Bot + Elysia (бэкенд фреймворк) > Примечание: Этот вариант доступен только с [bun](https://bun.sh/) (альтернатива среде выполнения Node.js) из-за требований фреймворка Elysia ### Установка Выберите предпочитаемый менеджер пакетов: ::: code-group ```bash [npm] npm create gramio@latest ./bot ``` ```bash [yarn] yarn create gramio@latest ./bot ``` ```bash [pnpm] pnpm create gramio@latest ./bot ``` ```bash [bun] bun create gramio@latest ./bot ``` ::: ### Параметры настройки проекта При запуске команды установки вы пройдете через ряд подсказок: 1. **Выбор типа проекта**: - Bot (отдельный бот) - Mini App + Bot + Elysia (фреймворк для бэкенда) монорепозиторий - Mini App + Bot монорепозиторий 2. **Для проектов с монорепозиторием**: - Вам будет предложено выбрать шаблон Mini App из опций `@telegram-apps/create-mini-app` - Если выбрана опция Elysia, вы также настроите бэкенд на Elysia 3. **Конфигурация фреймворка для бота**: - Выберите предпочитаемую базу данных (без БД, Prisma, Drizzle) - Выберите инструменты разработки (ESLint, Biome) - Настройте дополнительные функции (i18n, Redis, аналитика PostHog и т.д.) ### Структура монорепозитория Когда вы создаете проект с монорепозиторием, структура вашего каталога будет выглядеть так: ```tree ├── apps │ ├── bot # Ваше приложение бота GramIO │ ├── mini-app # Фронтенд Telegram Mini App │ └── server # Серверная часть (если используется опция Elysia) └── packages └── db # Общий пакет базы данных (если выбран) ``` ### Использование [Telegram apps (tma.js)](https://docs.telegram-mini-apps.com/) напрямую В качестве альтернативы вы можете создать только часть Mini App с помощью `@telegram-apps/create-mini-app`: ::: code-group ```bash [npm] npm create @telegram-apps/mini-app@latest ./miniapp ``` ```bash [yarn] yarn create @telegram-apps/mini-app@latest ./miniapp ``` ```bash [pnpm] pnpm create @telegram-apps/mini-app@latest ./miniapp ``` ```bash [bun] bun create @telegram-apps/mini-app@latest ./miniapp ``` ::: Эта команда поможет вам создать проект с шаблоном из следующих вариантов: - TypeScript - [React](https://github.com/Telegram-Mini-Apps/reactjs-template) - [Solid](https://github.com/Telegram-Mini-Apps/solidjs-template) - [Next](https://github.com/Telegram-Mini-Apps/nextjs-template) - JavaScript - [React](https://github.com/Telegram-Mini-Apps/reactjs-js-template) - [Solid](https://github.com/Telegram-Mini-Apps/solidjs-js-template) > [!WARNING] > На данный момент поддержка монорепозитория в `create-gramio` может быть не идеальной (не самой удобной из коробки), поскольку сложно поддерживать эти два варианта создания одновременно. ## HTTPS на localhost BotFather принимает только ссылки **http://** для продакшена, но для получения вашего приложения в **тестовой среде** требуется HTTPS. Вот как настроить HTTPS на localhost: 1. Сначала установите [mkcert](https://github.com/FiloSottile/mkcert): ::: code-group ```bash [Windows] choco install mkcert # или с помощью Scoop scoop bucket add extras scoop install mkcert ``` ```bash [macOS] brew install mkcert brew install nss # если вы используете Firefox ``` ```bash [Linux] sudo apt install libnss3-tools brew install mkcert ``` ::: 2. Создайте локальный сертификат для вашего пользовательского имени хоста и установите его: ```bash mkcert mini-app.local mkcert --install ``` 3. Добавьте пользовательское имя хоста в файл hosts: ::: code-group ```powershell [Windows (откройте терминал от имени администратора)] echo "127.0.0.1 mini-app.local" >> C:\Windows\System32\drivers\etc\hosts ``` ```bash [macOS | Linux] sudo echo "127.0.0.1 mini-app.local" >> /etc/hosts ``` ::: 4. Настройте его в `vite.config.ts`: ```ts import fs from "node:fs"; import { defineConfig } from "vite"; export default defineConfig({ server: { port: 443, host: "0.0.0.0", hmr: { host: "mini-app.local", port: 443, }, https: { key: fs.readFileSync("./mini-app.local-key.pem"), cert: fs.readFileSync("./mini-app.local.pem"), }, }, }); ``` 🔥 Теперь вам не нужны никакие туннели, и вы можете с удовольствием разрабатывать с HMR в производственной среде Telegram! ![](/tma-https-on-localhost.png) --- --- url: /troubleshooting.md --- # Troubleshooting // TODO: --- --- url: /updates/overview.md --- # Update handling ## Start The `start` method launches the process of receiving updates from Telegram for your bot. Depending on the parameters provided, the bot can use long-polling or webhook to receive events. This method initializes the bot, loads [lazy plugins](/plugins/lazy-load), and calls the [`onStart`](/hooks/on-start) hook. **Signature:** ```ts start(options?): Promise ``` **Parameters:** - `options` — an object with launch settings: - `webhook` — parameters for starting via webhook (`true`, a URL string, or an object with parameters). - `longPolling` — parameters for long-polling (for example, timeouts). - `dropPendingUpdates` — whether to drop pending updates on start. - `allowedUpdates` — a list of update types the bot will receive. - `deleteWebhook` — how to handle an existing webhook when starting long-polling. > [!IMPORTANT] > > **Parameter details:** > > - If you set `webhook: true`, GramIO will not attempt to set the webhook itself — it assumes you have already configured it. In this case, the bot will simply start receiving updates via the existing webhook. > > - The `deleteWebhook` parameter controls what to do with an existing webhook when starting long-polling: > - If `deleteWebhook: true`, the bot will always delete the webhook before starting long-polling. > - If `deleteWebhook: "on-conflict-with-polling"`, the webhook will only be deleted if it interferes with starting long-polling (when Telegram responds to `getUpdates` with a conflict error). > - If not specified, the default behavior (`on-conflict-with-polling`) is used. ```ts import { Bot } from "gramio"; const bot = new Bot(process.env.BOT_TOKEN) .command("start", (ctx) => ctx.send("Hi!")) .onStart(console.log); await bot.start({ longPolling: { timeout: 10 }, dropPendingUpdates: true, }); ``` **How it works:** - If webhook is not specified, long-polling is started. - If webhook is specified, the webhook is set up and the bot starts receiving updates via HTTP. - Calls the [`onStart`](/hooks/on-start) hook. - You can drop old updates on start. ## Stop The `stop` method stops receiving updates and gracefully shuts down all internal bot processes. The [`onStop`](/hooks/on-stop) hook is called, and the update queue is cleared. **Signature:** ```ts stop(timeout?): Promise ``` **Parameters:** - `timeout` — the time to wait for the update queue to finish processing (default is 3000 ms). **Example usage:** ```ts await bot.stop(); ``` **How it works:** - Stops long-polling or webhook (if it was running). - Waits for all current updates to finish processing. - Calls the [`onStop`](/hooks/on-stop) hook. # Context ## Listen to all events ```ts bot.use((context, next) => { // ... }); ``` ## Listen only to specific events ```ts bot.on("message", (context, next) => { // ... }); // or bot.on(["message", "callback_query"], (context, next) => { // ... }); ``` You can read API Reference for contexts [here](https://jsr.io/@gramio/contexts/doc). # Context injection ## Derive `Derive` allows you to inject what's you want to context with access to existing context data and type-safety. A handler will be called **every** update. #### Global derive ```ts twoslash // @errors: 2339 1003 import { Bot } from "gramio"; const bot = new Bot(process.env.BOT_TOKEN as string) .derive((context) => { return { key: 1, }; }) .on("message", (context) => { context.key; // ^? }) .use((context, next) => { context.key; // ^? return next(); }) .on("callback_query", (context) => { context.key; // ^? }); ``` #### Scoped derive You can scope the derive to specific **update** (or **updates**) with full type support. ```ts twoslash // @errors: 2339 1003 import { Bot } from "gramio"; // Simple example export function findOrRegisterUser(id: number) { return {} as Promise<{ id: number; name: string; balance: number }>; } const bot = new Bot(process.env.BOT_TOKEN as string) .derive(["message", "callback_query"], async (context) => { const fromId = context?.from?.id; if(!fromId) throw new Error("No from id"); return { fromId, } }) .derive("message", async (context) => { const user = await findOrRegisterUser(context.fromId); // ^? return { user, }; }) .on("message", (context) => { context.user.; // ^| // }) .use((context, next) => { if (context.is("message")) context.user; // ^? return next(); }) .on("callback_query", (context) => { context.user; // ^? context.fromId // ^? }); ``` ## Decorate With `decorate` you can inject **static values** and it will be injected to contexts **only when `decorate` called** (so it will not evaluate every update). ```ts twoslash // @errors: 2339 import { Bot } from "gramio"; const bot = new Bot(process.env.BOT_TOKEN as string) .decorate("TEST", "hi!! you're cute" as const) .decorate({ key: "value", key2: "value", }) .use((context) => { context.TEST; // ^? // context.k; // ^| }); ``` --- --- url: /files/media-upload.md --- # Media Upload Class-helper with static methods for file uploading. ## path Method for uploading Media File by local path. ```ts twoslash // @errors: 2345 import { BotLike, MessageContext } from "gramio"; import { MediaUpload } from "@gramio/files"; const ctx = {} as InstanceType>; // ---cut--- ctx.sendDocument(await MediaUpload.path("./package.json")); // or with filename ctx.sendDocument(await MediaUpload.path("./package.json", "some-other.json")); ``` If filename not specified, the filename set to filename :) ## url Method for uploading Media File by URL (also with fetch options). ```ts twoslash // @errors: 2345 import { BotLike, MessageContext } from "gramio"; import { MediaUpload } from "@gramio/files"; const ctx = {} as InstanceType>; // ---cut--- ctx.sendPhoto(await MediaUpload.url("https://example.com/cat.png")); // or with filename ctx.sendPhoto( await MediaUpload.url("https://example.com/cat.png", "cute-cat.png") ); // or with filename and fetch options (for example headers) ctx.sendPhoto( await MediaUpload.url("https://example.com/cat.png", "cute-cat.png", { headers: { Authorization: "Bearer gramio", }, }) ); ``` If filename not specified, the filename set to last part after `/`. ## buffer Method for uploading Media File by Buffer or ArrayBuffer. ```ts twoslash // @errors: 2345 import { BotLike, MessageContext } from "gramio"; import { MediaUpload } from "@gramio/files"; const ctx = {} as InstanceType>; // ---cut--- const res = await fetch("https://..."); ctx.sendDocument( MediaUpload.buffer(await res.arrayBuffer(), "from-buffer.json") ); ``` By default filename is `file.buffer`. ## stream Method for uploading Media File by Readable stream. ```ts twoslash // @errors: 2345 import { BotLike, MessageContext } from "gramio"; import { MediaUpload } from "@gramio/files"; // import fs from "node:fs"; // https://github.com/gramiojs/documentation/actions/runs/10424909870/job/28874689592 wtf const fs = {} as any; const ctx = {} as InstanceType>; // ---cut--- ctx.sendDocument( await MediaUpload.stream( fs.createReadStream("./cute-cat.png"), "the-same-cute-cat.png" ) ); ``` By default filename is `file.stream`. ## text Method for uploading Media File by text content. ```ts twoslash // @errors: 2345 import { BotLike, MessageContext } from "gramio"; import { MediaUpload } from "@gramio/files"; const ctx = {} as InstanceType>; // ---cut--- ctx.sendDocument(MediaUpload.text("GramIO is the best!", "truth.txt")); ``` By default filename is `text.txt`. --- --- url: /files/usage-without-gramio.md --- # Usage without GramIO ## Write your own type-safe Telegram Bot API wrapper with file uploading support! ```ts twoslash // @noErrors import type { APIMethods, APIMethodParams, TelegramAPIResponse, } from "@gramio/types"; import { isMediaUpload, convertJsonToFormData } from "@gramio/files"; const TBA_BASE_URL = "https://api.telegram.org/bot"; const TOKEN = ""; const api = new Proxy({} as APIMethods, { get: (_target: APIMethods, method: T) => async (params: APIMethodParams) => { const reqOptions: Parameters[1] = { method: "POST", }; if (params && isMediaUpload(method, params)) { const formData = await convertJsonToFormData(method, params); reqOptions.body = formData; } else { reqOptions.headers = { "Content-Type": "application/json", }; reqOptions.body = JSON.stringify(params); } const response = await fetch( `${TBA_BASE_URL}${TOKEN}/${method}`, reqOptions ); const data = (await response.json()) as TelegramAPIResponse; if (!data.ok) throw new Error(`Some error in ${method}`); return data.result; }, }); ``` --- --- url: /ru/guides/for-beginners/1.md --- # Введение ## На кого рассчитан этот материал? Этот материал рассчитан на новичков в программировании и разработке ботов в Telegram. Вам необходимо: - Знания JavaScript и хотя бы минимальное понимание TypeScript (не в начале гайда) - Установленный [Node.js](https://nodejs.org/en) (v20.x версии) - Способность гуглить - Телеграм аккаунт (🤯) --- --- url: /ru/files/media-upload.md --- # Media Upload Класс-помощник со статическими методами для загрузки файлов. ## path Метод для загрузки медиа-файла по локальному пути. ```ts twoslash // @errors: 2345 import { BotLike, MessageContext } from "gramio"; import { MediaUpload } from "@gramio/files"; const ctx = {} as InstanceType>; // ---cut--- ctx.sendDocument(await MediaUpload.path("./package.json")); // или с указанием имени файла ctx.sendDocument(await MediaUpload.path("./package.json", "some-other.json")); ``` Если имя файла не указано, в качестве имени файла используется исходное имя файла. ## url Метод для загрузки медиа-файла по URL (также с опциями fetch). ```ts twoslash // @errors: 2345 import { BotLike, MessageContext } from "gramio"; import { MediaUpload } from "@gramio/files"; const ctx = {} as InstanceType>; // ---cut--- ctx.sendPhoto(await MediaUpload.url("https://example.com/cat.png")); // или с указанием имени файла ctx.sendPhoto( await MediaUpload.url("https://example.com/cat.png", "cute-cat.png") ); // или с именем файла и опциями fetch (например, заголовками) ctx.sendPhoto( await MediaUpload.url("https://example.com/cat.png", "cute-cat.png", { headers: { Authorization: "Bearer gramio", }, }) ); ``` Если имя файла не указано, в качестве имени файла используется последняя часть после `/`. ## buffer Метод для загрузки медиа-файла из Buffer или ArrayBuffer. ```ts twoslash // @errors: 2345 import { BotLike, MessageContext } from "gramio"; import { MediaUpload } from "@gramio/files"; const ctx = {} as InstanceType>; // ---cut--- const res = await fetch("https://..."); ctx.sendDocument( MediaUpload.buffer(await res.arrayBuffer(), "from-buffer.json") ); ``` По умолчанию имя файла - `file.buffer`. ## stream Метод для загрузки медиа-файла из потока `Readable`. ```ts twoslash // @errors: 2345 import { BotLike, MessageContext } from "gramio"; import { MediaUpload } from "@gramio/files"; // import fs from "node:fs"; // https://github.com/gramiojs/documentation/actions/runs/10424909870/job/28874689592 wtf const fs = {} as any; const ctx = {} as InstanceType>; // ---cut--- ctx.sendDocument( await MediaUpload.stream( fs.createReadStream("./cute-cat.png"), "the-same-cute-cat.png" ) ); ``` По умолчанию имя файла - `file.stream`. ## text Метод для загрузки медиа-файла из текстового содержимого. ```ts twoslash // @errors: 2345 import { BotLike, MessageContext } from "gramio"; import { MediaUpload } from "@gramio/files"; const ctx = {} as InstanceType>; // ---cut--- ctx.sendDocument(MediaUpload.text("GramIO лучший!", "правда.txt")); ``` По умолчанию имя файла - `text.txt`. --- --- url: /ru/guides/for-beginners/3.md --- # TypeScript и виды клавиатур #### Что сделаем? - Научимся запускать TypeScript файлы с помощью TSX - Типизируем `process.env` - Познакомимся с клавиатурой и её видами - Научимся отвечать на обычные сообщения и нажатия на инлайн клавиатуру ### Запуск TypeScript файлов в Node.js > Не бойтесь. Никаких особенностей TypeScript кроме как подсказок мы сейчас использовать не будем. На момент написания этого гайда в Node.js только-только появляется нативная поддержка запуска файлов TypeScript ([Подробнее](https://t.me/kravetsone/306)), но сейчас использовать это рано. Поэтому мы воспользуемся прекрасной библиотекой «[TSX](https://www.npmjs.com/package/tsx)». Для начала нам необходимо поменять расширение у файла `index.js` с JavaScript на TypeScript (то есть переименовать в `index.ts`). А после мы можем опробовать `tsx` с помощью простой команды: ```bash npx tsx watch --env-file=.env index.ts ``` Довольно просто) Теперь давайте заменим этой командой содержимое `dev` команды в нашем `package.json` файле. ### Типизируем `process.env` К сожалению, TypeScript не знает что у нас находится в файле `.env` поэтому и подсказать не может. После переименования файла у вас должна была появиться ошибка у использования переменной среды `BOT_TOKEN`, поэтому давайте поправим это, добавив в конце файла: ```ts declare global { namespace NodeJS { interface ProcessEnv { BOT_TOKEN: string; } } } ``` Не пугайтесь, то что мы проделали называется `Declaration merging`, то есть мы объединили TypeScript интерфейс типов для `process.env` с необходимыми нам значениями в глобальной зоне видимости. Пока можете принять это как есть) Главное что ошибка исчезла и появились подсказки! ```ts twoslash // @errors: 2580 declare global { namespace NodeJS { interface ProcessEnv { BOT_TOKEN: string; } } } const process = {} as { env: { BOT_TOKEN: string; [key: string]: string | undefined; }; }; // ---cut--- process.env.B; // ^| ``` ### Знакомство с клавиатурой и её видами > [Раздел в документации о клавиатуре](https://gramio.dev/keyboards/overview) #### Keyboard > [Раздел в документации об этом виде клавиатуры](https://gramio.dev/keyboards/keyboard) Давайте напишем обработчик команды `/start` и отправим пользователю обычную клавиатуру. ```ts import { Bot, Keyboard } from "gramio"; const bot = new Bot(process.env.BOT_TOKEN) .command("start", (context) => context.send("Вы написали /start", { reply_markup: new Keyboard().text("Пинг"), }) ) .hears("Пинг", (context) => context.send("Понг")); bot.start(); ``` Давайте разберёмся что такое `new Keyboard().text("Пинг")`. ```ts new Keyboard().text("Пинг"); ``` Эта конструкция написанная с удобный классом `Keyboard` прячет за собой [страшные вещи](https://core.telegram.org/bots/api#replykeyboardmarkup) и на самом деле телеграм получает это уже так: ```json { "keyboard": [ [ { "text": "Пинг" } ] ], "one_time_keyboard": false, "is_persistent": false, "selective": false, "resize_keyboard": true } ``` А `hears` это метод, который в отличие от `command` слушает все сообщения. #### InlineKeyboard > [Раздел в документации об этом виде клавиатуры](https://gramio.dev/keyboards/inline-keyboard) Теперь же давайте познаем всю мощь `InlineKeyboard`! Давайте вместе с «Понг» будем отправлять пользователю инлайн клавиатуру! ```ts import { Bot, Keyboard, InlineKeyboard } from "gramio"; const bot = new Bot(process.env.BOT_TOKEN) .command("start", (context) => context.send("Вы написали /start", { reply_markup: new Keyboard().text("Пинг"), }) ) .hears("Пинг", (context) => context.send("Понг", { reply_markup: new InlineKeyboard().text("Пинг", "ping"), }) ); bot.start(); ``` Вы спросите а что за 2 аргумент в функции `text`, а я отвечу что это `payload`. Грубо говоря, строка, которую нам вернёт телеграм, когда будет оповещать о нажатой кнопке. Так что давайте обработаем это событие. Оно называется `callback_query` и у GramIO уже есть удобный шорт-хенд для этого! ```ts import { Bot, Keyboard, InlineKeyboard } from "gramio"; const bot = new Bot(process.env.BOT_TOKEN) .command("start", (context) => context.send("Вы написали /start", { reply_markup: new Keyboard().text("Пинг"), }) ) .hears("Пинг", (context) => context.send("Понг", { reply_markup: new InlineKeyboard().text("Пинг", "ping"), }) ) .callbackQuery("ping", (context) => { await context.answer("По лбу понг"); return context.editText("Понг, но уже из callback_query!"); }); bot.start(); ``` Теперь в ответ на нажатие кнопки с `payload` равным ping редактируем текст сообщения (кнопки при этом удаляются) и показываем всплывающее окно. #### RemoveKeyboard > [Раздел в документации об этом виде клавиатуры](https://gramio.dev/keyboards/remove-keyboard) Теперь давайте научимся удалять кнопки если пользователь напишем «Понг» ```ts import { Bot, Keyboard, InlineKeyboard, RemoveKeyboard } from "gramio"; const bot = new Bot(process.env.BOT_TOKEN) .command("start", (context) => context.send("Вы написали /start", { reply_markup: new Keyboard().text("Пинг"), }) ) .hears("Пинг", (context) => context.send("Понг", { reply_markup: new InlineKeyboard().text("Пинг", "ping"), }) ) .hears("Понг", (context) => context.send("А ты оригинален", { reply_markup: new RemoveKeyboard(), }) ) .callbackQuery("ping", async (context) => { await context.answer("По лбу понг"); return context.editText("Понг, но уже из callback_query!"); }); bot.start(); ``` #### ForceReply > [Раздел в документации об этом виде клавиатуры](https://gramio.dev/keyboards/force-reply-keyboard) С помощью этого вида кнопок (телеграм считает их кнопками) вы можете сделать так чтобы пользователь автоматически ответил на отправленное вами сообщение, а в поле `placeholder` мы запишем значение, которое Телеграм покажет пользователю вместо стандартного «Написать сообщение...» ```ts import { Bot, Keyboard, InlineKeyboard, RemoveKeyboard, ForceReplyKeyboard, } from "gramio"; const bot = new Bot(process.env.BOT_TOKEN) .command("start", (context) => context.send("Вы написали /start", { reply_markup: new Keyboard().text("Пинг").text("Вопрос"), }) ) .hears("Вопрос", (context) => { return context.send("Введите ответ", { reply_markup: new ForceReplyKeyboard().placeholder( "Введите своё ответ прямо сюда!!!" ), }); }) .hears("Пинг", (context) => context.send("Понг", { reply_markup: new InlineKeyboard().text("Пинг", "ping"), }) ) .hears("Понг", (context) => context.send("А ты оригинален", { reply_markup: new RemoveKeyboard(), }) ) .callbackQuery("ping", async (context) => { await context.answer("По лбу понг"); return context.editText("Понг, но уже из callback_query!"); }); bot.start(); ``` На сегодня всё. Надеюсь ещё встретимся! --- --- url: /ru/plugins/how-to-write.md --- # Как написать плагин Бывает, что вам чего-то не хватает... И плагины могут помочь вам с этим! # Пример ```ts twoslash import { Plugin, Bot } from "gramio"; export class PluginError extends Error { wow: "type" | "safe" = "type"; } const plugin = new Plugin("gramio-example") .error("PLUGIN", PluginError) .derive(() => { return { some: ["derived", "props"] as const, }; }); const bot = new Bot(process.env.BOT_TOKEN as string) .extend(plugin) .onError(({ context, kind, error }) => { if (context.is("message") && kind === "PLUGIN") { console.log(error.wow); // ^? } }) .use((context) => { console.log(context.some); // ^^^^ }); ``` ## Создание плагина Эта команда поможет вам создать плагин с GramIO самым простым способом. ::: code-group ```bash [npm] npm create gramio-plugin ./plugin ``` ```bash [yarn] yarn create gramio-plugin ./plugin ``` ```bash [pnpm] pnpm create gramio-plugin ./plugin ``` ```bash [bun] bun create gramio-plugin ./plugin ``` ::: #### Поддерживаемое окружение - Линтеры - - [Biome](https://biomejs.dev/) - - [ESLint](https://eslint.org/) с некоторыми плагинами - [Хранилище](https://gramio.dev/storages/) - Другое - - [Husky](https://typicode.github.io/husky/) (Git hooks) --- --- url: /ru/rate-limits.md --- # Ограничения частоты запросов В этом руководстве показано, как решить проблемы с ограничением частоты запросов (`Error code 429, Error: Too many requests: retry later`) от Telegram Bot API. В общем, если вы избегаете поведения, напоминающего **массовую рассылку** в вашем боте, то беспокоиться об ограничениях частоты запросов не нужно. Но лучше на всякий случай использовать плагин [auto-retry](/ru/plugins/official/auto-retry). Если вы достигаете этих ограничений без рассылки, то, скорее всего, что-то не так на вашей стороне. # Как делать рассылку Для начала мы можем решить проблемы с ограничением частоты запросов при рассылке без использования **очередей**. Давайте воспользуемся [встроенной функцией `withRetries`](/ru/bot-api#Обработка-Rate-Limit) которая ловит ошибки с полем `retry_after` (ошибки **ограничения частоты запросов**), **ждет** указанное время и **повторяет** запрос к API. Затем нам нужно создать цикл и настроить **задержку** так, чтобы мы с наименьшей вероятностью попали на ошибку `rate-limit`, а если мы её поймаем, мы будем ждать указанное время (и плагин [withRetries](/ru/bot-api#Обработка-Rate-Limit) повторит запрос) ```ts twoslash // экспериментальное API, доступное с Node.js@16.14.0 import { scheduler } from "node:timers/promises"; import { Bot, TelegramError } from "gramio"; import { withRetries } from "gramio/utils"; const bot = new Bot(process.env.BOT_TOKEN as string); const chatIds: number[] = [ /** идентификаторы чатов */ ]; for (const chatId of chatIds) { const result = await withRetries(() => bot.api.sendMessage({ chat_id: chatId, text: "Привет!", }) ); await scheduler.wait(100); // Базовая задержка между успешными запросами чтобы не попасть на ошибку `rate-limit` } ``` ## Реализация с очередью (@gramio/broadcast) Пример рассылки, которая сохраняется даже при перезапуске сервера и готова к горизонтальному масштабированию. GramIO имеет в своей экосистеме удобную библиотеку для работы с рассылками - [@gramio/broadcast](https://github.com/gramiojs/broadcast) Предварительные требования: - [Redis](https://redis.io/) ```ts import { Bot, InlineKeyboard } from "gramio"; import Redis from "ioredis"; import { Broadcast } from "@gramio/broadcast"; const redis = new Redis({ maxRetriesPerRequest: null, }); const bot = new Bot(process.env.BOT_TOKEN as string); const broadcast = new Broadcast(redis).type("test", (chatId: number) => bot.api.sendMessage({ chat_id: chatId, text: "test", }) ); console.log("prepared to start"); const chatIds = [617580375]; await broadcast.start( "test", chatIds.map((x) => [x]) ); // graceful shutdown async function gracefulShutdown() { console.log(`Process ${process.pid} go to sleep`); await broadcast.job.queue.close(); console.log("closed"); process.exit(0); } process.on("SIGTERM", gracefulShutdown); process.on("SIGINT", gracefulShutdown); ``` Эта библиотека предоставляет удобный интерфейс для работы с рассылками не теряя типизации. Вы создаёте типы рассылок и принимаете в функции данные, а затем вызываете `broadcast.start` с массивом аргументов. ## Своя реализация Или вы можете написать свою логику: Предварительные требования: - [Redis](https://redis.io/) - ioredis, bullmq, [jobify](https://github.com/kravetsone/jobify) // TODO: больше информации об этом ```ts import { Worker } from "bullmq"; import { Bot, TelegramError } from "gramio"; import { Redis } from "ioredis"; import { initJobify } from "jobify"; const bot = new Bot(process.env.BOT_TOKEN as string); const redis = new Redis({ maxRetriesPerRequest: null, }); const defineJob = initJobify(redis); const text = "Привет, мир!"; const sendMailing = defineJob("send-mailing") .input<{ chatId: number }>() .options({ limiter: { max: 20, duration: 1000, }, }) .action(async ({ data: { chatId } }) => { const response = await bot.api.sendMessage({ chat_id: chatId, suppress: true, text, }); if (response instanceof TelegramError) { if (response.payload?.retry_after) { await sendMailing.worker.rateLimit( response.payload.retry_after * 1000 ); // используйте это только если вы не используете auto-retry // потому что это запускает эту задачу заново throw Worker.RateLimitError(); } else throw response; } }); const chats: number[] = []; // получите чаты из базы данных await sendMailing.addBulk( chats.map((x) => ({ name: "mailing", data: { chatId: x, }, })) ); ``` ### Дополнительное чтение - [Так что ваш бот ограничен по частоте запросов...](https://telegra.ph/So-your-bot-is-rate-limited-01-26) - [Рассылка пользователям](https://core.telegram.org/bots/faq#broadcasting-to-users) --- --- url: /ru/bot-class.md --- # Основной класс бота [`Bot`](https://jsr.io/@gramio/core/doc/~/Bot) - главный класс фреймворка. Вы используете его для взаимодействия с [Telegram Bot API](/ru/bot-api). ## [Конструктор](https://jsr.io/@gramio/core/doc/~/Bot#constructors) Существует [два способа](https://jsr.io/@gramio/core/doc/~/Bot#constructors) передачи токена и параметров. 1. Передать токен в качестве первого аргумента и (опционально) параметры ```ts const bot = new Bot(process.env.BOT_TOKEN, { api: { fetchOptions: { headers: { "X-Hi-Telegram": "10", }, }, }, }); ``` 2. Передать параметры с обязательным полем `token` ```ts const bot = new Bot({ token: process.env.BOT_TOKEN, api: { fetchOptions: { headers: { "X-Hi-Telegram": "10", }, }, }, }); ``` ### Информация о боте Когда бот начинает прослушивать обновления, `GramIO` получает информацию о боте, чтобы проверить, является ли **токен бота действительным**, и использовать некоторые данные бота. Например, эти данные будут использоваться для обработки упоминаний бота в командах. Если вы это настроите это поле сами, `GramIO` не будет отправлять запрос `getMe` при запуске. ```ts const bot = new Bot(process.env.BOT_TOKEN, { info: process.env.NODE_ENV === "production" ? { id: 1, is_bot: true, first_name: "Bot example", username: "example_bot", // .. } : undefined }, }); ``` > [!IMPORTANT] > Вам следует настроить это при **горизонтальном масштабировании** вашего бота (из-за `ограничений частоты запросов` метода `getMe` и **более быстрого времени запуска**) или при работе в **бессерверных** средах. ### Плагины по умолчанию Некоторые плагины используются по умолчанию, но вы можете отключить их. ```ts const bot = new Bot(process.env.BOT_TOKEN, { plugins: { // отключить форматирование. Все format`` будут текстом без форматирования format: false, }, }); ``` ## Параметры API ### fetchOptions Настройка [параметров](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch) ```ts const bot = new Bot(process.env.BOT_TOKEN, { api: { fetchOptions: { headers: { "X-Hi-Telegram": "10", }, }, }, }); ``` ### baseURL URL, который будет использоваться для отправки запросов. По умолчанию `"https://api.telegram.org/bot"`. ```ts const bot = new Bot(process.env.BOT_TOKEN, { api: { // случайный домен baseURL: "https://telegram.io/bot", }, }); ``` ### useTest Следует ли отправлять запросы в `тестовый` дата-центр? По умолчанию `false`. Тестовая среда полностью отделена от основной среды, поэтому вам потребуется создать новую учетную запись пользователя и нового бота с помощью `@BotFather`. [Документация](https://core.telegram.org/bots/webapps#using-bots-in-the-test-environment) ```ts const bot = new Bot(process.env.BOT_TOKEN, { api: { useTest: true, }, }); ``` ### retryGetUpdatesWait Время в миллисекундах перед повторным вызовом `getUpdates`. По умолчанию `1000`. ```ts const bot = new Bot(process.env.BOT_TOKEN, { api: { retryGetUpdatesWait: 300, }, }); ``` ## Поддержка прокси В GramIO очень просто настроить прокси для запросов. ### Node.js ```ts import { ProxyAgent } from "undici"; const proxyAgent = new ProxyAgent("my.proxy.server"); const bot = new Bot(process.env.BOT_TOKEN, { api: { fetchOptions: { dispatcher: proxyAgent, }, }, }); ``` > [!WARNING] > Несмотря на то, что `undici` работает под капотом `Node.js`, вам придется установить его. Также убедитесь, что у вас нет `"lib": ["DOM"]` в вашем `tsconfig.json`, иначе вы не увидите свойство **dispatcher** в **типах** (хотя `undici` всё равно будет его обрабатывать). [Документация](https://github.com/nodejs/undici/blob/e461407c63e1009215e13bbd392fe7919747ab3e/docs/api/ProxyAgent.md) ### Bun ```ts const bot = new Bot(process.env.BOT_TOKEN, { api: { fetchOptions: { proxy: "https://my.proxy.server", }, }, }); ``` [Документация](https://bun.sh/docs/api/fetch#proxy-requests) ### Deno ```ts const client = Deno.createHttpClient({ proxy: { url: "http://host:port/" }, }); const bot = new Bot(process.env.BOT_TOKEN, { api: { fetchOptions: { client, }, }, }); ``` > [!WARNING] > This API is **unstable**, so you should run it with `deno run index.ts --unstable` [Documentation](https://docs.deno.com/api/web/~/fetch#function_fetch_1) | [Deno.proxy](https://docs.deno.com/api/deno/~/Deno.Proxy) | [`HTTP_PROXY` environment variables](https://docs.deno.com/runtime/manual/basics/modules/proxies/) --- --- url: /ru/updates/graceful-shutdown.md --- # Корректное завершение работы Корректное завершение работы (Graceful shutdown) - паттерн, который помогает остановить приложение без потери данных или прерывания выполняемых процессов (например, ответ на команду `/start`). > Какие будут ваши последние слова? ### Сигналы - `SIGINT` (Signal Interrupt) Этот сигнал отправляется, когда пользователь нажимает Ctrl+C. - `SIGTERM` (Signal Terminate) Этот сигнал часто отправляется менеджерами процессов (такими как Docker, Kubernetes или systemd), когда им нужно завершить процесс. Поэтому мы хотим обрабатывать оба. ### Пример Представьте, что у вас есть **бот** на **вебхуках** с использованием **аналитики**. ```ts import { app } from "./webhook"; import { bot } from "./bot"; import { posthog } from "./analytics"; const signals = ["SIGINT", "SIGTERM"]; for (const signal of signals) { process.on(signal, async () => { console.log(`Получен ${signal}. Начинаем завершение работы...`); await app.stop(); await bot.stop(); await posthog.shutdown(); process.exit(0); }); } ``` Во-первых, мы должны прекратить получение обновлений - остановить наш бэкенд API-фреймворк (например, Elysia). Во-вторых, мы должны обработать все ожидающие обновления - остановить нашего бота с помощью метода `bot.stop`. И, наконец, мы должны вызвать `posthog.shutdown()`, который отправляет собранную аналитику в наш экземпляр PostHog. После этого мы выходим из нашего процесса с кодом статуса 0, и это всё! Этот пример показывает, как корректно завершить работу вашего бота в правильном порядке для большинства приложений. --- --- url: /ru/plugins/lazy-load.md --- # Ленивая загрузка плагинов Плагины могут быть **лениво загружены**, если они находятся в **асинхронной** функции. Такие плагины всегда подключаются в самый **последний** момент (во время вызова **.start**). Если вы хотите вызвать их **раньше**, поставьте перед ними **await**. ## Пример ```ts const bot = new Bot(process.env.BOT_TOKEN as string) .extend(autoload()) // autoload асинхронный .command("start", () => { // эта команда регистрируется ДО загрузки autoload }); bot.start(); // autoload загружается здесь ``` Вы можете исправить это с помощью **await**. ```ts const bot = new Bot(process.env.BOT_TOKEN as string) .extend(await autoload()) // autoload асинхронный, но мы используем await .command("start", () => { // эта команда регистрируется ПОСЛЕ загрузки autoload }); bot.start(); ``` теперь это работает как ожидалось! --- --- url: /ru/updates/webhook.md --- # Как использовать вебхуки Telegram Bot API поддерживает два способа получения обновлений: [long-polling](https://en.wikipedia.org/wiki/Push_technology#Long_polling) и [webhook](https://en.wikipedia.org/wiki/Webhook?useskin=vector). GramIO хорошо работает с обоими. Вот пример использования вебхуков ## Поддерживаемые фреймворки - [Elysia](https://elysiajs.com/) - [Fastify](https://fastify.dev/) - [Hono](https://hono.dev/) - [Express](https://expressjs.com/) - [Koa](https://koajs.com/) - [node:http](https://nodejs.org/api/http.html) - [Bun.serve](https://bun.sh/docs/api/http) - [std/http (Deno.serve)](https://docs.deno.com/runtime/manual/runtime/http_server_apis#http-server-apis) - [И любой другой фреймворк](#напишите-свой-собственный-обработчик-обновлений) ## Пример ```ts import { Bot, webhookHandler } from "gramio"; import Fastify from "fastify"; const bot = new Bot(process.env.BOT_TOKEN as string); const fastify = Fastify(); fastify.post("/telegram-webhook", webhookHandler(bot, "fastify")); fastify.listen({ port: 3445, host: "::" }); bot.on("message", (context) => { return context.send("Fastify!"); }); bot.start({ webhook: { url: "https://example.com:3445/telegram-webhook", }, }); ``` ## Использование вебхуков только в продакшене Вместо использования туннелей вы можете просто использовать polling в среде разработки! ```ts const bot = new Bot(process.env.BOT_TOKEN); await bot.start({ webhook: process.env.NODE_ENV === "production" ? { url: `${process.env.API_URL}/${process.env.BOT_TOKEN}`, } : undefined, }); ``` ## Локальная разработка с вебхуками Для локальной разработки с вебхуками мы рекомендуем использовать untun Logounjs/untun. **Untun** — это инструмент для создания туннеля между вашим **локальным** HTTP(s) сервером и внешним миром! > [!IMPORTANT] > Примеры запуска с конкретными фреймворками опущены. Смотрите [этот пример](#пример). ### Через API Этот метод позволяет установить ссылку на наш туннель непосредственно в скрипте. Установите пакет: ::: code-group ```bash [npm] npm install untun ``` ```bash [yarn] yarn add untun ``` ```bash [pnpm] pnpm install untun ``` ```bash [bun] bun install untun ``` ::: Запустите туннель и установите вебхук: ```ts import { startTunnel } from "untun"; const tunnel = await startTunnel({ port: 3000 }); bot.start({ webhook: { url: await tunnel.getURL(), }, }); ``` ### Через CLI Мы прослушиваем порт `3000` локально. Поэтому открываем туннель следующим образом: ::: code-group ```bash [npm] npx untun@latest tunnel http://localhost:3000 ``` ```bash [yarn] yarn dlx untun@latest tunnel http://localhost:3000 ``` ```bash [pnpm] pnpm dlx untun@latest tunnel http://localhost:3000 ``` ```bash [bun] bunx untun@latest tunnel http://localhost:3000 ``` ::: ```bash ◐ Starting cloudflared tunnel to http://localhost:3000 ℹ Waiting for tunnel URL... ✔ Tunnel ready at https://unjs-is-awesome.trycloudflare.com ``` Теперь мы используем эту ссылку при установке вебхука: ```ts bot.start({ webhook: { url: "https://unjs-is-awesome.trycloudflare.com", }, }); ``` ## Напишите свой собственный обработчик обновлений ```ts // не существующий фреймворк для примера import { App } from "some-http-framework"; import { Bot } from "gramio"; const bot = new Bot(process.env.BOT_TOKEN as string).on("message", (context) => context.send("Hello!") ); // init обязателен. Он используется для ленивой загрузки плагинов, а также получает информацию о боте. await bot.init(); const app = new App().post("/telegram", async (req) => { // req.body должен быть json эквивалентом TelegramUpdate await bot.updates.handleUpdate(req.body); }); app.listen(80); ``` --- --- url: /ru/get-started.md --- # Начало работы Создайте нового бота с GramIO за считанные минуты. У вас уже должен быть установлен [Node.js](https://nodejs.org/), [Bun](https://bun.sh/) или [Deno](https://deno.com/). ## Получите токен бота Сначала создайте бота и получите `токен`. Вы можете сделать это с помощью бота [@BotFather](https://t.me/BotFather). Отправьте команду `/newbot` и следуйте инструкциям, пока не получите токен вида `110201543:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw`. ## Создание проекта вместе с `create-gramio` Эта команда поможет вам создать проект с GramIO самым простым способом. ::: code-group ```bash [npm] npm create gramio@latest ./bot ``` ```bash [yarn] yarn create gramio@latest ./bot ``` ```bash [pnpm] pnpm create gramio@latest ./bot ``` ```bash [bun] bun create gramio@latest ./bot ``` ```bash [deno] TODO:// Deno поддерживается, но не в генераторе проектов ``` ::: #### Поддерживаемые инструменты - ORM/Query builders - - [Prisma](https://www.prisma.io/) - - [Drizzle](https://orm.drizzle.team/) - Линтеры - - [Biome](https://biomejs.dev/) - - [ESLint](https://eslint.org/) с [@antfu/eslint-config](https://github.com/antfu/eslint-config) - Плагины - - [Scenes](https://gramio.dev/ru/plugins/official/scenes.html) - - [Session](https://gramio.dev/ru/plugins/official/session.html) - - [Autoload](https://gramio.dev/ru/plugins/official/autoload.html) - - [Prompt](https://gramio.dev/ru/plugins/official/prompt.html) - - [Auto-retry](https://gramio.dev/ru/plugins/official/auto-retry.html) - - [Media-cache](https://gramio.dev/ru/plugins/official/media-cache.html) - - [I18n](https://gramio.dev/ru/plugins/official/i18n.html) - - [Media-group](https://gramio.dev/ru/plugins/official/media-group.html) - Другое - - [Dockerfile](https://www.docker.com/) + [docker-compose.yml](https://docs.docker.com/compose/) - - [Jobify](https://github.com/kravetsone/jobify) (обертка для [Bullmq](https://docs.bullmq.io/)) - - [Husky](https://typicode.github.io/husky/) (Git-хуки) - - [Fluent2ts](https://github.com/kravetsone/fluent2ts) - - [GramIO storages](https://gramio.dev/ru/storages/) - [Telegram apps](https://github.com/Telegram-Mini-Apps/telegram-apps/tree/master/packages/create-mini-app) - [Elysia](https://elysiajs.com/) (через [create-elysiajs](https://github.com/kravetsone/create-elysiajs)) > Инструменты могут работать `вместе` > > Когда вы выбираете [ESLint](https://eslint.org/) и [Drizzle](https://orm.drizzle.team/), вы получаете [eslint-plugin-drizzle](https://orm.drizzle.team/docs/eslint-plugin) ## Ручная установка Чтобы вручную создать нового бота с GramIO, установите пакет: ::: code-group ```bash [npm] npm install gramio ``` ```bash [yarn] yarn add gramio ``` ```bash [pnpm] pnpm add gramio ``` ```bash [bun] bun install gramio ``` ::: Настройте TypeScript: ::: code-group ```bash [npm] npm install typescript -D npx tsc --init ``` ```bash [yarn] yarn add typescript -D yarn dlx tsc --init ``` ```bash [pnpm] pnpm add typescript -D pnpm exec tsc --init ``` ```bash [bun] bun install typescript -D bunx tsc --init ``` ::: Создайте папку `src` с файлом `index.ts` и напишите что-то вроде: ::: code-group ```ts twoslash [Bun или Node.js] import { Bot } from "gramio"; const bot = new Bot("") // укажите ваш токен здесь .command("start", (context) => context.send("Привет!")) .onStart(console.log); bot.start(); ``` ```ts [Deno] import { Bot } from "jsr:@gramio/core"; const bot = new Bot("") // укажите ваш токен здесь .command("start", (context) => context.send("Привет!")) .onStart(console.log); bot.start(); ``` ::: И запустите бота с помощью: ::: code-group ```bash [tsx] npx tsx ./src/index.ts ``` ```bash [bun] bun ./src/index.ts ``` ```bash [deno] deno run --allow-net ./src/index.ts ``` ::: Готово! 🎉 Теперь вы можете взаимодействовать со своим ботом Telegram. --- --- url: /ru/triggers/inline-query.md --- # inlineQuery Метод `inlineQuery` в GramIO позволяет вашему боту отвечать на инлайн-запросы, отправленные пользователями. Инлайн-запрос - это особый тип сообщения, когда пользователи могут искать контент от вашего бота, введя запрос в поле ввода сообщения, не взаимодействуя напрямую с ботом. ![предварительный просмотр функции](https://core.telegram.org/file/464001466/10e4a/r4FKyQ7gw5g.134366/f2606a53d683374703) [Документация Telegram](https://core.telegram.org/bots/inline) > [!WARNING] > Вам необходимо включить эту опцию через [@BotFather](https://telegram.me/botfather). Отправьте команду `/setinline`, выберите бота и укажите текст-заполнитель, который пользователь увидит в поле ввода после ввода имени вашего бота. ## Основное использование ### Ответ на инлайн-запрос с использованием регулярного выражения Вы можете настроить бота на прослушивание определенных инлайн-запросов, соответствующих регулярному выражению, и ответить результатами. Вот пример: ```ts bot.inlineQuery(/search (.*)/i, async (context) => { if (context.args) { await context.answer([ InlineQueryResult.article( "id-1", `Результат для ${context.args.at(1)}`, InputMessageContent.text( `Это результат сообщения для запроса ${context.args.at(1)}` ) ), ]); } }); ``` В этом примере: - Бот прослушивает инлайн-запросы, соответствующие шаблону `search (.*)`. - Если соответствие найдено, бот извлекает поисковый запрос и возвращает список результатов инлайн-запроса. ### Параметры - **`trigger`**: Условие, которому должен соответствовать инлайн-запрос. Это может быть регулярное выражение, строка или пользовательская функция. - **Регулярное выражение**: Соответствует запросам, которые соответствуют определенному шаблону. - **Строка**: Соответствует точной строке запроса. - **Функция**: Оценивает запрос на основе пользовательской логики. Должна возвращать true для соответствия. - **`handler`**: Функция, которая обрабатывает инлайн-запрос. Она получает объект `context`, который включает детали о запросе и предоставляет методы для ответа. - **`options`**: Дополнительные опции для обработки выбора результата. - **`onResult`**: Функция, которая вызывается, когда пользователь выбирает результат инлайн-запроса. Она может использоваться для изменения или обновления сообщения после выбора. Использует [ChosenInlineResult](/ru/triggers/chosen-inline-result) под капотом. > [!IMPORTANT] > Вы можете изменять только сообщения, которые содержат InlineKeyboard. Подробнее о [ChosenInlineResult](/ru/triggers/chosen-inline-result). ### Как работает `inlineQuery` 1. **Сопоставление запроса**: Когда пользователь вводит инлайн-запрос, метод `inlineQuery` проверяет, соответствует ли запрос предоставленному `trigger`. Это может быть прямое соответствие (строка), соответствие шаблону (регулярное выражение) или проверка условия (функция). 2. **Обработка запроса**: Если запрос соответствует, вызывается функция `handler`. Внутри этой функции вы можете получить доступ к параметрам запроса, сгенерировать результаты и отправить их обратно пользователю, используя `context.answer()`. 3. **Реагирование на выбор результата**: Если предоставлена опция `onResult`, бот прослушивает, когда пользователь выбирает один из результатов инлайн-запроса. Затем предоставленная функция может, например, изменить текст сообщения. ### Пример: Пользовательский обработчик инлайн-запросов Вот более подробный пример, демонстрирующий использование как триггера инлайн-запроса, так и обработки выбора результата: ```ts bot.inlineQuery( /search (.*)/i, async (context) => { if (context.args) { await context.answer( [ InlineQueryResult.article( "result-1", `Результат для ${context.args[1]}`, InputMessageContent.text( `Вы искали: ${context.args[1]}` ), { reply_markup: new InlineKeyboard().text( "Получить подробности", "details-callback" ), } ), ], { cache_time: 0, } ); } }, { onResult: (context) => context.editText("Вы выбрали результат!"), } ); ``` В этом примере: - Бот прослушивает инлайн-запросы, которые начинаются с `search `, за которыми следует поисковый термин. - Бот отвечает результатом инлайн-запроса, который включает кнопку с надписью "Получить подробности." - Когда пользователь выбирает этот результат, бот редактирует сообщение, чтобы указать, что был сделан выбор. --- --- url: /ru/triggers/command.md --- # Команды Метод command в GramIO позволяет создавать обработчики для определенных [команд бота](https://core.telegram.org/bots/features#commands). Команды - это определенные слова или фразы, которые обычно начинаются с `/` и используются для вызова действий или ответов от бота. Приложения Telegram будут: - **Выделять** команды в сообщениях. Когда пользователь нажимает на выделенную команду, эта команда сразу же отправляется снова. - Предлагать **список поддерживаемых команд** с описаниями, когда пользователь вводит `/` (для этого вам нужно предоставить список команд [@BotFather](https://t.me/botfather) или через [соответствующий метод API](https://core.telegram.org/bots/api#setmycommands)). Выбор команды из списка сразу отправляет её. - Показывать [кнопку меню](https://core.telegram.org/bots/features#menu-button), содержащую все или некоторые команды бота (которые вы устанавливаете через [@BotFather](https://t.me/botfather)). Команды всегда должны начинаться с символа `/` и содержать **до 32 символов**. Они могут использовать **латинские буквы**, **цифры** и **подчеркивания**, хотя для более чистого вида рекомендуется использовать простой текст в нижнем регистре. > [!IMPORTANT] > Команда также может быть вызвана с использованием полной команды с именем пользователя бота, например, `/start@name_bot`. ![](https://core.telegram.org/file/464001775/10227/HCr0XgSUHrg.119089/c17ff5d34fe528361e) ## Основное использование ### Регистрация простой команды Вы можете использовать метод `command`, чтобы ваш бот отвечал на определенные команды. Вот простой пример: ```ts bot.command("start", (context) => { return context.send(`Привет!`); }); ``` > [!IMPORTANT] > Вам не нужно включать `/` при указании имени команды. Если вы включите его, вы получите `ошибку`. В этом примере бот прослушивает команду `/start`. Когда команда получена, бот отвечает простым сообщением "Привет!". ### Пример с аргументами команды Вот пример, демонстрирующий, как использовать аргументы с командами: ```ts bot.command("start", async (context) => { return context.send( `Вы ввели команду /start с аргументами: ${context.args}` ); }); ``` В этом сценарии, если пользователь отправляет `/start arg1 arg2`, бот ответит `Вы ввели команду /start с аргументами: arg1 arg2`. ### Как работает `command` 1. **Распознавание команды:** Бот проверяет сообщение на наличие команды (например, `/start`). Команды обычно обозначаются сущностью [`bot_command`](https://core.telegram.org/bots/api#messageentity) в сообщениях Telegram. 2. **Сопоставление команды:** Бот сравнивает обнаруженную команду с командой, которую вы указали в методе `command`. 3. **Обработка команд с именем пользователя бота:** Команда также распознается, если она включает имя пользователя бота, например, `/start@name_bot`. 4. **Аргументы команды:** Если после команды есть дополнительные аргументы (например, `/start arg1 arg2`), они передаются в `context.args` для дальнейшей обработки. --- --- url: /ru/updates/overview.md --- # Обработка обновлений ## Start Метод `start` запускает процесс получения обновлений от Telegram для вашего бота. В зависимости от переданных параметров, бот может использовать long-polling или webhook для получения событий. Этот метод инициализирует бота, подгружает [lazy плагины](/ru/plugins/lazy-load) и вызывает хук [`onStart`](/ru/hooks/on-start). **Сигнатура:** ```ts start(options?): Promise ``` **Параметры:** - `options` — объект с настройками запуска: - `webhook` — параметры для запуска через webhook (`true`, строка-URL или объект с параметрами). - `longPolling` — параметры для long-polling (например, таймауты). - `dropPendingUpdates` — сбрасывать ли неотправленные обновления при запуске. - `allowedUpdates` — список типов обновлений, которые бот будет получать. - `deleteWebhook` — как поступать с существующим webhook при запуске long-polling. > [!IMPORTANT] > > **Особенности параметров:** > > - Если указать `webhook: true`, GramIO не будет пытаться установить webhook самостоятельно — предполагается, что вы уже настроили его. В этом случае бот просто начнёт принимать обновления через уже существующий webhook. > > - Параметр `deleteWebhook` управляет тем, что делать с существующим webhook при запуске long-polling: > - Если `deleteWebhook: true`, бот всегда удаляет webhook перед запуском long-polling. > - Если `deleteWebhook: "on-conflict-with-polling"`, webhook будет удалён только если он мешает запуску long-polling (когда Telegram отвечает на запрос `getUpdates` с ошибкой конфликта). > - Если не указано, используется поведение по умолчанию (`on-conflict-with-polling`). ```ts import { Bot } from "gramio"; const bot = new Bot(process.env.BOT_TOKEN) .command("start", (ctx) => ctx.send("Привет!")) .onStart(console.log); await bot.start({ longPolling: { timeout: 10 }, dropPendingUpdates: true, }); ``` **Описание работы:** - Если не указан webhook, запускается long-polling. - Если указан webhook, настраивается webhook и бот начинает принимать обновления через HTTP. - Вызывает хук [`onStart`](/ru/hooks/on-start). - Можно сбросить старые обновления при запуске. ## Stop Метод `stop` завершает приём обновлений и корректно останавливает все внутренние процессы бота. Вызывается хук [`onStop`](/ru/hooks/on-stop), очищается очередь обновлений. **Сигнатура:** ```ts stop(timeout?): Promise ``` **Параметры:** - `timeout` — время ожидания завершения обработки очереди обновлений (по умолчанию 3000 мс). **Пример использования:** ```ts await bot.stop(); ``` **Описание работы:** - Останавливает long-polling или webhook (если был запущен). - Дожидается завершения обработки всех текущих обновлений. - Вызывает хук [`onStop`](/ru/hooks/on-stop). ## Контекст ## Прослушивание всех событий ```ts bot.use((context, next) => { // ... }); ``` ## Прослушивание только определенных событий ```ts bot.on("message", (context, next) => { // ... }); // или bot.on(["message", "callback_query"], (context, next) => { // ... }); ``` Вы можете ознакомиться с API Reference для контекстов [здесь](https://jsr.io/@gramio/contexts/doc). # Внедрение в контекст ## Derive `Derive` позволяет внедрить в контекст что угодно, с доступом к существующим данным контекста и с проверкой типов. Обработчик будет вызываться при **каждом** обновлении. #### Глобальный derive ```ts twoslash // @errors: 2339 1003 import { Bot } from "gramio"; const bot = new Bot(process.env.BOT_TOKEN as string) .derive((context) => { return { key: 1, }; }) .on("message", (context) => { context.key; // ^? }) .use((context, next) => { context.key; // ^? return next(); }) .on("callback_query", (context) => { context.key; // ^? }); ``` #### Ограниченный (scoped) derive Вы можете ограничить derive для конкретного **обновления** (или **обновлений**) с полной поддержкой типов. ```ts twoslash // @errors: 2339 1003 import { Bot } from "gramio"; // Простой пример export function findOrRegisterUser(id: number) { return {} as Promise<{ id: number; name: string; balance: number }>; } const bot = new Bot(process.env.BOT_TOKEN as string) .derive(["message", "callback_query"], async (context) => { const fromId = context?.from?.id; if(!fromId) throw new Error("No from id"); return { fromId, } }) .derive("message", async (context) => { const user = await findOrRegisterUser(context.fromId); // ^? return { user, }; }) .on("message", (context) => { context.user.; // ^| // }) .use((context, next) => { if (context.is("message")) context.user; // ^? return next(); }) .on("callback_query", (context) => { context.user; // ^? context.fromId // ^? }); ``` ## Decorate С помощью `decorate` вы можете внедрить **статические значения**, и они будут внедрены в контексты **только когда вызывается `decorate`** (т.е. они не будут вычисляться при каждом обновлении). ```ts twoslash // @errors: 2339 import { Bot } from "gramio"; const bot = new Bot(process.env.BOT_TOKEN as string) .decorate("TEST", "привет!! ты милашка" as const) .decorate({ key: "значение", key2: "значение", }) .use((context) => { context.TEST; // ^? // context.k; // ^| }); ``` --- --- url: /ru/triggers/reaction.md --- # reaction Метод `reaction` используется для регистрации обработчика изменений реакций на сообщение. Этот метод позволяет вашему боту реагировать, когда пользователь отвечает на сообщение эмодзи. Указав триггер (на какой эмодзи реагировать) и соответствующий обработчик, вы можете настроить, как ваш бот будет отвечать на такие взаимодействия. ## Ключевые особенности ### Обработка реакций Чтобы использовать метод `reaction`, вы определяете триггер (или несколько триггеров) и обработчик. Триггер - это эмодзи (или эмодзи), которые, при реакции на сообщение, вызовут выполнение обработчика. ```ts bot.reaction("👍", async (context) => { await context.reply(`Спасибо!`); }); ``` В этом примере: - Метод `reaction` вызывается с триггером `"👍"`, который является эмодзи. - Всякий раз, когда пользователь реагирует на сообщение эмодзи с большим пальцем вверх (`👍`), бот выполнит обработчик и ответит "Спасибо!". ### Типы триггеров Параметр `trigger` может быть одним эмодзи или массивом эмодзи. Обработчик будет выполняться, если используется любой из указанных эмодзи. ```ts bot.reaction(["👍", "❤️"], async (context) => { await context.reply(`Спасибо за вашу реакцию!`); }); ``` В этом примере: - Бот ответит, если пользователь отреагирует либо большим пальцем вверх (`👍`), либо сердцем (`❤️`). --- --- url: /ru/files/overview.md --- # Обзор [`@gramio/files`](https://github.com/gramiojs/files) - это встроенный пакет GramIO. Вы также можете использовать его вне этого фреймворка, так как он не зависит от него. ## Использование ```ts twoslash // @errors: 2345 import { Bot, MediaInput, MediaUpload, InlineKeyboard } from "gramio"; const bot = new Bot(process.env.BOT_BOT_TOKEN as string) .on("message", async (ctx) => { ctx.sendMediaGroup([ MediaInput.document( await MediaUpload.url( "https://raw.githubusercontent.com/gramiojs/types/main/README.md" ) ), MediaInput.document(await MediaUpload.path("./package.json")), ]); }) .onStart(console.log); bot.start(); ``` ## Отправка файлов Существует три способа отправки файлов (фотографий, стикеров, аудио, медиа и т.д.): 1. Если файл уже хранится на серверах Telegram, вам не нужно загружать его повторно: каждый файловый объект имеет поле file_id, просто передайте этот file_id в качестве параметра вместо загрузки. Для файлов, отправляемых таким способом, ограничений нет. > [!TIP] > Вам может быть полезно использовать [media-cache](/ru/plugins/official/media-cache) плагин для кэширования file_id со стороны фреймворка. 2. Предоставьте Telegram HTTP URL для отправляемого файла. Telegram загрузит и отправит файл. Максимальный размер 5 МБ для фотографий и 20 МБ для других типов контента. 3. Отправьте файл, используя multipart/form-data обычным способом, которым файлы загружаются через браузер. Максимальный размер 10 МБ для фотографий, 50 МБ для других файлов (если TelegramBotAPI = `api.telegram.org`). ### Отправка по `file_id` - Невозможно изменить тип файла при повторной отправке по file_id. То есть видео нельзя отправить как фото, фото нельзя отправить как документ и т.д. - Невозможно повторно отправить миниатюры. - Повторная отправка фотографии по file_id отправит все ее размеры. - file_id уникален для каждого отдельного бота и не может быть передан от одного бота другому. - file_id однозначно идентифицирует файл, но файл может иметь разные действительные file_id даже для одного и того же бота. Чтобы отправить файл по `file_id` с помощью GramIO, вы можете указать его `как есть`. ```ts twoslash import { BotLike, MessageContext } from "gramio"; const fileId = "" as string; const ctx = {} as InstanceType>; // ---cut--- ctx.sendPhoto(fileId); ``` ### Отправка по `URL` - При отправке по URL целевой файл должен иметь правильный MIME-тип (например, audio/mpeg для sendAudio и т.д.). - В sendDocument отправка по URL в настоящее время работает только для файлов GIF, PDF и ZIP. - Чтобы использовать sendVoice, файл должен иметь тип audio/ogg и размер не более 1 МБ. Голосовые заметки размером 1-20 МБ будут отправлены как файлы. Чтобы отправить файл по `URL` с помощью GramIO, вы можете указать его `как есть`. ```ts twoslash import { BotLike, MessageContext } from "gramio"; const ctx = {} as InstanceType>; // ---cut--- ctx.sendPhoto("https://.../cute-cat.png"); ``` ### Загрузка файлов Для загрузки и отправки файла вы можете использовать вспомогательный класс [`Media Upload`](/ru/files/media-upload.html), и GramIO сделает всю работу за вас. ```ts twoslash // @errors: 2345 import { MediaUpload } from "@gramio/files"; import { BotLike, MessageContext } from "gramio"; const ctx = {} as InstanceType>; // ---cut--- ctx.sendPhoto(await MediaUpload.path("../cute-cat.png")); ``` #### Использование [`File`](https://developer.mozilla.org/ru/docs/Web/API/File) На самом деле, GramIO может принимать Web API [`File`](https://developer.mozilla.org/ru/docs/Web/API/File) и [`Blob`](https://developer.mozilla.org/ru/docs/Web/API/Blob). Так что вы можете загружать файлы даже так: ```ts import { Elysia } from "elysia"; import { bot } from "./bot"; new Elysia().post( "/", ({ body: { file } }) => { bot.api.sendPhoto({ chat_id: 1, photo: file, }); }, { body: t.Object({ file: t.File({ type: "image", maxSize: "2m", }), // Elysia проверяет и принимает только тип File }), } ); ``` Или, например, с [нативным чтением файлов Bun](https://bun.sh/docs/api/file-io#reading-files-bun-file) ```ts bot.api.sendDocument({ chat_id: 1, document: Bun.file("README.md"), }); ``` --- --- url: /ru/files/media-input.md --- # Media Input Класс-помощник со статическими методами, который представляет содержимое медиа-сообщения для отправки. [API Reference](https://jsr.io/@gramio/files/doc) [Документация](https://core.telegram.org/bots/api/#inputmedia) ## document Представляет общий файл для отправки. ```ts twoslash // @errors: 2345 import { BotLike, MessageContext } from "gramio"; import { MediaInput, MediaUpload } from "@gramio/files"; const ctx = {} as InstanceType>; // ---cut--- // ctx из bot.on("message", (ctx) => ...) ctx.sendMediaGroup([ MediaInput.document( await MediaUpload.url( "https://raw.githubusercontent.com/gramiojs/types/main/README.md" ) ), MediaInput.document(await MediaUpload.path("./package.json")), ]); ``` [Документация](https://core.telegram.org/bots/api/#inputmediadocument) ## audio Представляет аудиофайл, который будет обрабатываться как музыка для отправки. ```ts twoslash // @errors: 2345 import { BotLike, MessageContext } from "gramio"; import { MediaInput, MediaUpload } from "@gramio/files"; const ctx = {} as InstanceType>; // ---cut--- // ctx из bot.on("message", (ctx) => ...) ctx.sendMediaGroup([ MediaInput.audio(await MediaUpload.url("https://.../music.mp3")), MediaInput.audio(await MediaUpload.path("./music.mp3")), ]); ``` [Документация](https://core.telegram.org/bots/api/#inputmediaaudio) ## photo Представляет фотографию для отправки. ```ts twoslash // @errors: 2345 import { BotLike, MessageContext } from "gramio"; import { MediaInput, MediaUpload } from "@gramio/files"; const ctx = {} as InstanceType>; // ---cut--- // ctx из bot.on("message", (ctx) => ...) ctx.sendMediaGroup([ MediaInput.photo( await MediaUpload.url( "https://w7.pngwing.com/pngs/140/552/png-transparent-kitten-if-cats-could-talk-the-meaning-of-meow-pet-sitting-dog-pet-dog-mammal-animals-cat-like-mammal.png" ), { has_spoiler: true, caption: "MaybeCat" } ), MediaInput.photo(await MediaUpload.path("./no-cat.png")), ]); ``` [Документация](https://core.telegram.org/bots/api/#inputmediaphoto) ## video Представляет видео для отправки. ```ts twoslash // @noErrors import { BotLike, MessageContext } from "gramio"; import { MediaInput, MediaUpload } from "@gramio/files"; const ctx = {} as InstanceType>; // ---cut--- // ctx из bot.on("message", (ctx) => ...) ctx.sendMediaGroup([ MediaInput.video(await MediaUpload.url("https://.../video.mp4"), { has_spoiler: true, thumbnail: MediaUpload.buffer(/**буфер файла */), }), MediaInput.photo(await MediaUpload.path("./cat-walk.mp4")), ]); ``` [Документация](https://core.telegram.org/bots/api/#inputmediavideo) ## animation Представляет анимационный файл (`GIF` или видео `H.264/MPEG-4 AVC` без звука) для отправки. ```ts twoslash // @noErrors import { MediaInput, MediaUpload } from "@gramio/files"; // ---cut--- MediaInput.animation( await MediaUpload.url( "https://media1.tenor.com/m/47qpxBq_Tw0AAAAd/cat-cat-meme.gif" ) ); ``` [Документация](https://core.telegram.org/bots/api/#inputmediaanimation) --- --- url: /ru/plugins/official/i18n.md --- # Плагин i18n
[![npm](https://img.shields.io/npm/v/@gramio/i18n?logo=npm&style=flat&labelColor=000&color=3b82f6)](https://www.npmjs.org/package/@gramio/i18n) [![JSR](https://jsr.io/badges/@gramio/i18n)](https://jsr.io/@gramio/i18n) [![JSR Score](https://jsr.io/badges/@gramio/i18n/score)](https://jsr.io/@gramio/i18n)
Плагин `i18n` для [GramIO](https://gramio.dev/). Этот плагин помогает добавить многоязычность в ваши боты с помощью синтаксиса [Fluent](https://projectfluent.org/). ![example](https://github.com/gramiojs/i18n/assets/57632712/47e04c22-f442-4a5a-b8b9-15b8512f7c4b) Вы можете настроить [типо-безопасность](#типо-безопасность) для него. Плагин `i18n` для [GramIO](https://gramio.dev/). Этот плагин предоставляет удобный способ добавить многоязычность в ваши боты! Его можно использовать без GramIO, но он всегда будет учитывать его особенности. > [!IMPORTANT] > Начиная с версии `1.0.0`, у нас есть два способа написания локализации: [`I18n-in-TS`](#синтаксис-i18n-in-ts) и [`Fluent`](#синтаксис-fluent) ### Установка Для [синтаксиса I18n-in-TS](#синтаксис-i18n-in-ts) ::: code-group ```bash [npm] npm install @gramio/i18n ``` ```bash [bun] bun add @gramio/i18n ``` ```bash [yarn] yarn add @gramio/i18n ``` ```bash [pnpm] pnpm add @gramio/i18n ``` ::: Для [синтаксиса Fluent](#синтаксис-fluent) ::: code-group ```bash [npm] npm install @gramio/i18n @fluent/bundle ``` ```bash [bun] bun add @gramio/i18n @fluent/bundle ``` ```bash [yarn] yarn add @gramio/i18n @fluent/bundle ``` ```bash [pnpm] pnpm add @gramio/i18n @fluent/bundle ``` ::: ## Синтаксис I18n-in-TS Этот синтаксис позволяет вам писать локализацию, не выходя из файлов `.ts`, и не требует генерации кода для **типо-безопасности**, а также обеспечивает удобную интеграцию с **Format API** из коробки! ```ts twoslash import { format, Bot } from "gramio"; import { defineI18n, type LanguageMap, type ShouldFollowLanguage, } from "@gramio/i18n"; const en = { greeting: (name: string) => format`Hello, ${name}!`, and: { some: { nested: "Hi!!!", }, }, } satisfies LanguageMap; const ru = { greeting: (name: string) => format`Привет, ${name}!`, and: { some: { nested: "Hi!!!", }, }, } satisfies ShouldFollowLanguage; // Strict покажет ошибку при отсутствии ключей // satisfies ShouldFollowLanguageStrict; const i18n = defineI18n({ primaryLanguage: "en", languages: { en, ru, }, }); i18n.t("en", "greeting", "World"); // Hello, World! i18n.t("en", "and.some.nested"); // Hi!!! const bot = new Bot(process.env.BOT_TOKEN as string) .derive("message", (context) => { // вы можете взять язык из базы данных или любого другого источника и привязать его к контексту, не теряя типо-безопасности return { t: i18n.buildT(context.from?.languageCode), }; }) .on("message", (context) => { return context.send( context.t("greeting", context.from?.firstName ?? "World") ); }); ``` ### Множественные числа ```ts import { pluralizeEnglish, pluralizeRussian } from "@gramio/i18n"; const count = 5; console.log(`You have ${count} ${pluralizeEnglish(count, "apple", "apples")}.`); // You have 5 apples. console.log( `У вас ${count} ${pluralizeRussian(count, "яблоко", "яблока", "яблок")}.` ); // У вас 5 яблок. ``` `ExtractLanguages` помогает извлечь типы языков из экземпляра i18n. ```ts type EnLocalization = ExtractLanguages["en"]; type EnLocalizationKeys = keyof ExtractLanguages["en"]; type EnGreetingArgs = ExtractArgsParams; ``` ## Синтаксис [Fluent](https://projectfluent.org/) Этот плагин помогает добавить многоязычность в ваши боты с помощью синтаксиса [Fluent](https://projectfluent.org/). ![example](https://github.com/gramiojs/i18n/assets/57632712/47e04c22-f442-4a5a-b8b9-15b8512f7c4b) Вы можете настроить [типо-безопасность](#типо-безопасность) для него. ## Использование ### Создайте папку `locales` с файлом `ru.ftl` ```ftl # Простые вещи просты. hello-user = Привет, {$userName}! # Сложные вещи возможны. shared-photos = {$userName} {$photoCount -> [one] добавил новую фотографию [few] добавил {$photoCount} новые фотографии *[other] добавил {$photoCount} новых фотографий } в {$userGender -> [male] свою ленту [female] свою ленту *[other] свою ленту }. ``` > [!IMPORTANT] > Есть расширения с поддержкой языка Fluent для [VSCode](https://marketplace.visualstudio.com/items?itemName=macabeus.vscode-fluent) и [WebStorm](https://plugins.jetbrains.com/plugin/18416-fluent-language) ### Использование плагина ```ts // @moduleResolution: NodeNext // @module: NodeNext // src/index.ts import { Bot } from "gramio"; import { i18n } from "@gramio/i18n/fluent"; const bot = new Bot(process.env.BOT_TOKEN as string) .extend(i18n()) .command("start", async (context) => { return context.send( context.t("shared-photos", { userName: "Анна", userGender: "female", photoCount: 3, }) ); }) .onError(console.error) .onStart(console.log); bot.start(); ``` ## Опции | Ключ | Тип | По умолчанию | Описание | | -------------- | ------ | ------------ | ------------------------------ | | defaultLocale? | string | "en" | Язык по умолчанию | | directory? | string | "locales" | Путь к папке с файлами `*.ftl` | ##### Или предоставьте клиент ```ts twoslash import { Bot } from "gramio"; import { getFluentClient, i18n } from "@gramio/i18n/fluent"; // или getFluentClient() const client = getFluentClient({ defaultLocale: "ru", directory: "locales", }); const bot = new Bot(process.env.BOT_TOKEN as string) .extend(i18n(client)) .command("start", async (context) => { return context.send(context.t("hello-user", { userName: "Анна" })); }); ``` > [!IMPORTANT] > См. [Типо-безопасность](#типо-безопасность). Для обеспечения типо-безопасности нужно указать дженерик для `getFluentClient`. ### Методы #### t С помощью этого метода вы можете получить текст на выбранном языке. Например: ```ftl hello-user = Привет, {$userName}! ``` ```ts context.t("hello-user", { userName: "Анна" }); // Привет, Анна! ``` #### setLocale Вы можете установить язык пользователя методом `setLocale`. > [!WARNING] > На данный момент нет интеграции с сессиями, поэтому после сообщения язык снова станет таким, какой указан в defaultLocale ```ts bot.command("start", async (context) => { context.setLocale("ru"); return context.send( context.t("shared-photos", { userName: "Анна", userGender: "female", photoCount: 3, }) ); }); ``` ## Типо-безопасность Вы можете использовать этот плагин с [fluent2ts](https://github.com/kravetsone/fluent2ts), который генерирует типы TypeScript из ваших файлов `.ftl`. См. [инструкцию по использованию](https://github.com/kravetsone/fluent2ts?tab=readme-ov-file#usage). Npm: ```bash [npm] npx fluent2ts ``` Bun: ```bash [bun] bunx fluent2ts ``` Yarn: ```bash [yarn] yarn dlx fluent2ts ``` Pnpm: ```bash [pnpm] pnpm exec fluent2ts ``` В результате вы получите сгенерированный файл `locales.types.ts` в папке `src`, который экспортирует интерфейс `TypedFluentBundle`. Устанавливаем этот тип как **дженерик** для плагина `i18n` - и вот у нас есть **типо-безопасность**! ```ts twoslash // @filename: locales.types.ts import type { FluentBundle, FluentVariable, Message as FluentMessage, } from "@fluent/bundle"; export interface LocalesMap { "hello-user": never; "shared-photos": { userName: FluentVariable; photoCount: FluentVariable; userGender: FluentVariable; }; } export interface Message extends FluentMessage { id: Key; } export interface TypedFluentBundle extends FluentBundle { getMessage(key: Key): Message; formatPattern( key: Key, ...args: LocalesMap[Key] extends never ? [] : [args: LocalesMap[Key]] ): string; formatPattern( key: Key, args: LocalesMap[Key] extends never ? null : LocalesMap[Key], errors?: Error[] | null ): string; } // @filename: index.ts // @errors: 2554 // @moduleResolution: NodeNext // @module: NodeNext // ---cut--- import type { TypedFluentBundle } from "./locales.types.js"; import { Bot } from "gramio"; import { i18n } from "@gramio/i18n/fluent"; const bot = new Bot(process.env.BOT_TOKEN as string) // или .extend(i18n(createFluentClient())) .extend(i18n()) .command("start", async (context) => { const firstMsg = context.t("hello-user"); // // // const secondMsg = context.t("shared-photos", { // ^? userName: "Анна", userGender: "female", photoCount: 3, }); // // // // return context.send(secondMsg); }) .onError(console.error) .onStart(console.log); bot.start(); ``` --- --- url: /ru/plugins/official/prompt.md --- # Плагин Prompt
[![npm](https://img.shields.io/npm/v/@gramio/prompt?logo=npm&style=flat&labelColor=000&color=3b82f6)](https://www.npmjs.org/package/@gramio/prompt) [![JSR](https://jsr.io/badges/@gramio/prompt)](https://jsr.io/@gramio/prompt) [![JSR Score](https://jsr.io/badges/@gramio/prompt/score)](https://jsr.io/@gramio/prompt)
Плагин, который предоставляет методы [Prompt](#prompt) и [Wait](#wait) ### Установка ::: code-group ```bash [npm] npm install @gramio/prompt ``` ```bash [yarn] yarn add @gramio/prompt ``` ```bash [pnpm] pnpm add @gramio/prompt ``` ```bash [bun] bun install @gramio/prompt ``` ::: ## Использование ```ts import { Bot, format, bold } from "gramio"; import { prompt } from "@gramio/prompt"; const bot = new Bot(process.env.BOT_TOKEN as string) .extend(prompt()) .command("start", async (context) => { const answer = await context.prompt( "message", format`Как вас ${bold`зовут`}?` ); return context.send(`✨ Ваше имя: ${answer.text}`); }) .onStart(console.log); bot.start(); ``` ## Prompt ### Prompt с текстом + параметрами ```ts const answer = await context.prompt("Как вас зовут?"); // или с SendMessageParams const answer = await context.prompt("Правда или ложь?", { reply_markup: new Keyboard().text("правда").row().text("ложь"), }); ``` ответ - это объект `MessageContext` или `CallbackQueryContext` ### Prompt с текстом + параметрами и указанным событием ```ts const answer = await context.prompt("message", "Как вас зовут?"); const answer = await context.prompt("callback_query", "Правда или ложь?", { reply_markup: new InlineKeyboard() .text("правда", "true") .row() .text("ложь", "false"), }); ``` ответ - это объект `CallbackQueryContext` ### Валидация Можно указать обработчик в параметрах для проверки ответа пользователя. Если обработчик вернёт false, сообщение будет отправлено повторно. ```ts const answer = await context.prompt( "message", "Введите строку, содержащую русскую букву", { validate: (context) => /[а-яА-Я]/.test(context.text), //... и другие SendMessageParams } ); ``` ### Трансформация ```ts const name = await context.prompt( "message", format`Как вас ${bold`зовут`}?`, { transform: (context) => context.text || context.caption || "", } ); ``` name имеет тип `string` ## Wait ### Ожидание следующего события от пользователя ```ts const answer = await context.wait(); ``` ответ - это объект `MessageContext` или `CallbackQueryContext` ### Ожидание события от пользователя с игнорированием других типов событий ```ts const answer = await context.wait("message"); ``` ответ - это объект `CallbackQueryContext` ### Ожидание события с отфильтрованными ответами Можно указать обработчик в параметрах для проверки ответа пользователя. Если обработчик вернёт `false`, **сообщение** будет проигнорировано ```ts const answer = await context.wait((context) => /[а-яА-Я]/.test(context.text)); // или в сочетании с событием const answer = await context.wait("message", (context) => /[а-яА-Я]/.test(context.text) ); ``` ### Ожидание события с проверкой и трансформацией ответа Можно указать обработчик в параметрах для **трансформации** ответа пользователя. ```ts const answer = await context.wait((context) => /[а-яА-Я]/.test(context.text)); // или в сочетании с событием const answer = await context.wait("message", { validate: (context) => /[а-яА-Я]/.test(context.text), transform: (context) => c.text || "", }); ``` ответ имеет тип `string` --- --- url: /ru/plugins/official/autoload.md --- # Плагин автозагрузки
[![npm](https://img.shields.io/npm/v/@gramio/autoload?logo=npm&style=flat&labelColor=000&color=3b82f6)](https://www.npmjs.org/package/@gramio/autoload) [![JSR](https://jsr.io/badges/@gramio/autoload)](https://jsr.io/@gramio/autoload) [![JSR Score](https://jsr.io/badges/@gramio/autoload/score)](https://jsr.io/@gramio/autoload)
Плагин автозагрузки команд для GramIO с поддержкой [`Bun.build`](#использование-bun-build). ### Установка ::: code-group ```bash [npm] npm install @gramio/autoload ``` ```bash [yarn] yarn add @gramio/autoload ``` ```bash [pnpm] pnpm add @gramio/autoload ``` ```bash [bun] bun install @gramio/autoload ``` ::: ## Использование > [полный пример](https://github.com/gramiojs/autoload/tree/main/example) > [!IMPORTANT] > Пожалуйста, прочитайте о [Ленивой загрузке плагинов](https://gramio.dev/ru/plugins/official/autoload.html) ## Регистрация плагина ```ts twoslash // index.ts import { Bot } from "gramio"; import { autoload } from "@gramio/autoload"; const bot = new Bot(process.env.BOT_TOKEN as string) .extend(await autoload()) .onStart(console.log); bot.start(); export type BotType = typeof bot; ``` ## Создание команды ```ts // commands/command.ts import type { BotType } from ".."; export default (bot: BotType) => bot.command("start", (context) => context.send("привет!")); ``` ## Опции | Ключ | Тип | По умолчанию | Описание | | ----------------- | -------------------------------------------------------------------------------------------------- | -------------------------- | ------------------------------------------------------------------------ | | pattern? | string \| string[] | "\*\*\/\*.{ts,js,cjs,mjs}" | [Шаблоны Glob]() | | path? | string | "./commands" | Путь к папке | | import? | string \| (file: any) => string | "default" | Импорт конкретного `export` из файла | | failGlob? | boolean | true | Бросать ошибку, если не найдены совпадения | | skipImportErrors? | boolean | false | Пропускать импорты, где нужный `export` не определён | | onLoad? | (params: { absolute: string; relative: string }) => unknown | | Хук, вызываемый при загрузке файла | | onFinish? | (paths: { absolute: string; relative: string }[]) => unknown; | | Хук, вызываемый после загрузки всех файлов | | fdir? | [Options](https://github.com/thecodrr/fdir/blob/HEAD/documentation.md#method-chaining-alternative) | | Настройки для [fdir](https://github.com/thecodrr/fdir) | | picomatch? | [PicomatchOptions](https://github.com/micromatch/picomatch?tab=readme-ov-file#picomatch-options) | | Настройки для [picomatch](https://www.npmjs.com/package/picomatch) | ### Использование [Bun build](https://bun.sh/docs/bundler) Вы можете использовать этот плагин с [`Bun.build`](https://bun.sh/docs/bundler) благодаря [esbuild-plugin-autoload](https://github.com/kravetsone/esbuild-plugin-autoload)! ```ts // @filename: build.ts import { autoload } from "esbuild-plugin-autoload"; // также поддерживается импорт по умолчанию await Bun.build({ entrypoints: ["src/index.ts"], target: "bun", outdir: "out", plugins: [autoload("./src/commands")], }).then(console.log); ``` Затем соберите с помощью `bun build.ts` и запустите с помощью `bun out/index.ts`. ### Использование [Bun compile](https://bun.sh/docs/bundler/executables) Вы можете собрать, а затем скомпилировать в [единый исполняемый бинарный файл](https://bun.sh/docs/bundler/executables) ```ts import { autoload } from "esbuild-plugin-autoload"; // также поддерживается импорт по умолчанию await Bun.build({ entrypoints: ["src/index.ts"], target: "bun", outdir: "out", plugins: [autoload("./src/commands")], }).then(console.log); await Bun.$`bun build --compile out/index.js`; ``` > [!WARNING] > Вы не можете использовать это в режиме `bun build --compile` без дополнительного шага ([Issue с функционалом](https://github.com/oven-sh/bun/issues/11895)) [Узнать больше](https://github.com/kravetsone/esbuild-plugin-autoload) --- --- url: /ru/plugins/official/auto-answer-callback-query.md --- # Плагин автоматического ответа на запросы обратного вызова
[![npm](https://img.shields.io/npm/v/@gramio/auto-answer-callback-query?logo=npm&style=flat&labelColor=000&color=3b82f6)](https://www.npmjs.org/package/@gramio/auto-answer-callback-query) [![JSR](https://jsr.io/badges/@gramio/auto-answer-callback-query)](https://jsr.io/@gramio/auto-answer-callback-query) [![JSR Score](https://jsr.io/badges/@gramio/auto-answer-callback-query/score)](https://jsr.io/@gramio/auto-answer-callback-query)
Этот плагин автоматически отвечает на события `callback_query` методом `answerCallbackQuery`, если вы этого еще не сделали. ### Установка ::: code-group ```bash [npm] npm install @gramio/auto-answer-callback-query ``` ```bash [yarn] yarn add @gramio/auto-answer-callback-query ``` ```bash [pnpm] pnpm add @gramio/auto-answer-callback-query ``` ```bash [bun] bun install @gramio/auto-answer-callback-query ``` ::: ```ts import { Bot, InlineKeyboard } from "gramio"; import { autoAnswerCallbackQuery } from "@gramio/auto-answer-callback-query"; const bot = new Bot(process.env.BOT_TOKEN as string) .extend(autoAnswerCallbackQuery()) .command("start", (context) => context.send("Привет!", { reply_markup: new InlineKeyboard() .text("тест", "test") .text("тест2", "test2"), }) ) .callbackQuery("test", () => { // Плагин вызовет метод answerCallbackQuery, так как вы его не вызвали return context.send("Привет"); }) .callbackQuery("test2", (context) => { // вы уже ответили, поэтому плагин не будет пытаться ответить return context.answer("ПРИВЕТ"); }); ``` ### Параметры Вы можете передать параметры для метода [answerCallbackQuery](https://core.telegram.org/bots/api#answercallbackquery) ```ts bot.extend( autoAnswerCallbackQuery({ text: "Автоматический ответ", show_alert: true, }) ); ``` > [!IMPORTANT] > Этот плагин перехватывает методы `context.answerCallbackQuery` (и `context.answer`), чтобы понять, был ли уже отправлен ответ на запрос обратного вызова. Старайтесь не использовать напрямую метод `bot.api.answerCallbackQuery` в контексте - это может помешать корректной работе плагина. --- --- url: /ru/plugins/official/auto-retry.md --- # Плагин автоматического повтора
[![npm](https://img.shields.io/npm/v/@gramio/auto-retry?logo=npm&style=flat&labelColor=000&color=3b82f6)](https://www.npmjs.org/package/@gramio/auto-retry) [![JSR](https://jsr.io/badges/@gramio/auto-retry)](https://jsr.io/@gramio/auto-retry) [![JSR Score](https://jsr.io/badges/@gramio/auto-retry/score)](https://jsr.io/@gramio/auto-retry)
Плагин, который ловит ошибки с полем `retry_after` (**ошибки превышения лимита запросов**), **ждёт** указанное время и **повторяет** API-запрос. ### Установка ::: code-group ```bash [npm] npm install @gramio/auto-retry ``` ```bash [yarn] yarn add @gramio/auto-retry ``` ```bash [pnpm] pnpm add @gramio/auto-retry ``` ```bash [bun] bun install @gramio/auto-retry ``` ::: ### Использование ```ts import { Bot } from "gramio"; import { autoRetry } from "@gramio/auto-retry"; const bot = new Bot(process.env.BOT_TOKEN as string) .extend(autoRetry()) .command("start", async (context) => { for (let index = 0; index < 100; index++) { await context.reply(`сообщение ${index}`); } }) .onStart(console.log); bot.start(); ``` --- --- url: /ru/plugins/official/media-cache.md --- # Плагин кэширования медиа
[![npm](https://img.shields.io/npm/v/@gramio/media-cache?logo=npm&style=flat&labelColor=000&color=3b82f6)](https://www.npmjs.org/package/@gramio/media-cache) [![JSR](https://jsr.io/badges/@gramio/media-cache)](https://jsr.io/@gramio/media-cache) [![JSR Score](https://jsr.io/badges/@gramio/media-cache/score)](https://jsr.io/@gramio/media-cache)
Плагин `Media cache` для [GramIO](https://gramio.dev/). Этот плагин кэширует отправленные `file_id` и предотвращает повторную загрузку файлов. На данный момент **sendMediaGroup** не кэшируется. ## Использование ```ts import { Bot } from "gramio"; import { mediaCache } from "@gramio/media-cache"; const bot = new Bot(process.env.BOT_TOKEN as string) .extend(mediaCache()) .command("start", async (context) => { return context.sendDocument( await MediaUpload.url( "https://raw.githubusercontent.com/gramiojs/types/main/README.md" ) ); }) .onStart(console.log); bot.start(); ``` --- --- url: /ru/plugins/official/media-group.md --- # Плагин медиа-групп
[![npm](https://img.shields.io/npm/v/@gramio/media-group?logo=npm&style=flat&labelColor=000&color=3b82f6)](https://www.npmjs.org/package/@gramio/media-group) [![JSR](https://jsr.io/badges/@gramio/media-group)](https://jsr.io/@gramio/media-group) [![JSR Score](https://jsr.io/badges/@gramio/media-group/score)](https://jsr.io/@gramio/media-group)
Этот плагин собирает `mediaGroup` из сообщений (**1** вложение = **1** сообщение), используя **задержку**, если `mediaGroupId` присутствует в **MessageContext**. Далее плагин передает только **первое** сообщение по цепочке обработчиков с ключом `mediaGroup`, содержащим **массив** всех сообщений этой **mediaGroup** (включая **первое** сообщение). ```ts import { Bot } from "gramio"; import { mediaGroup } from "@gramio/media-group"; const bot = new Bot(process.env.BOT_TOKEN as string) .extend(mediaGroup()) .on("message", async (context) => { if (!context.mediaGroup) return; return context.send( `Подпись из первого сообщения - ${context.caption}. Медиа-группа содержит ${context.mediaGroup.length} вложений` ); }) .onStart(({ info }) => console.log(`✨ Бот ${info.username} запущен!`)); bot.start(); ``` ### Установка ::: code-group ```bash [npm] npm install @gramio/media-group ``` ```bash [yarn] yarn add @gramio/media-group ``` ```bash [pnpm] pnpm add @gramio/media-group ``` ```bash [bun] bun install @gramio/media-group ``` ::: ### Настройка Вы можете изменить время задержки в миллисекундах, просто указав его при вызове: ```typescript const bot = new Bot(process.env.BOT_TOKEN as string) .extend(mediaGroup(1000)) // ждём 1 секунду сообщений с mediaGroupId (обновляется с каждым новым сообщением) .on("message", async (context) => { if (!context.mediaGroup) return; return context.send( `Подпись из первого сообщения - ${context.caption}. Медиа-группа содержит ${context.mediaGroup.length} вложений` ); }) .onStart(({ info }) => console.log(`✨ Бот ${info.username} запущен!`)); bot.start(); ``` По умолчанию это `150 мс`. --- --- url: /ru/plugins/official/session.md --- # Плагин сессий
[![npm](https://img.shields.io/npm/v/@gramio/session?logo=npm&style=flat&labelColor=000&color=3b82f6)](https://www.npmjs.org/package/@gramio/session) [![JSR](https://jsr.io/badges/@gramio/session)](https://jsr.io/@gramio/session) [![JSR Score](https://jsr.io/badges/@gramio/session/score)](https://jsr.io/@gramio/session)
Плагин сессий для GramIO. **Пока не оптимизирован и находится в разработке.** ### Установка ::: code-group ```bash [npm] npm install @gramio/session ``` ```bash [yarn] yarn add @gramio/session ``` ```bash [pnpm] pnpm add @gramio/session ``` ```bash [bun] bun install @gramio/session ``` ::: ## Использование ```ts twoslash import { Bot } from "gramio"; import { session } from "@gramio/session"; const bot = new Bot(process.env.BOT_TOKEN as string) .extend( session({ key: "sessionKey", initial: () => ({ apple: 1 }), }) ) .on("message", (context) => { context.send(`🍏 количество яблок: ${++context.sessionKey.apple}`); // ^? }) .onStart(console.log); bot.start(); ``` Этот плагин можно использовать с любым хранилищем ([Подробнее](/ru/storages/index)) ### Пример с Redis [Больше информации](https://github.com/gramiojs/storages/tree/master/packages/redis) ```ts import { Bot } from "gramio"; import { session } from "@gramio/session"; import { redisStorage } from "@gramio/storage-redis"; const bot = new Bot(process.env.BOT_TOKEN as string) .extend( session({ key: "sessionKey", initial: () => ({ apple: 1 }), storage: redisStorage(), }) ) .on("message", (context) => { context.send(`🍏 количество яблок: ${++context.sessionKey.apple}`); }) .onStart(console.log); bot.start(); ``` ### TypeScript Чтобы **типизировать** данные сессии, укажите тип как `ReturnType` начальной функции. ```ts interface MySessionData { apple: number; some?: "maybe-empty"; } bot.extend( session({ key: "sessionKey", initial: (): MySessionData => ({ apple: 1 }), }) ); ``` --- --- url: /ru/plugins/official/scenes.md --- # @gramio/scenes
[![npm](https://img.shields.io/npm/v/@gramio/scenes?logo=npm&style=flat&labelColor=000&color=3b82f6)](https://www.npmjs.org/package/@gramio/scenes) [![JSR](https://jsr.io/badges/@gramio/scenes)](https://jsr.io/@gramio/scenes) [![JSR Score](https://jsr.io/badges/@gramio/scenes/score)](https://jsr.io/@gramio/scenes)
API может немного измениться, но мы уже активно используем его на боевых проектах. # Использование ```ts twoslash import { Bot } from "gramio"; import { scenes, Scene } from "@gramio/scenes"; export const greetingScene = new Scene("greeting") .params<{ test: boolean }>() .step("message", (context) => { if (context.scene.step.firstTime) return context.send("Привет! Как тебя зовут?"); if (!context.text) return context.send("Пожалуйста, напиши своё имя"); return context.scene.update({ name: context.text, }); }) .step("message", (context) => { if (context.scene.step.firstTime) return context.send("Сколько тебе лет?"); const age = Number(context.text); if (!age || Number.isNaN(age) || age < 0) return context.send("Пожалуйста, укажи возраст корректно"); return context.scene.update({ age, }); }) .step("message", async (context) => { await context.send( `Рад познакомиться! Теперь я знаю, что тебя зовут ${ context.scene.state.name } и тебе ${context.scene.state.age} лет. ${ context.scene.params.test ? "Также у тебя есть параметр test!" : "" }` ); return context.scene.exit(); }); const bot = new Bot(process.env.TOKEN as string) .extend(scenes([greetingScene])) .command("start", async (context) => { return context.scene.enter(greetingScene, { test: true, }); }); ``` > [!WARNING] > Будьте внимательны. Первый шаг сцены должен так же включать в себя событие из которого вы вошли в сцену. (например если по нажатию InlineButton - `callback_query`) ### Общее состояние между шагами ```ts twoslash import { Scene } from "@gramio/scenes"; const testScene = new Scene("test") .step("message", async (context) => { if (context.scene.step.firstTime || context.text !== "1") return context.send("1"); return context.scene.update({ messageId: context.id, some: "привет!" as const, }); }) .step("message", async (context) => { if (context.scene.step.firstTime || context.text !== "2") return context.send("2"); console.log(context.scene.state.messageId); }); ``` ## Использование хранилища ```ts import { redisStorage } from "@gramio/storage-redis"; const bot = new Bot(process.env.TOKEN as string) .extend( scenes([testScene], { storage: redisStorage(), }) ) .command("start", async (context) => { return context.scene.enter(someScene, { test: true, }); }); ``` [Подробнее о хранилищах](/ru/storages/) ### step Это функция — шаг сцены. Он выполняется только когда текущий id шага сцены совпадает с порядком зарегистрированного шага. ```ts const testScene = new Scene("test") // Для одного события .step("message", async (context) => { if (context.scene.step.firstTime) return context.send("Первое сообщение"); return context.scene.exit(); }) // Для конкретных событий .step(["message", "callback_query"], async (context) => { if (context.scene.step.firstTime) return context.send( "Второе сообщение после сообщения пользователя" ); if (context.is("callback_query")) return context.answer("Вы нажали на кнопку"); return context.scene.exit(); }) // Для всех событий .step((context) => { console.log(context); return context.scene.exit(); }); ``` > [!NOTE] > Если пользователь создаёт событие, которое не зарегистрировано в шаге, оно будет проигнорировано этим шагом (но зарегистрированные для него обработчики событий будут вызваны). ### on Этот метод позволяет зарегистрировать обработчик событий для сцены. ```ts const guessRandomNumberScene = new Scene("guess-random-number") .params<{ randomNumber: number }>() .on("message", async (context, next) => { // Это условие нужно, чтобы обработчик не срабатывал при firstTime так как context будет одинаковым с предыдущим шагом if (context.scene.step.firstTime) return next(); return await Promise.all([context.delete(), next()]); }) .step(["message", "callback_query"], async (context) => { if (context.scene.step.firstTime) return context.send("Попробуй угадать число от 1 до 10"); if (!context.is("message")) return context.answer("Пожалуйста, отправьте число сообщением"); const number = Number(context.text); if ( Number.isNaN(number) || number !== context.scene.params.randomNumber ) return; // Обработчик выше удалит отправленное пользователем сообщение return Promise.all([ context.send( format( `Поздравляю! Ты угадал число ${bold( context.scene.params.randomNumber )}!` ) ), context.scene.exit(), ]); }); ``` Обратите внимание: обработчик применяется только ко всем шагам, объявленным после него (или к следующим .on), а не к предыдущим. ```ts new Scene("test") .on(...) // Вызывается для всех шагов .step(...) .on(...) // Вызывается только после достижения второго шага .step(...) ``` ## Контекст сцены ### update `update` - это функция, которая обновляет данные в сцене и при этом сохраняет их типы. ```ts twoslash import { Scene } from "@gramio/scenes"; const testScene = new Scene("test") .step("message", async (context) => { if (context.scene.step.firstTime) return context.send("Первое сообщение"); if (!context.text) return context.delete(); return context.scene.update({ message: context.text, }); }) .step("message", async (context) => { return context.send(context.scene.state.message); // ^? }); ``` ### state `state` - это объект, который содержит все данные, которые были собраны в этой сцене. (смотрите пример выше) ### params `params` - это объект, который содержит все данные, которые были переданы в сцену при её входе. ```ts twoslash import { Bot } from "gramio"; import { scenes, Scene } from "@gramio/scenes"; const testScene = new Scene("test") // тут мы указываем тип параметров сцены .params<{ test: boolean }>() .step("message", async (context) => { return context.send(context.scene.params.test); // ^? }); const bot = new Bot(process.env.TOKEN as string) .extend(scenes([testScene])) .command("start", async (context) => { return context.scene.enter(testScene, { test: true, }); }); ``` ### reenter `reenter` - это функция, которая позволяет перезайти в сцену в первый шаг потеряв [state](#state). ```ts const testScene = new Scene("test") .on("message", async (context, next) => { if (context.text === "/start") return context.scene.reenter(); return next(); }) .step("message", async (context) => { if (context.scene.step.firstTime) return context.send("Привет!"); return context.send("Пока!"); }); ``` ### Контекст шага Тут находится вся информация о текущем шаге сцены. Он хранится в `context.scene.step`. #### firstTime `firstTime` - это флаг, который указывает, является ли текущее выполнение шага первым. ```ts const testScene = new Scene("test").step("message", async (context) => { if (context.scene.step.firstTime) return context.send("Первое сообщение"); if (context.text !== "следующее") return context.send( "Последующие сообщения до того как напишут «следующее»" ); return Promise.all([ context.send("Последнее сообщение"), context.scene.exit(), ]); }); ``` #### next `next` - это функция, которая передаёт управление следующему шагу сцены. ```ts const testScene = new Scene("test").step("message", async (context) => { if (context.scene.step.firstTime) return context.send("Первое сообщение"); return context.scene.next(); }); ``` #### previous `previous` - это функция, которая передаёт управление предыдущему шагу сцены. ```ts const testScene = new Scene("test").step("message", async (context) => { if (context.scene.step.firstTime) return context.send("Первое сообщение"); return context.scene.previous(); }); ``` #### go `go` - это функция, которая передаёт управление конкретному шагу сцены. ```ts const testScene = new Scene("test").step("message", async (context) => { if (context.scene.step.firstTime) return context.send("Первое сообщение"); return context.scene.go(5); }); ``` #### id `id` - это идентификатор шага сцены. ```ts const testScene = new Scene("test").step("message", async (context) => { if (context.scene.step.firstTime) return context.send(`Шаг ${context.scene.step.id}`); return context.scene.exit(); }); ``` #### previousId `previousId` - это идентификатор предыдущего шага сцены. Сохраняется id последнего шага при вызове [#go](#go), [#previous](#previous), [#next](#next). ```ts const testScene = new Scene("test") .step("message", async (context) => { if (context.scene.step.firstTime) return context.send( `из шага ${context.scene.step.previousId} в шаг ${context.scene.step.id}` ); return context.scene.exit(); }) .step("message", async (context) => { return context.scene.step.go(1); }); ``` ## scenesDerives Иногда нужно управлять сценами до выполнения шагов сцены плагином, но после получения сцены из хранилища. По умолчанию функция `scenes()` передаёт необходимые данные последующим обработчикам, если пользователь не находится в сцене. С помощью `scenesDerives()` вы можете получить эти данные раньше и управлять ими. ```ts twoslash import { Scene } from "@gramio/scenes"; const testScene = new Scene("test").state<{ simple: string; example: number[]; }>(); // ---cut--- import { scenes, scenesDerives, type AnyScene } from "@gramio/scenes"; import { Bot } from "gramio"; import { redisStorage } from "@gramio/storage-redis"; const storage = redisStorage(); const scenesList: AnyScene[] = [testScene]; const bot = new Bot(process.env.TOKEN as string) .extend( scenesDerives(scenesList, { withCurrentScene: true, storage, }) ) .on("message", (context, next) => { if (context.text === "/start" && context.scene.current) { if (context.scene.current.is(testScene)) { console.log(context.scene.current.state); return context.scene.current.step.previous(); } else return context.scene.current.reenter(); } return next(); }) .extend( scenes(scenesList, { storage, }) ) .command("start", async (context) => { return context.scene.enter(testScene, { test: true, }); }); ``` > [!IMPORTANT] > Одно и то же **хранилище** и **список сцен** нужно использовать и в `scenes()`, и в опциях `scenesDerives()`. По умолчанию при регистрации плагина `scenes()` используется `inMemoryStorage`. Поэтому если вам нужно использовать `scenesDerives()` для управления сценами, необходимо явно объявить `inMemoryStorage` и явно указать его в опциях как для `scenesDerives()`, так и для `scenes()`. ```ts import { inMemoryStorage } from "@gramio/storage"; const storage = inMemoryStorage(); // Хранит в памяти процесса и будет стёрто при перезапуске const bot = new Bot(process.env.TOKEN as string) .extend(scenes([testScene], { storage })) // ... .extend(scenesDerives([testScene], { storage })); ``` --- --- url: /ru/plugins/index.md --- # Обзор Пожалуйста, ознакомьтесь с нашими официальными плагинами: - [Сцены](/ru/plugins/official/scenes) - [I18n](/ru/plugins/official/i18n) - [Автозагрузка](/ru/plugins/official/autoload) - [Автоответ на callback запросы](/ru/plugins/official/auto-answer-callback-query) - [Сессия](/ru/plugins/official/session) - [Авто-повтор](/ru/plugins/official/auto-retry) - [Кеш медиа](/ru/plugins/official/media-cache) - [Медиа группы](/ru/plugins/official/media-group) - [Prompt](/ru/plugins/official/prompt) --- --- url: /ru/keyboards/inline-keyboard.md --- # Inline Keyboard Inline Keyboard прикрепляется к сообщению. Представляет собой [встроенную клавиатуру](https://core.telegram.org/bots/features#inline-keyboards), которая появляется рядом с сообщением, к которому она принадлежит. Смотрите также [API Reference](https://jsr.io/@gramio/keyboards/doc/~/InlineKeyboard) и [как отвечать на нажатия](/ru/triggers/callback-query). ## Импорт ### С GramIO ```ts twoslash import { InlineKeyboard } from "gramio"; ``` ### Без GramIO ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; ``` ## Кнопки ([Документация](https://core.telegram.org/bots/api/#inlinekeyboardbutton)) Кнопки - это методы, которые собирают inline клавиатуру для вас. ### text Текстовая кнопка с данными, которые будут отправлены в [callback query](https://core.telegram.org/bots/api/#callbackquery) боту при нажатии кнопки, 1-64 байта. ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- new InlineKeyboard().text("какой-то текст", "payload"); // или new InlineKeyboard().text("какой-то текст", { json: "payload", }); // использует JSON.stringify ``` ### url HTTP или tg:// URL, который будет открыт при нажатии на кнопку. Ссылки `tg://user?id=` можно использовать для упоминания пользователя по их идентификатору без использования имени пользователя, если это разрешено их настройками конфиденциальности. ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- new InlineKeyboard().url("GitHub", "https://github.com/gramiojs/gramio"); ``` ### webApp Описание [Веб-приложения](https://core.telegram.org/bots/webapps), которое будет запущено, когда пользователь нажмет на кнопку. Веб-приложение сможет отправить произвольное сообщение от имени пользователя, используя метод [answerWebAppQuery](https://core.telegram.org/bots/api/#answerwebappquery). Доступно только в приватных чатах между пользователем и ботом. ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- new InlineKeyboard().webApp("какой-то текст", "https://..."); ``` ### copy Тип кнопки, которая копирует указанный текст в буфер обмена. ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- new InlineKeyboard().copy( "Скопируй меня", "Добро пожаловать в буфер обмена Gboard, любой скопированный вами текст будет сохранен здесь. Нажмите на клип для вставки его в текстовое поле. Используйте значок редактирования, чтобы закрепить, добавить или удалить клипы. Нажмите и удерживайте клип, чтобы закрепить его. Незакрепленные клипы будут удалены через 1 час." ); ``` ### login Эта кнопка inline-клавиатуры используется для автоматической авторизации пользователя. Служит отличной заменой [Telegram Login Widget](https://core.telegram.org/widgets/login), когда пользователь приходит из Telegram. Все, что нужно пользователю, — это нажать на кнопку и подтвердить, что он хочет войти в систему: Приложения Telegram поддерживают эти кнопки начиная с [версии 5.7](https://telegram.org/blog/privacy-discussions-web-bots#meet-seamless-web-bots). Пример бота: [@discussbot](https://t.me/discussbot) ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- new InlineKeyboard().login("какой-то текст", "https://..."); // или new InlineKeyboard().login("какой-то текст", { url: "https://...", request_write_access: true, }); ``` Подробнее о параметрах в [документации](https://core.telegram.org/bots/api/#loginurl) ### pay Отправляет [Кнопку оплаты](https://core.telegram.org/bots/api/#payments). ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- new InlineKeyboard().pay("5 монет"); ``` > [!WARNING] > Этот тип кнопки **всегда** должен быть первой кнопкой в первом ряду и может использоваться только в сообщениях со счетом. ### switchToChat Нажатие на кнопку предложит пользователю выбрать один из своих чатов, открыть этот чат и вставить имя пользователя бота и указанный inline-запрос в поле ввода. По умолчанию пустой, в этом случае будет вставлено только имя пользователя бота. ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- new InlineKeyboard().switchToChat("Выберите чат"); // или new InlineKeyboard().switchToChat("Выберите чат", "InlineQuery"); ``` ### switchToChosenChat Нажатие на кнопку предложит пользователю выбрать один из своих чатов указанного типа, открыть этот чат и вставить имя пользователя бота и указанный inline-запрос в поле ввода. ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- new InlineKeyboard().switchToChosenChat("Выберите чат"); // или new InlineKeyboard().switchToChosenChat("Выберите чат", "InlineQuery"); // или new InlineKeyboard().switchToChosenChat("Выберите чат", { query: "InlineQuery", allow_channel_chats: true, allow_group_chats: true, allow_bot_chats: true, allow_user_chats: true, }); ``` Подробнее о параметрах в [документации](https://core.telegram.org/bots/api/#switchinlinequerychosenchat) ### switchToCurrentChat Нажатие на кнопку вставит имя пользователя бота и указанный inline-запрос в поле ввода текущего чата. Может быть пустым, в этом случае будет вставлено только имя пользователя бота. Это предлагает пользователю быстрый способ открыть вашего бота в режиме инлайн в том же чате - отлично подходит для выбора чего-либо из нескольких вариантов. ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- new InlineKeyboard().switchToChosenChat("Открыть инлайн режим"); // или new InlineKeyboard().switchToChosenChat("Открыть инлайн режим", "InlineQuery"); ``` ### game Описание игры, которая будет запущена, когда пользователь нажмет на кнопку. ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- new InlineKeyboard().game("текст", {}); // ??? нет параметров... ``` > [!WARNING] > Этот тип кнопки **всегда** должен быть первой кнопкой в первом ряду. ## Помощники Методы, которые помогают вам создать клавиатуру. ### row Добавляет `разрыв строки`. Вызовите этот метод, чтобы следующие добавленные кнопки были на новой строке. ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- new InlineKeyboard() .text("первая строка", "payload") .row() .text("вторая строка", "payload"); ``` ### columns Позволяет ограничить количество столбцов в клавиатуре. ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- new InlineKeyboard() .columns(1) .text("первая строка", "payload") .text("вторая строка", "payload") .text("третья строка", "payload"); ``` ### wrap Кастомный обработчик, который управляет переносом строк. ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- new InlineKeyboard() .wrap(({ button }) => button.callback_data === "2") .text("первая строка", "1") .text("первая строка", "1") .text("вторая строка", "2"); ``` обработчик имеет вид ```ts (options: { button: T; index: number; row: T[]; rowIndex: number }) => boolean; ``` ### pattern Массив с количеством столбцов в каждой строке. Позволяет установить «шаблон». ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- new InlineKeyboard() .pattern([1, 3, 2]) .text("1", "payload") .text("2", "payload") .text("2", "payload") .text("2", "payload") .text("3", "payload") .text("3", "payload"); ``` ### filter Обработчик, который помогает фильтровать кнопки клавиатуры. ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- new InlineKeyboard() .filter(({ button }) => button.callback_data !== "hidden") .text("кнопка", "pass") .text("кнопка", "hidden") .text("кнопка", "pass"); ``` ### add Позволяет добавить несколько кнопок в _сыром_ формате. ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- const labels = ["какие-то", "кнопки"]; new InlineKeyboard() .add({ text: "сырая кнопка", callback_data: "payload" }) .add( InlineKeyboard.text("сырая кнопка через InlineKeyboard.text", "payload") ) .add(...labels.map((x) => InlineKeyboard.text(x, `${x}payload`))); ``` ### addIf Позволяет динамически подставлять кнопки в зависимости от чего-либо. ```ts twoslash // @noErrors import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- const labels = ["какие-то", "кнопки"]; const isAdmin = true; new InlineKeyboard() .addIf(1 === 2, { text: "сырая кнопка", callback_data: "payload" }) .addIf( isAdmin, InlineKeyboard.text("сырая кнопка через InlineKeyboard.text", "payload") ) .addIf( ({ index, rowIndex }) => rowIndex === index, ...labels.map((x) => InlineKeyboard.text(x, `${x}payload`)) ); ``` обработчик имеет вид ```ts (options: { rowIndex: number; index: number }) => boolean; ``` ### matrix Позволяет создать матрицу кнопок. ```ts twoslash // @noErrors import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- import { randomInt } from "node:crypto"; const bomb = [randomInt(0, 9), randomInt(0, 9)] as const; new InlineKeyboard().matrix(10, 10, ({ rowIndex, index }) => InlineKeyboard.text( rowIndex === bomb[0] && index === bomb[1] ? "💣" : "ㅤ", "payload" ) ); ``` Результатом является клавиатура с бомбой на случайной кнопке. обработчик имеет вид ```ts (options: { index: number; rowIndex: number }) => T; ``` ### combine Позволяет объединять клавиатуры. Объединяются только клавиатуры. Вам нужно вызвать метод `.row()` для переноса строки после объединения. ```ts twoslash import { InlineKeyboard } from "@gramio/keyboards"; // ---cut--- new InlineKeyboard() .combine(new InlineKeyboard().text("первая строка", "payload")) .row() .combine( new InlineKeyboard() .text("вторая строка", "payload") .row() .text("третья строка", "payload") ); ``` --- --- url: /ru/keyboards/keyboard.md --- # Keyboard Эта клавиатура отображается под полем ввода и также известна как Reply/Custom Keyboard. Представляет собой [кастомную клавиатуру](https://core.telegram.org/bots/features#keyboards) с вариантами ответа (смотрите [Введение в ботов](https://core.telegram.org/bots/features#keyboards) для деталей и примеров). См. также [API Reference](https://jsr.io/@gramio/keyboards/doc/~/Keyboard) ## Импорт ### С GramIO ```ts twoslash import { Keyboard } from "gramio"; ``` ### Без GramIO ```ts twoslash import { Keyboard } from "@gramio/keyboards"; ``` ## Кнопки ([Документация](https://core.telegram.org/bots/api/#keyboardbutton)) Кнопки - это методы, которые собирают клавиатуру для вас. ### text Текстовая кнопка. Будет отправлена как сообщение при нажатии на кнопку. ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard().text("текст кнопки"); ``` ### requestUsers Кнопка запроса пользователей. Нажатие на кнопку откроет список подходящих пользователей. Идентификаторы выбранных пользователей будут отправлены боту в служебном сообщении `users_shared`. Доступно только в приватных чатах. `Второй параметр` - это 32-битный идентификатор запроса, который будет получен обратно в объекте [UsersShared](https://core.telegram.org/bots/api/#usersshared). Должен быть уникальным в пределах сообщения. ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard().requestUsers("текст кнопки", 228, { user_is_premium: true, }); ``` Подробнее о параметрах в [документации](https://core.telegram.org/bots/api/#keyboardbuttonrequestusers) ### requestChats Кнопка запроса чатов. Нажатие на кнопку откроет список подходящих чатов. Нажатие на чат отправит его идентификатор боту в служебном сообщении `chat_shared`. Доступно только в приватных чатах. `Второй параметр` - это 32-битный идентификатор запроса, который будет получен обратно в объекте [ChatShared](https://core.telegram.org/bots/api/#chatshared). Должен быть уникальным в пределах сообщения. ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard().requestChat("gramio", 255, { chat_is_forum: true, }); ``` > [!WARNING] > По умолчанию параметр `chat_is_channel` установлен в false! Подробнее о параметрах в [документации](https://core.telegram.org/bots/api/#keyboardbuttonrequestchat) ### requestPoll Кнопка запроса опроса. Нажатие на кнопку откроет список подходящих пользователей. Идентификаторы выбранных пользователей будут отправлены боту в служебном сообщении `users_shared`. Доступно только в приватных чатах. ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard().requestPoll("текст кнопки", "quiz"); ``` Подробнее о параметрах в [документации](https://core.telegram.org/bots/api/#keyboardbuttonpolltype) ### requestLocation Кнопка запроса местоположения пользователя. Текущее местоположение пользователя будет отправлено при нажатии на кнопку. Доступно только в приватных чатах. ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard().requestLocation("текст кнопки"); ``` ### requestContact Кнопка запроса контакта. Номер телефона пользователя будет отправлен как контакт при нажатии на кнопку. Доступно только в приватных чатах. ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard().requestContact("текст кнопки"); ``` ### webApp Кнопка webApp. Описанное [Веб-приложение](https://core.telegram.org/bots/webapps) будет запущено при нажатии на кнопку. Веб-приложение сможет отправить служебное сообщение `web_app_data`. Доступно только в приватных чатах. ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard().webApp("текст кнопки", "https://..."); ``` ## Параметры ([Документация](https://core.telegram.org/bots/api/#replykeyboardmarkup)) Эти параметры отвечают за настройки кнопок. ### resized Запрашивает у клиентов изменение размера клавиатуры по вертикали для оптимального размещения (например, уменьшить клавиатуру, если есть всего две строки кнопок). Если `false`, то кастомная клавиатура всегда имеет такую же высоту, как и стандартная клавиатура приложения. По умолчанию `true`. ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard().text("какой-то текст").resized(); // для включения new Keyboard().text("какой-то текст").resized(false); // для отключения ``` > [!WARNING] > Размер кнопок по умолчанию изменяется! Чтобы отменить это, используйте `.resized(false)` ### oneTime Запрашивает у клиентов скрытие клавиатуры, как только она была использована. Клавиатура все еще будет доступна, но клиенты автоматически отобразят обычную буквенную клавиатуру в чате - пользователь может нажать специальную кнопку в поле ввода, чтобы снова увидеть кастомную клавиатуру. По умолчанию `false`. ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard().text("какой-то текст").oneTime(); // для включения new Keyboard().text("какой-то текст").oneTime(false); // для отключения ``` ### persistent Запрашивает у клиентов всегда показывать клавиатуру, когда обычная клавиатура скрыта. По умолчанию `false`, в этом случае кастомная клавиатура может быть скрыта и открыта с помощью значка клавиатуры. По умолчанию `false`. ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard().text("какой-то текст").persistent(); // для включения new Keyboard().text("какой-то текст").persistent(false); // для отключения ``` ### selective Используйте этот параметр, если вы хотите показать клавиатуру только определенным пользователям. Цели: 1. пользователи, которые упоминаются в _тексте_ объекта [Message](https://core.telegram.org/bots/api/#message). 2. если сообщение бота является ответом на сообщение в том же чате и теме форума, отправитель исходного сообщения. _Пример:_ Пользователь запрашивает изменение языка бота, бот отвечает на запрос клавиатурой для выбора нового языка. Другие пользователи в группе не видят клавиатуру. По умолчанию `false`. ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard().text("какой-то текст").selective(); // для включения new Keyboard().text("какой-то текст").selective(false); // для отключения ``` ### placeholder Заполнитель, отображаемый в поле ввода, когда клавиатура активна. 1-64 символа. По умолчанию `не отображается`. ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard().text("какой-то текст").placeholder("какой-то текст"); // для включения new Keyboard().text("какой-то текст").placeholder(); // для отключения ``` ## Помощники Методы, которые помогают вам создать клавиатуру. ### row Добавляет `разрыв строки`. Вызовите этот метод, чтобы следующие добавленные кнопки были на новой строке. ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard().text("первая строка").row().text("вторая строка"); ``` ### columns Позволяет ограничить количество столбцов в клавиатуре. ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard() .columns(1) .text("первая строка") .text("вторая строка") .text("третья строка"); ``` ### wrap Кастомный обработчик, который управляет переносом строк. ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard() .wrap(({ button }) => button.text === "вторая строка") .text("первая строка") .text("первая строка") .text("вторая строка"); ``` обработчик имеет вид ```ts (options: { button: T; index: number; row: T[]; rowIndex: number }) => boolean; ``` ### pattern Массив с количеством столбцов в каждой строке. Позволяет установить «шаблон». ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard() .pattern([1, 3, 2]) .text("1") .text("2") .text("2") .text("2") .text("3") .text("3"); ``` ### filter Обработчик, который помогает фильтровать кнопки клавиатуры. ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard() .filter(({ button }) => button.text !== "скрытая") .text("проходит") .text("скрытая") .text("проходит"); ``` ### add Позволяет добавить несколько кнопок в _сыром_ формате. ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- const labels = ["какие-то", "кнопки"]; new Keyboard() .add({ text: "сырая кнопка" }) .add(Keyboard.text("сырая кнопка через Keyboard.text")) .add(...labels.map((x) => Keyboard.text(x))); ``` ### addIf Позволяет динамически подставлять кнопки в зависимости от чего-либо. ```ts twoslash // @noErrors import { Keyboard } from "@gramio/keyboards"; // ---cut--- const labels = ["какие-то", "кнопки"]; const isAdmin = true; new Keyboard() .addIf(1 === 2, { text: "сырая кнопка" }) .addIf(isAdmin, Keyboard.text("сырая кнопка через Keyboard.text")) .addIf( ({ index, rowIndex }) => rowIndex === index, ...labels.map((x) => Keyboard.text(x)) ); ``` обработчик имеет вид ```ts (options: { rowIndex: number; index: number }) => boolean; ``` ### matrix Позволяет создать матрицу кнопок. ```ts twoslash // @noErrors import { Keyboard } from "@gramio/keyboards"; // TODO: remove no errors // ---cut--- import { randomInt } from "node:crypto"; const bomb = [randomInt(0, 9), randomInt(0, 9)] as const; new Keyboard().matrix(10, 10, ({ rowIndex, index }) => Keyboard.text(rowIndex === bomb[0] && index === bomb[1] ? "💣" : "ㅤ") ); ``` Результатом является клавиатура с бомбой на случайной кнопке. обработчик имеет вид ```ts (options: { index: number; rowIndex: number }) => T; ``` ### combine Позволяет объединять клавиатуры. Объединяются только клавиатуры. Вам нужно вызвать метод `.row()` для переноса строки после объединения. ```ts twoslash import { Keyboard } from "@gramio/keyboards"; // ---cut--- new Keyboard() .combine(new Keyboard().text("первая")) .row() .combine(new Keyboard().text("вторая").row().text("третья")); ``` --- --- url: /ru/keyboards/overview.md --- # Обзор [`@gramio/keyboards`](https://github.com/gramiojs/keyboards) - это встроенный пакет GramIO. Вы также можете использовать его вне этого фреймворка, так как он не зависит от него. Смотрите также [API Reference](https://jsr.io/@gramio/keyboards/doc). ### Установка (не требуется для пользователей GramIO) ::: code-group ```bash [npm] npm install @gramio/keyboards ``` ```bash [yarn] yarn add @gramio/keyboards ``` ```bash [pnpm] pnpm add @gramio/keyboards ``` ```bash [bun] bun install @gramio/keyboards ``` ::: ## Использование ::: code-group ```ts twoslash [с GramIO] import { Keyboard } from "gramio"; const keyboard = new Keyboard() .text("первая строка") .row() .text("вторая строка"); ``` ```ts twoslash [без GramIO] import { Keyboard } from "@gramio/keyboards"; const keyboard = new Keyboard() .text("первая строка") .row() .text("вторая строка") .build(); ``` ::: ### Отправка через [GramIO](https://gramio.dev/) ```ts import { Bot, Keyboard } from "gramio"; // импорт из пакета GramIO!! const bot = new Bot(process.env.BOT_TOKEN as string); const data = ["Apple", "Realme", "Tesla", "Xiaomi"]; bot.on("message", (ctx) => { return ctx.send("тест", { reply_markup: new Keyboard() .columns(1) .text("простая клавиатура") .add(...data.map((x) => Keyboard.text(x))) .filter(({ button }) => button.text !== "Tesla"), }); }); bot.start(); ``` ### Отправка через [Grammy](https://grammy.dev/) ```ts import { Keyboard } from "@gramio/keyboards"; import { Bot } from "grammy"; const bot = new Bot(process.env.BOT_TOKEN as string); const data = ["Apple", "Realme", "Tesla", "Xiaomi"]; bot.on("message", (ctx) => { return ctx.reply("тест", { reply_markup: new Keyboard() .columns(1) .text("простая клавиатура") .add(...data.map((x) => Keyboard.text(x))) .filter(({ button }) => button.text !== "Tesla") .build(), }); }); bot.start(); ``` ### Отправка через [Telegraf](https://github.com/telegraf/telegraf) > [!WARNING] > `Telegraf` не поддерживает последнюю версию Bot API ```ts import { Keyboard } from "@gramio/keyboards"; import { Telegraf } from "telegraf"; const bot = new Telegraf(process.env.BOT_TOKEN as string); const data = ["Apple", "Realme", "Tesla", "Xiaomi"]; bot.on("message", (ctx) => { return ctx.reply("тест", { reply_markup: new Keyboard() .columns(1) .text("простая клавиатура") .add(...data.map((x) => Keyboard.text(x))) .filter(({ button }) => button.text !== "Tesla") .build(), }); }); bot.launch(); ``` ### Отправка через [node-telegram-bot-api](https://www.npmjs.com/package/node-telegram-bot-api) > [!WARNING] > `node-telegram-bot-api` не поддерживает последнюю версию Bot API, и типы написаны плохо, поэтому типы могут не совпадать ```ts import { Keyboard } from "@gramio/keyboards"; import TelegramBot from "node-telegram-bot-api"; const bot = new TelegramBot(process.env.TOKEN as string, { polling: true }); const data = ["Apple", "Realme", "Tesla", "Xiaomi"]; bot.on("message", (msg) => { return bot.sendMessage(msg.chat.id, "тест", { reply_markup: new Keyboard() .columns(1) .text("простая клавиатура") .add(...data.map((x) => Keyboard.text(x))) .filter(({ button }) => button.text !== "Tesla") .build(), }); }); ``` ### Отправка через [puregram](https://puregram.cool/) > [!WARNING] > `puregram` не поддерживает последнюю версию Bot API ```ts import { Telegram } from "puregram"; import { Keyboard } from "@gramio/keyboards"; const bot = new Telegram({ token: process.env.TOKEN as string, }); const data = ["Apple", "Realme", "Tesla", "Xiaomi"]; bot.on("message", (ctx) => { return ctx.send("тест", { reply_markup: new Keyboard() .columns(1) .text("простая клавиатура") .add(...data.map((x) => Keyboard.text(x))) .filter(({ button }) => button.text !== "Tesla") .build(), }); }); bot.updates.startPolling(); ``` #### Результат ```json { "keyboard": [ [ { "text": "простая клавиатура" } ], [ { "text": "Apple" } ], [ { "text": "Realme" } ], [ { "text": "Xiaomi" } ] ], "one_time_keyboard": false, "is_persistent": false, "selective": false, "resize_keyboard": true } ``` ![image](https://github.com/gramiojs/keyboards/assets/57632712/e65e2b0a-40f0-43ae-9887-04360e6dbeab) --- --- url: /ru/tma/init-data.md --- # Валидация WebAppInitData `@gramio/init-data` — это библиотека на TypeScript для безопасной проверки, разбора и генерации строк инициализации (init data) Telegram Web App. Она предоставляет набор утилит для работы с init data Telegram Mini App, обеспечивая целостность и подлинность пользовательских данных, передаваемых из Telegram-клиента на ваш сервер. Библиотека не зависит от фреймворка. Основные возможности: - Проверка подлинности init data Telegram Web App с помощью токена бота - Разбор и получение структурированных данных пользователя и чата из строки init data - Генерация валидных строк init data для тестирования и документации - Строгие типы TypeScript для всех структур и методов [Документация и справочник](https://jsr.io/@gramio/init-data@latest/doc) ## Установка ::: code-group ```bash [npm] npm install @gramio/init-data ``` ```bash [yarn] yarn add @gramio/init-data ``` ```bash [pnpm] pnpm add @gramio/init-data ``` ```bash [bun] bun install @gramio/init-data ``` ::: # getBotTokenSecretKey Получает секретный ключ из токена вашего Telegram-бота. Этот ключ необходим для проверки хэша строки init data, отправленной из Telegram Web App. Всегда используйте этот метод для генерации ключа перед валидацией или подписью init data, если хотите максимальную производительность. Если не передавать ключ в методы проверки, он будет вычисляться **при каждом вызове**. ```ts import { getBotTokenSecretKey } from "@gramio/init-data"; const botToken = process.env.BOT_TOKEN as string; const secretKey = getBotTokenSecretKey(botToken); // используйте secretKey далее ``` # validateAndParseInitData Проверяет подлинность строки init data Telegram Web App с помощью секретного ключа или токена бота. Если данные валидны, возвращает объект [WebAppInitData](https://core.telegram.org/bots/webapps#webappinitdata). Если данные некорректны — возвращает `false`. ```ts import { validateAndParseInitData, getBotTokenSecretKey, } from "@gramio/init-data"; const botToken = process.env.BOT_TOKEN as string; const secretKey = getBotTokenSecretKey(botToken); const initData = req.headers["x-init-data"] as string; const result = validateAndParseInitData(initData, secretKey); if (!result) { // Данные невалидны или подделаны } // Доступ к данным пользователя и чата const userId = result.user?.id; const chatId = result.chat?.id; ``` # validateInitData Проверяет подлинность строки init data Telegram Web App с помощью секретного ключа или токена бота. Возвращает `true`, если данные валидны, иначе — `false`. Этот метод только проверяет данные и **не разбирает** их. Используйте, если нужно только проверить подлинность без извлечения информации о пользователе или чате. ```ts import { validateInitData, getBotTokenSecretKey } from "@gramio/init-data"; const botToken = process.env.BOT_TOKEN as string; const secretKey = getBotTokenSecretKey(botToken); const initData = req.headers["x-init-data"] as string; const isValid = validateInitData(initData, secretKey); if (!isValid) { // Данные невалидны или подделаны } // Если true — строке init data можно доверять ``` # parseInitData Разбирает строку init data Telegram Web App и возвращает объект [WebAppInitData](https://core.telegram.org/bots/webapps#webappinitdata). Этот метод **не выполняет проверку** подлинности или целостности — используйте его только после проверки через `validateInitData` или `validateAndParseInitData`. ```ts import { parseInitData } from "@gramio/init-data"; const initData = req.headers["x-init-data"] as string; const parsed = parseInitData(initData); // Доступ к данным пользователя и чата const userId = parsed.user?.id; const chatId = parsed.chat?.id; ``` # signInitData Генерирует валидную строку init data из объекта данных и секретного ключа или токена бота. Полезно для тестирования, документации или генерации примеров для клиентов API. ```ts import { signInitData, getBotTokenSecretKey, type WebAppUser, } from "@gramio/init-data"; const botToken = process.env.BOT_TOKEN as string; const secretKey = getBotTokenSecretKey(botToken); const data = { user: { id: 1, first_name: "durov", username: "durov", }, } satisfies WebAppUser; const signedInitData = signInitData(data, secretKey); // Используйте signedInitData как валидную строку init data для тестов ``` # Пример: интеграция с Elysia Вы можете использовать `@gramio/init-data` с любым backend-фреймворком. Ниже — бонус: пример интеграции с Elysia для типобезопасной аутентификации. ```ts twoslash; import { validateAndParseInitData, signInitData, getBotTokenSecretKey, } from "@gramio/init-data"; import { Elysia, t } from "elysia"; const secretKey = getBotTokenSecretKey(process.env.BOT_TOKEN as string); export const authElysia = new Elysia({ name: "auth", }) .guard({ headers: t.Object({ "x-init-data": t.String({ examples: [ signInitData( { user: { id: 1, first_name: "durov", username: "durov", }, }, secretKey ), ], }), }), response: { 401: t.Literal("UNAUTHORIZED"), }, }) .resolve(({ headers, error }) => { const result = validateAndParseInitData( headers["x-init-data"], secretKey ); if (!result || !result.user) return error("Unauthorized", "UNAUTHORIZED"); return { tgId: result.user.id, user: result.user, }; }) .as("plugin"); const app = new Elysia() .get("hello", "world") .use(authElysia) .get("/user", ({ user }) => { user; // ^? }); ``` --- --- url: /ru/guides/for-beginners/4.md --- # Soon --- --- url: /ru/files/usage-without-gramio.md --- # Использование без GramIO ## Напишите свою собственную типо-безопасную обертку для Telegram Bot API с поддержкой загрузки файлов! ```ts twoslash // @noErrors import type { APIMethods, APIMethodParams, TelegramAPIResponse, } from "@gramio/types"; import { isMediaUpload, convertJsonToFormData } from "@gramio/files"; const TBA_BASE_URL = "https://api.telegram.org/bot"; const TOKEN = ""; const api = new Proxy({} as APIMethods, { get: (_target: APIMethods, method: T) => async (params: APIMethodParams) => { const reqOptions: Parameters[1] = { method: "POST", }; if (params && isMediaUpload(method, params)) { const formData = await convertJsonToFormData(method, params); reqOptions.body = formData; } else { reqOptions.headers = { "Content-Type": "application/json", }; reqOptions.body = JSON.stringify(params); } const response = await fetch( `${TBA_BASE_URL}${TOKEN}/${method}`, reqOptions ); const data = (await response.json()) as TelegramAPIResponse; if (!data.ok) throw new Error(`Произошла ошибка в ${method}`); return data.result; }, }); ``` --- --- url: /ru/files/download.md --- # Скачивание Стандартный путь скачивания файлов выглядит так: - Вызвать `getFile` с параметром `file_id` - Извлечь `file_path` из ответа - Составить ссылку следующего вида `https://api.telegram.org/file/bot/` - Отправить запрос и скачать запрашиваемый медиафайл - ? Возможно, сохранить файл в файловой системе ? но в нашем случае это выглядит более удобно и проще. ## Через методы контекста ```ts bot.on("message", async (context) => { if (!context.document) return; // скачать в ./file-name await context.download(context.document.fileName || "file-name"); // получить ArrayBuffer const buffer = await context.download(); return context.send("Спасибо!"); }); ``` > [!IMPORTANT] > > **Одно сообщение** содержит только **одно вложение**. Поэтому для скачивания вложения можно просто использовать метод `context.download` > Но вы можете работать с медиагруппами, используя плагин [media-group](/ru/plugins/official/media-group) и итерацию по массиву `context.mediaGroup`. ## Через метод экземпляра бота ```ts const chat = await bot.api.getChat({ chat_id: "@not_found" }); if (!chat.photo?.big_file_id) return; // скачать в ./not_found_chat_photo.png await bot.downloadFile(chat.photo.big_file_id, "not_found_chat_photo.png"); // получить ArrayBuffer const buffer = await bot.downloadFile(chat.photo.big_file_id); ``` --- --- url: /ru/guides/for-beginners/2.md --- # Первое знакомство #### Что сделаем? - Создадим аккаунт боту - Установим и настроим проект - Запустим бота, который отвечает на команду `/start` - Научимся работать с «переменными окружения» и `--watch` режимом ### Создание аккаунта телеграм боту Для начала, создайте своего бота и получите `токен`. Вы можете сделать это воспользовавшись ботом [@BotFather](https://t.me/BotFather). Отправьте ему команду `/newbot` и следуйте инструкциям пока вы не получите токен вида `110201543:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw`. ### Установка Для начала нам необходим создать проект и установить туда `GramIO` с помощью `npm`. Создаём папку и прописываем в ней `npm init` для создания package.json для нашего проекта. Так же укажите `"type": "module"` (TODO: рассказать зачем) чтобы конфиг выглядел примерно так: ```json { "name": "bot-test", "type": "module", "version": "1.0.0", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "description": "" } ``` Следующим шагом мы установим необходимую нам зависимость - GramIO. ```bash npm install gramio # или npm i gramio ``` ### Первый бот Давайте напишем логику нашему первому боту. Создайте файл `index.js` и напишите в нём: ```js import { Bot } from "gramio"; const bot = new Bot("12345678:AaBbCcDdEeFfGgHh").command("start", (context) => context.send("Вы написали /start") ); bot.start(); ``` где `12345678:AaBbCcDdEeFfGgHh` это токен нашего бота. Этот код можно разобрать на части - - `import { Bot } from "gramio";` - это [импорт](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Statements/import) главного класса нашей библиотеки. - `const bot = new Bot("12345678:AaBbCcDdEeFfGgHh")` - создаём переменную `bot` и инициализируем класс передав ему токен нашего бота - `.command("start", (context) => ???);` - Регистрируем нашу команду `/start` для которой будет вызываться эта функция` - `context.send("Вы написали /start")` - с помощью этого метода отправляем в тот же чат указанное сообщение - `bot.start();` - а благодаря этому запускаем прослушивание событий с помощью Long-Polling (пока не важно) Запустим этот код с помощью `node index.js` и проверим работу бота. // TODO: картинка ### Что за `.send`? Это метод контекста `Message`, который позволяет отправить сообщение в тот же чат откуда и пришло исходное сообщение. Благодаря этому мы можем не описать полностью в какой чат нам нужно отправлять сообщение, но всё же если нужно мы также можем воспользоваться обычными методами из [API](https://core.telegram.org/bots/api). Вы можете отправить запрос к API и вне обработчиков событий. ```js import { Bot } from "gramio"; const bot = new Bot("12345678:AaBbCcDdEeFfGgHh"); bot.api.sendMessage({ chat_id: "id нужного чата", text: "Вы написали /start", }); ``` А вот в обработчиках вам понадобиться обратиться к `context.bot` переменную которая является нашем инициализированным классом бота ```js import { Bot } from "gramio"; const bot = new Bot("12345678:AaBbCcDdEeFfGgHh").command("start", (context) => context.bot.api.sendMessage({ chat_id: "id нужного чата", text: "Вы написали /start", }) ); bot.start(); ``` ### Конфигурация нашего бота Сразу же стоит запомнить что хранить так токен - зло. Рекомендуется хранить секреты в переменных окружения, поэтому давайте создадим файл `.env` в нашем проекте и перенесём токен туда. ```dotenv BOT_TOKEN=12345678:AaBbCcDdEeFfGgHh ``` Объявление переменных окружения в этом файле до нельзя простое, ведь это всего-лишь пара `ключ=значение`. Хорошо, файл создан, но как обращаться к этим «переменным окружения»? Для этого существует объект `process.env` в котором в нашей программе и будут лежать эти переменные, но стоит помнить что файл `.env` существует для удобства и не все программы читают его по умолчанию. Например в Node.js нам необходимо указать его в аргументе `--env-file`. Давайте модифицируем наш код, чтобы использовать переменные окружения. ```js import { Bot } from "gramio"; const bot = new Bot(process.env.BOT_TOKEN).command("start", (context) => context.send("Вы написали /start") ); bot.start(); ``` И запустим указав наш `.env` файл. ([дока](https://nodejs.org/en/learn/command-line/how-to-read-environment-variables-from-nodejs)) ```bash node --env-file=.env index.js ``` а ещё давайте добавим `--watch` режим, который будет перезапускать наш скрипт при изменениях в файле! ```bash node --watch --env-file=.env index.js ``` Так же давайте для удобства вынесем эту команду в наш `package.json`. для этого добавьте `scripts` объект и в нём `dev` с содержимым нашей команды. В итоге это выглядит примерно так: ```json { "name": "bot-test", "type": "module", "version": "1.0.0", "main": "index.js", "scripts": { "dev": "node --watch --env-file=.env index.js" }, "author": "", "license": "ISC", "description": "", "dependencies": { "gramio": "^0.0.40" } } ``` Теперь мы можем её вызывать так - `npm run dev`. --- --- url: /ru/triggers/hears.md --- # Метод Hears Метод `hears` в GramIO позволяет настроить ответы на определенные сообщения. Вы можете определить, какие сообщения должен прослушивать ваш бот и как он должен обрабатывать эти сообщения при их обнаружении. Метод гибкий и может работать с регулярными выражениями, точными строками или пользовательскими функциями. ## Основное использование ### Использование сопоставителя `Regular Expression` (Регулярное выражение) Вы можете настроить бота на прослушивание сообщений, соответствующих определенному шаблону, используя регулярные выражения. Это полезно, когда вы хотите, чтобы ваш бот отвечал на сообщения, соответствующие определенной структуре. ```ts bot.hears(/hello (.*)/i, async (context) => { if (context.args) { await context.send(`Привет, ${context.args[1]}!`); } }); ``` В этом примере бот прослушивает сообщения, начинающиеся со слова "привет", за которым следует любой текст. Когда такое сообщение получено, бот извлекает текст после "привет" и отправляет персонализированное приветствие. ### Использование сопоставителя `String` (Строка) Если вы хотите, чтобы ваш бот отвечал на конкретное слово или фразу, вы можете использовать строковый триггер. ```ts bot.hears("hello", async (context) => { await context.send("Добро пожаловать! Введите 'помощь', чтобы увидеть доступные команды."); }); ``` В этом случае бот прослушивает точное слово "старт" и отвечает приветственным сообщением. ### Использование сопоставителя `Function` (Функция) Для более сложных сценариев вы можете использовать функцию для определения триггера. Эта функция проверяет контекст сообщения и решает, запускать ли ответ. ```ts bot.hears( (context) => context.user.role === "admin", async (context) => { await context.send("Здравствуйте, Администратор! Чем я могу вам помочь?"); } ); ``` Здесь бот проверяет, является ли пользователь администратором. Если да, он отправляет специальное сообщение для администраторов. --- --- url: /ru/types/index.md --- # Типы Telegram Bot API > Автоматически сгенерированные и опубликованные типы Telegram Bot API [Справочник по типам API](https://jsr.io/@gramio/types/doc) ### Версионирование Типы 7.7.x соответствуют Telegram Bot API 7.7 ## Использование в качестве [пакета NPM](https://www.npmjs.com/package/@gramio/types) ```ts twoslash import type { APIMethods, APIMethodReturn } from "@gramio/types"; type SendMessageReturn = Awaited>; // ^? type SendMessageReturn = TelegramMessage type GetMeReturn = APIMethodReturn<"getMe">; // ^? type GetMeReturn = TelegramUser ``` ### Автоматическое обновление пакета Эта библиотека автоматически обновляется до последней версии Telegram Bot API в случае изменений благодаря CI/CD! Если GitHub Action завершается с ошибкой, это означает, что в Bot API нет изменений. ## Импорты - `index` - экспортирует всё в разделе - `methods` - экспортирует `APIMethods`, который описывает функции API - `objects` - экспортирует объекты с префиксом `Telegram` (например, [Update](https://core.telegram.org/bots/api/#update)) - `params` - экспортирует параметры, которые используются в `methods` ### Создайте свою собственную обертку Telegram Bot API с проверкой типов ```ts twoslash import type { APIMethods, APIMethodParams, TelegramAPIResponse, } from "@gramio/types"; const TBA_BASE_URL = "https://api.telegram.org/bot"; const TOKEN = ""; const api = new Proxy({} as APIMethods, { get: (_target: APIMethods, method: T) => async (params: APIMethodParams) => { const response = await fetch(`${TBA_BASE_URL}${TOKEN}/${method}`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(params), }); const data = (await response.json()) as TelegramAPIResponse; if (!data.ok) throw new Error(`Some error occurred in ${method}`); return data.result; }, }); api.sendMessage({ chat_id: 1, text: "message", }); ``` #### Использование с [`@gramio/keyboards`](https://github.com/gramiojs/keyboards) ```typescript twoslash import type { APIMethods, APIMethodParams, TelegramAPIResponse, } from "@gramio/types"; const TBA_BASE_URL = "https://api.telegram.org/bot"; const TOKEN = ""; const api = new Proxy({} as APIMethods, { get: (_target: APIMethods, method: T) => async (params: APIMethodParams) => { const response = await fetch(`${TBA_BASE_URL}${TOKEN}/${method}`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(params), }); const data = (await response.json()) as TelegramAPIResponse; if (!data.ok) throw new Error(`Some error occurred in ${method}`); return data.result; }, }); // ---cut--- import { Keyboard } from "@gramio/keyboards"; // код из примера выше api.sendMessage({ chat_id: 1, text: "message with keyboard", reply_markup: new Keyboard().text("button text"), }); ``` ### С поддержкой загрузки файлов См. [files/usage-without-gramio](/ru/files/usage-without-gramio) ## Генерация типов вручную Требуется - [`rust`](https://www.rust-lang.org/) 1. Клонируйте [этот репозиторий](https://github.com/gramiojs/types) и откройте его ```bash git clone https://github.com/gramiojs/types.git ``` 2. Клонируйте [репозиторий](https://github.com/ark0f/tg-bot-api) с генератором схемы Telegram Bot API ```bash git clone https://github.com/ark0f/tg-bot-api.git ``` 3. Запустите генератор схемы JSON в папке `cloned` ```bash cd tg-bot-api && cargo run --package gh-pages-generator --bin gh-pages-generator -- dev && cd .. ``` 4. Запустите генерацию кода типов из `корня` проекта ```bash bun generate ``` или, если вы не используете `bun`, используйте `tsx` ```bash npx tsx src/index.ts ``` 5. Готово! Проверьте типы Telegram Bot API в папке `out`! --- --- url: /ru/keyboards/remove-keyboard.md --- # Remove Keyboard При получении сообщения с этим объектом, клиенты Telegram удалят текущую кастомную клавиатуру и отобразят стандартную буквенную клавиатуру. По умолчанию кастомные клавиатуры отображаются до тех пор, пока бот не отправит новую клавиатуру. Исключение составляют одноразовые клавиатуры, которые скрываются сразу после нажатия пользователем на кнопку (см. [ReplyKeyboardMarkup](https://core.telegram.org/bots/api/#replykeyboardmarkup)). Смотрите также [API Reference](https://jsr.io/@gramio/keyboards/doc/~/RemoveKeyboard). ## Импорт ### С GramIO ```ts twoslash import { RemoveKeyboard } from "gramio"; ``` ### Без GramIO ```ts twoslash import { RemoveKeyboard } from "@gramio/keyboards"; ``` ## Параметры ([Документация](https://core.telegram.org/bots/api/#replykeyboardremove)) Эти параметры отвечают за настройки удаления кнопок ### selective Используйте этот параметр, если вы хотите удалить клавиатуру только для определенных пользователей. Цели: 1. пользователи, которые упоминаются в _тексте_ объекта [Message](https://core.telegram.org/bots/api/#message). 2. если сообщение бота является ответом на сообщение в том же чате и теме форума, отправитель исходного сообщения. Пример: Пользователь голосует в опросе, бот возвращает сообщение с подтверждением в ответ на голос и удаляет клавиатуру для этого пользователя, при этом продолжая показывать клавиатуру с вариантами опроса пользователям, которые еще не голосовали. ```ts twoslash import { RemoveKeyboard } from "@gramio/keyboards"; // ---cut--- new RemoveKeyboard().selective(); // для включения new RemoveKeyboard().selective(false); // для отключения ``` --- --- url: /ru/formatting/index.md --- # Форматирование сообщений [`@gramio/format`](https://github.com/gramiojs/format) - это встроенный пакет GramIO. Вы также можете использовать его вне этого фреймворка, так как он не зависит от него. Смотрите также [API Reference](https://jsr.io/@gramio/format/doc). ## Format Шаблонный литерал, который помогает создавать сущности сообщений для форматирования текста. Используйте его, если хотите удалить все отступы в начале каждой строки. (как [common-tags#stripindents](https://www.npmjs.com/package/common-tags#stripindents) или [dedent](https://www.npmjs.com/package/dedent)) > [!IMPORTANT] > Для форматирования с **массивами** используйте его с помощником [`join`](#join) ```ts twoslash import { format, bold, link, italic, Bot } from "gramio"; const bot = new Bot(""); // ---cut--- format`какой-то текст`; // или format`${bold`Хмм...`} ${link( "GramIO", "https://github.com/gramiojs/gramio" )}?`; ``` Давайте отправим что-нибудь... ```ts twoslash import { format, bold, link, italic, spoiler, Bot } from "gramio"; const bot = new Bot(""); // ---cut--- bot.api.sendMessage({ chat_id: 12321, text: format`${bold`Hi!`} Can ${italic("you")} help ${spoiler`me`}? Can you give me ${link("a star", "https://github.com/gramiojs/gramio")}?`, }); ``` ![format](/ru/formatting/format.png) ## FormatSaveIndents Шаблонный литерал, который помогает создавать сущности сообщений для форматирования текста. Используйте его, если хотите сохранить все отступы. **ПРИМЕЧАНИЕ**: для форматирования с **массивами** используйте его с помощником [`join`](#join) ```ts twoslash import { formatSaveIndents, bold, link, italic, spoiler, Bot } from "gramio"; const bot = new Bot(""); // ---cut--- bot.api.sendMessage({ chat_id: 12321, text: formatSaveIndents`${bold`Hi!`} Can ${italic("you")} help ${spoiler`me`}? Can you give me ${link("a star", "https://github.com/gramiojs/gramio")}?`, }); ``` ![format-save-indents](/formatting/format-save-indents.png) ## Сущности ### **Жирный** Форматирует текст как **жирный**. Нельзя комбинировать с `code` и `pre`. ```ts twoslash import { format, bold } from "@gramio/format"; // ---cut--- format`Format text as ${bold`bold`}`; ``` ![bold example](/formatting/bold.png) ### _Курсив_ Форматирует текст как _курсив_. Нельзя комбинировать с `code` и `pre`. ```ts twoslash import { format, italic } from "@gramio/format"; // ---cut--- format`Format text as ${italic`italic`}`; ``` ![italic example](/formatting/italic.png) ### Подчеркнутый Форматирует текст как подчеркнутый. Нельзя комбинировать с `code` и `pre`. ```ts twoslash import { format, underline } from "@gramio/format"; // ---cut--- format`Format text as ${underline`underlined`}`; ``` ![underline example](/formatting/underline.png) ### ~~Зачеркнутый~~ Форматирует текст как ~~зачеркнутый~~. Нельзя комбинировать с `code` и `pre`. ```ts twoslash import { format, strikethrough } from "@gramio/format"; // ---cut--- format`Format text as ${strikethrough`strikethrough`}`; ``` ![strikethrough example](/formatting/strikethrough.png) ### Спойлер Форматирует текст как спойлер. Нельзя комбинировать с `code` и `pre`. ```ts twoslash import { format, spoiler } from "@gramio/format"; // ---cut--- format`Format text as ${spoiler`spoiler`}`; ``` ![spoiler example](/formatting/spoiler.png) > ### Цитата Форматирует текст как цитату. Не может быть вложенной. ```ts twoslash import { format, blockquote } from "@gramio/format"; // ---cut--- format`Format text as ${blockquote`quote`}`; ``` ![blockquote example](/formatting/blockquote.png) > ### Раскрывающаяся цитата Форматирует текст как раскрывающуюся цитату. Не может быть вложенной. ```ts twoslash import { format, expandableBlockquote } from "@gramio/format"; function loremIpsum(options: { count: number }): string { return ""; } // ---cut--- format`Format text as ${expandableBlockquote(loremIpsum({ count: 20 }))}`; ``` ![expandable blockquote example](/formatting/expandable_blockquote.png) ### `Код` Форматирует текст как `код`. Удобно для скопированных элементов. Нельзя комбинировать с любым другим форматированием. ```ts twoslash import { format, code } from "@gramio/format"; // ---cut--- format`Format text as ${code`code`}`; ``` ![code example](/formatting/code.png) ### `Pre` Форматирует текст как `pre`. Нельзя комбинировать с любым другим форматированием. ([Поддерживаемые языки](https://github.com/TelegramMessenger/libprisma#supported-languages)) ```ts twoslash import { format, pre } from "@gramio/format"; // ---cut--- format`Format text as ${pre`pre`}`; // или с указанием языка format`Format text as ${pre(`console.log("pre with language")`, "js")}`; ``` ![pre example](/formatting/pre.png) ### [Ссылка](https://github.com/gramiojs/gramio) Форматирует текст как [ссылку](https://github.com/gramiojs/gramio). Нельзя комбинировать с `code` и `pre`. ```ts twoslash import { format, link } from "@gramio/format"; // ---cut--- format`Format text as ${link("link", "https://github.com/gramiojs/gramio")}`; ``` ![link example](/formatting/link.png) ### [Упоминание](https://github.com/gramiojs/gramio) Форматирует текст как [упоминание](https://github.com/gramiojs/gramio). Нельзя комбинировать с `code` и `pre`. ```ts twoslash import { format, mention } from "@gramio/format"; // ---cut--- format`Format text as ${mention("mention", { id: 12312312, is_bot: false, first_name: "GramIO", })}`; ``` ![mention example](/formatting/mention.png) ### 🄲 🅄 🅂 🅃 🄾 🄼 ㅤ🄴 🄼 🄾 🄹 🄸 Вставляет пользовательский эмодзи по его id. ```ts twoslash import { format, customEmoji } from "@gramio/format"; // ---cut--- format`text with emoji - ${customEmoji("⚔️", "5222106016283378623")}`; ``` > [!WARNING] > Сущности пользовательских эмодзи могут использоваться только ботами, которые приобрели дополнительные имена пользователей на [Fragment](https://fragment.com/). ## Помощники ### Join Помощник для отличной работы с форматируемыми массивами. ([].join разрушает стилизацию) Разделитель по умолчанию - `, ` ```ts twoslash import { format, join, bold } from "@gramio/format"; // ---cut--- format`${join(["test", "other"], (x) => format`${bold(x)}`, "\n")}`; ``` --- --- url: /ru/storages/index.md --- # Хранилища Это специальные хранилища данных. Их основное применение — в плагинах (например, [сессии](/ru/plugins/official/session)). ## Адаптеры GramIO имеет множество готовых адаптеров, но вы также можете написать свой собственный! - [В памяти (In Memory)](#в-памяти) (`@gramio/storage`) - [Redis](#redis) (`@gramio/storage-redis`) ## Как написать свой собственный адаптер хранилища Написать свой адаптер очень просто! Достаточно вернуть объект с необходимыми методами и использовать методы выбранного вами решения для адаптера (например, `ioredis`). ```ts import type { Storage } from "@gramio/storage"; import ThirdPartyStorage, { type ThirdPartyStorageOptions } from "some-library"; export interface MyOwnStorageOptions extends ThirdPartyStorageOptions { /** добавить новое свойство в параметры */ some?: number; } export function myOwnStorage(options: MyOwnStorageOptions = {}): Storage { const storage = new ThirdPartyStorage(options); return { async get(key) { const data = await storage.get(key); return data ? JSON.parse(data) : undefined; }, async has(key) { return storage.has(key); }, async set(key, value) { await storage.set(key, JSON.stringify(value)); }, async delete(key) { return storage.delete(key); }, }; } ``` > [!IMPORTANT] > Если вы хотите опубликовать свой адаптер, мы рекомендуем следовать **соглашению** и назвать его начиная с `gramio-storage` и добавить ключевые слова `gramio` + `gramio-storage` в ваш **package.json** ## Как использовать адаптеры хранилища в своем плагине Работать с адаптерами хранилища в вашем плагине также очень просто! Всё, что нам нужно, уже есть в `@gramio/storage`. ```ts import { Plugin } from "gramio"; import { type Storage, inMemoryStorage } from "@gramio/storage"; export interface MyOwnPluginOptions { storage?: Storage; } export function myOwnPlugin(options: MyOwnPluginOptions = {}) { // используем хранилище в памяти по умолчанию const storage = options.storage ?? inMemoryStorage(); return new Plugin("gramio-example"); } ``` > [!IMPORTANT] > Вы можете создать шаблон этого примера с помощью [create-gramio-plugin](/ru/plugins/how-to-write.html#scaffolding-the-plugin) ## Список ## В памяти
[![npm](https://img.shields.io/npm/v/@gramio/storage?logo=npm&style=flat&labelColor=000&color=3b82f6)](https://www.npmjs.org/package/@gramio/storage) [![npm downloads](https://img.shields.io/npm/dw/@gramio/storage?logo=npm&style=flat&labelColor=000&color=3b82f6)](https://www.npmjs.org/package/@gramio/storage) [![JSR](https://jsr.io/badges/@gramio/storage)](https://jsr.io/@gramio/storage) [![JSR Score](https://jsr.io/badges/@gramio/storage/score)](https://jsr.io/@gramio/storage)
##### Установка ::: code-group ```bash [npm] npm install @gramio/storage ``` ```bash [yarn] yarn add @gramio/storage ``` ```bash [pnpm] pnpm add @gramio/storage ``` ```bash [bun] bun install @gramio/storage ``` ::: ##### Использование 1. С использованием стандартного [Map](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Map) ```ts twoslash import { inMemoryStorage } from "@gramio/storage"; const storage = inMemoryStorage(); ``` 2. Предоставление своего собственного [Map](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Map) ```ts twoslash import { inMemoryStorage, type InMemoryStorageMap } from "@gramio/storage"; const map: InMemoryStorageMap = new Map(); const storage = inMemoryStorage(map); ``` ## Redis
[![npm](https://img.shields.io/npm/v/@gramio/storage-redis?logo=npm&style=flat&labelColor=000&color=3b82f6)](https://www.npmjs.org/package/@gramio/storage-redis) [![npm downloads](https://img.shields.io/npm/dw/@gramio/storage-redis?logo=npm&style=flat&labelColor=000&color=3b82f6)](https://www.npmjs.org/package/@gramio/storage-redis) [![JSR](https://jsr.io/badges/@gramio/storage-redis)](https://jsr.io/@gramio/storage-redis) [![JSR Score](https://jsr.io/badges/@gramio/storage-redis/score)](https://jsr.io/@gramio/storage-redis)
##### Установка ::: code-group ```bash [npm] npm install @gramio/storage-redis ``` ```bash [yarn] yarn add @gramio/storage-redis ``` ```bash [pnpm] pnpm add @gramio/storage-redis ``` ```bash [bun] bun install @gramio/storage-redis ``` ::: ##### Использование 1. Предоставление параметров ioredis для функции `redisStorage` ```ts twoslash import { redisStorage } from "@gramio/storage-redis"; const storage = redisStorage({ host: process.env.REDIS_HOST, }); ``` 2. Предоставление экземпляра ioredis для функции `redisStorage` ```ts twoslash import { redisStorage } from "@gramio/storage-redis"; import { Redis } from "ioredis"; const redis = new Redis({ host: process.env.REDIS_HOST, }); const storage = redisStorage(redis); ``` ##### Советы - Вы можете установить переменную окружения `DEBUG` в `ioredis:*` для вывода отладочной информации: ```bash DEBUG=ioredis:* npm run start ``` и это будет выглядеть так: ```bash ioredis:redis write command[::1:6379]: 0 -> get([ '@gramio/scenes:617580375' ]) +187ms ioredis:redis write command[::1:6379]: 0 -> set([ '@gramio/scenes:617580375', '{"name":"scene-name","state":{},"stepId":0,"previousStepId":0,"firstTime":false}' ]) +1ms ``` - Для проверки того, какие данные хранятся в Redis, мы рекомендуем использовать графические клиенты, такие как [AnotherRedisDesktopManager](https://github.com/qishibo/AnotherRedisDesktopManager). AnotherRedisDesktopManager --- --- url: /ru/hooks/on-error.md --- # onError (Обработка ошибок) Бывает, что в middleware возникают ошибки, и нам нужно их обрабатывать. Для этого был создан хук `onError`. ```ts twoslash import { Bot } from "gramio"; const bot = new Bot(""); // ---cut--- bot.on("message", () => { bot.api.sendMessage({ chat_id: "@not_found", text: "Чат не существует....", }); }); bot.onError(({ context, kind, error }) => { if (context.is("message")) return context.send(`${kind}: ${error.message}`); }); ``` ### Добавление хука только для определенных контекстов ```ts bot.onError("message", ({ context, kind, error }) => { return context.send(`${kind}: ${error.message}`); }); // или массив bot.onError(["message", "message_reaction"], ({ context, kind, error }) => { return context.send(`${kind}: ${error.message}`); }); ``` ## Типы ошибок ### Пользовательские Вы можете отловить ошибку определенного класса, унаследованного от Error. ```ts twoslash import { Bot, format, bold } from "gramio"; // ---cut--- export class NoRights extends Error { needRole: "admin" | "moderator"; constructor(role: "admin" | "moderator") { super(); this.needRole = role; } } const bot = new Bot(process.env.BOT_TOKEN as string) .error("NO_RIGHTS", NoRights) .onError("message", ({ context, kind, error }) => { if (kind === "NO_RIGHTS") return context.send( format`У вас недостаточно прав! Вам нужно иметь роль «${bold( error.needRole // ^^^^^^^^ )}».` ); }); bot.on("message", (context) => { if (context.text === "ban") throw new NoRights("admin"); }); ``` > [!IMPORTANT] > Мы рекомендуем следовать **соглашению** и называть типы ошибок в формате **SCREAMING_SNAKE_CASE** ### Telegram Эта ошибка является результатом неудачного запроса к Telegram Bot API. ```ts twoslash import { Bot } from "gramio"; const bot = new Bot(""); // ---cut--- bot.onError(({ context, kind, error }) => { if (kind === "TELEGRAM" && error.method === "sendMessage") { error.params; // это параметры sendMessage } }); ``` ### Unknown Эта ошибка - любая неизвестная ошибка, будь то ваш класс или просто Error. ```ts twoslash import { Bot } from "gramio"; const bot = new Bot(""); // ---cut--- bot.onError(({ context, kind, error }) => { if (kind === "UNKNOWN") { console.log(error.message); } }); ``` --- --- url: /ru/hooks/on-response.md --- # onResponse Этот хук вызывается `после получения успешного ответа` от Telegram Bot API. ## Параметры Объект с: - method - название метода API - params - параметры метода API - response - ответ ## Пример ```ts twoslash import { Bot } from "gramio"; const bot = new Bot(process.env.BOT_TOKEN as string).onResponse((context) => { console.log("ответ для", context.method, context.response); }); ``` ### Добавление хука только для определенных методов API ```ts bot.onResponse("sendMessage", (context) => { console.log("ответ для sendMessage", context.response); }); // или массив bot.onResponse(["sendMessage", "sendPhoto"], (context) => { console.log("ответ для", context.method, context.response); }); ``` --- --- url: /ru/hooks/on-response-error.md --- # onResponseError Этот хук вызывается `после получения ответа с ошибкой` от Telegram Bot API. ## Параметры [TelegramError](https://jsr.io/@gramio/core@latest/doc/~/TelegramError) ## Пример ```ts twoslash import { Bot } from "gramio"; const bot = new Bot(process.env.BOT_TOKEN as string).onResponseError( (context) => { console.log("Ошибка для", context.method, context.message); } ); ``` ### Добавление хука только для определенных методов API ```ts bot.onResponseError("sendMessage", (context) => { console.log("Ошибка для sendMessage", context.message); }); // или массив bot.onResponseError(["sendMessage", "sendPhoto"], (context) => { console.log("Ошибка для", context.method, context.message); }); ``` --- --- url: /ru/hooks/on-start.md --- # onStart Этот хук вызывается при `запуске` бота. ## Параметры ```ts { plugins: string[]; info: TelegramUser; updatesFrom: "webhook" | "long-polling"; } ``` - plugins - массив плагинов - info - информация об аккаунте бота - updatesFrom - webhook/polling ## Пример ```ts twoslash import { Bot } from "gramio"; const bot = new Bot(process.env.BOT_TOKEN as string).onStart( ({ plugins, info, updatesFrom }) => { console.log(`список плагинов - ${plugins.join(", ")}`); console.log(`имя пользователя бота @${info.username}`); console.log(`обновления из ${updatesFrom}`); } ); bot.start(); ``` --- --- url: /ru/hooks/on-stop.md --- # onStop Этот хук вызывается при остановке бота. ## Параметры ```ts { plugins: string[]; info: TelegramUser; } ``` - plugins - массив плагинов - info - информация об аккаунте бота ## Пример ```ts twoslash // @noErrors import { Bot } from "gramio"; const bot = new Bot(process.env.BOT_TOKEN as string).onStop( ({ plugins, info }) => { console.log(`список плагинов - ${plugins.join(", ")}`); console.log(`имя пользователя бота @${info.username}`); } ); bot.start(); bot.stop(); ``` --- --- url: /ru/hooks/pre-request.md --- # preRequest Этот хук вызывается `перед отправкой запроса` в Telegram Bot API (позволяет нам влиять на отправляемые параметры). ## Параметры - method - название метода API - params - параметры метода API > [!IMPORTANT] > Возврат контекста из обработчика хука обязателен! ## Пример ```ts twoslash import { Bot } from "gramio"; const bot = new Bot(process.env.BOT_TOKEN as string).preRequest((context) => { if (context.method === "sendMessage") { context.params.text = "измененные параметры"; } return context; }); bot.start(); ``` ### Добавление хука только для определенных методов API ```ts bot.preRequest("sendMessage", (context) => { context.params.text = "измененные параметры"; return context; }); // или массив bot.preRequest(["sendMessage", "sendPhoto"], (context) => { if (context.method === "sendMessage") { context.params.text = "измененные параметры"; } else context.params.caption = "метод sendPhoto"; return context; }); ``` --- --- url: /ru/hooks/overview.md --- # Хуки Система хуков позволяет нам подключаться к жизненному циклу API-запроса/контекста и каким-то образом влиять на него. Ниже приведены хуки, доступные в GramIO: - [onStart](/ru/hooks/on-start) - вызывается при запуске бота - [onStop](/ru/hooks/on-stop) - вызывается при остановке бота - [onError](/ru/hooks/on-error) - вызывается при возникновении ошибки в контекстах - [preRequest](/ru/hooks/pre-request) - вызывается перед отправкой запроса в Telegram Bot API (позволяет нам влиять на отправляемые параметры) - [onResponse](/ru/hooks/on-response) - вызывается при получении ответа на API-запрос (только в случае успеха) - [onResponseError](/ru/hooks/on-response-error) - вызывается при получении ответа на API-запрос (только в случае ошибки)