Skip to content

inlineQuery

Метод inlineQuery в GramIO позволяет вашему боту отвечать на инлайн-запросы, отправленные пользователями. Инлайн-запрос - это особый тип сообщения, когда пользователи могут искать контент от вашего бота, введя запрос в поле ввода сообщения, не взаимодействуя напрямую с ботом.

предварительный просмотр функции

Документация Telegram

WARNING

Вам необходимо включить эту опцию через @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 (.*).
  • Если соответствие найдено, бот извлекает поисковый запрос и возвращает список результатов инлайн-запроса.

TIP

context.answer() принимает параметры answerInlineQuery вторым аргументом. Наиболее важный из них — cache_time: как долго Telegram кэширует результаты на своих серверах (по умолчанию 300 с). Передайте cache_time: 0 для динамических результатов, которые меняются с каждым нажатием клавиши — и особенно во время разработки. Все полезные параметры разобраны ниже в разделе опции context.answer().

Ограничения для превью

thumbnail_url у любого варианта InlineQueryResult* (article, photo, video, document, gif и т.д.) должен вести на JPEG — PNG и WebP молча не отрендерятся в части Telegram-клиентов, и превью просто исчезнет, не откатившись к дефолту. Держите картинку ≤320×320 px и ≤200 KB; слишком большой JPEG ломается так же. Если превью не показывается, а URL доступен — начните проверку с формата и размеров.

Параметры

  • trigger: Условие, которому должен соответствовать инлайн-запрос. Это может быть регулярное выражение, строка или пользовательская функция.
    • Регулярное выражение: Соответствует запросам, которые соответствуют определенному шаблону.
    • Строка: Соответствует точной строке запроса.
    • Функция: Оценивает запрос на основе пользовательской логики. Должна возвращать true для соответствия.
  • handler: Функция, которая обрабатывает инлайн-запрос. Она получает объект context, который включает детали о запросе и предоставляет методы для ответа.
  • options: Дополнительные опции для обработки выбора результата.
    • onResult: Функция, которая вызывается, когда пользователь выбирает результат инлайн-запроса. Она может использоваться для изменения или обновления сообщения после выбора. Использует ChosenInlineResult под капотом.

IMPORTANT

Вы можете изменять только сообщения, которые содержат InlineKeyboard. Подробнее о ChosenInlineResult.

Как работает 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 , за которыми следует поисковый термин.
  • Бот отвечает результатом инлайн-запроса, который включает кнопку с надписью "Получить подробности."
  • Когда пользователь выбирает этот результат, бот редактирует сообщение, чтобы указать, что был сделан выбор.

Связка с кнопками switchTo* — лучший UX для инлайн-режима

Большинство пользователей никогда не наберут @YourBot <запрос> сами — это трение, о котором они попросту не знают. Решение: спрятать инлайн-запрос за инлайн-кнопку. Три кнопки switchTo* телепортируют пользователя прямо в инлайн-режим с предзаполненным запросом, а ваш inlineQuery-хендлер отдаёт курированный пикер. Один тап, выбор из списка, Telegram сам постит результат — без ввода, без команд.

  • switchToCurrentChat — открывает инлайн-режим в этом же чате. Идеально для «выбери одно из вариантов» внутри приватного UI бота.
  • switchToChat — предлагает выбрать любой чат и пошарить туда. Канонический flow «Отправить другу» / «Поделиться карточкой».
  • switchToChosenChat — то же самое, но с фильтром по типу чата (личка / группа / канал / бот).

Пример шаринга — /share отвечает кнопкой-пикером, инлайн-хендлер отдаёт конкретную карточку по id:

ts
bot.command("share", (ctx) =>
    ctx.send("Поделиться карточкой:", {
        reply_markup: new InlineKeyboard().switchToChosenChat("📤 Отправить в…", {
            query: "card-42",
            allow_user_chats: true,
            allow_group_chats: true,
        }),
    }),
);

bot.inlineQuery(/^card-(\d+)$/, async (ctx) => {
    const card = await loadCard(ctx.args![1]);
    return ctx.answer(
        [
            InlineQueryResult.article(
                card.id,
                card.title,
                InputMessageContent.text(card.body),
            ),
        ],
        { cache_time: 0, is_personal: true },
    );
});

Почему связка настолько сильная:

  • Ноль ввода. query предзаполнен — пользователь только выбирает результат.
  • Нативный UI пикера. Диалог инлайн-результатов пользователи уже знают по @gif, @stickers, @vote — узнаваемость в подарок.
  • Не выпадаешь из потока. switchToCurrentChat не уводит из чата; switchToChat превращает шаринг в один тап.
  • Без мини-аппов, без диплинков, без редиректов на бэкенд. Весь round-trip — нативный Telegram.

Любая фича, которая иначе превратилась бы в «введите команду, потом ответьте опцией» — голосование, пикеры GIF/стикеров, выбор языка или таймзоны, шаринг цитат/карточек, пикер контактов — почти всегда лучше делать через switchTo* + инлайн-хендлер.

Опции context.answer()

Второй аргумент context.answer() принимает все параметры answerInlineQuery. Четыре, которые реально пригодятся:

button — пригласить пользователя войти, пройти онбординг или открыть Mini App

Инлайн-режим работает в любом чате — он не может показывать приватный UI вашего бота, принимать загрузку файлов или вести многошаговые сценарии. Если для выдачи результатов нужно что-то такое (авторизация, подключение стороннего аккаунта, согласие на условия, настройка параметров), верните пустой массив results плюс верхнюю button, которая уведёт пользователя туда, где ваш бот эти сценарии может выполнить.

InlineQueryResultsButton — это discriminated union: укажите ровно одно из:

  • start_parameter — открывает приватный чат и вызывает /start <parameter>, который попадает в ваш bot.command("start") как ctx.args. 1-64 символа, только A-Z, a-z, 0-9, _, -.
  • web_app — запускает Mini App. См. гайд по TMA.
ts
bot.inlineQuery(async (ctx) => {
    if (!(await isAuthenticated(ctx.from.id))) {
        return ctx.answer([], {
            cache_time: 0, // не кэшируем приглашение к логину
            is_personal: true, // оно персонально для каждого пользователя
            button: {
                text: "Войти, чтобы продолжить",
                start_parameter: "login-inline",
            },
        });
    }

    // ...обычные результаты после авторизации
});

// В обработчике /start — принять deep-link payload
bot.command("start", (ctx) => {
    if (ctx.args === "login-inline") {
        return ctx.send("Давайте вас авторизуем…");
    }
});

Это канонический паттерн «пользователь не авторизован → уводим в ЛС» для инлайн-ботов — из инлайн-режима сам бот никак пользователя в приватный чат не вытянет. button — единственный путь.

is_personal — персональные результаты (отключает общий кэш)

Передавайте is_personal: true, когда результаты зависят от конкретного пользователя (его подписки, избранное, права). Без этого флага персональная выдача одного пользователя может утечь другому с тем же запросом — Telegram по умолчанию шарит кэш между пользователями.

next_offset — пагинация

Передайте короткий токен (максимум 64 байта) — это сигнал «есть ещё результаты». Когда пользователь долистает до конца вашей выдачи, Telegram повторит запрос, и токен придёт в ctx.inlineQuery.offset. Передайте пустую строку, чтобы сказать «больше нет».

ts
bot.inlineQuery(async (ctx) => {
    const page = Number(ctx.inlineQuery.offset) || 0;
    const { items, hasMore } = await fetchPage(page);

    return ctx.answer(
        items.map((i) =>
            InlineQueryResult.article(
                i.id,
                i.title,
                InputMessageContent.text(i.body)
            )
        ),
        { next_offset: hasMore ? String(page + 1) : "" }
    );
});

cache_time — длительность общего кэша

Telegram кэширует вашу выдачу для одинаковых запросов у всех пользователей на указанное число секунд (по умолчанию 300). Ставьте 0 во время разработки и для динамического контента. Комбинируйте с is_personal: true, если персональные результаты всё же должны коротко кэшироваться для самого пользователя.