Inline-режим — это способ вызвать бота прямо в любом чате, набрав @bot query. Telegram показывает выпадающий список результатов, юзер выбирает один, и сообщение отправляется от его имени с пометкой «via @bot». Это самая «вирусная» фича Bot API: каждое отправленное сообщение — реклама вашего бота.
Разберём, как включить inline, как структурировать inline_query, как кешировать результаты, как обрабатывать выбор и как делать inline-платежи через Stars.
Когда inline-режим уместен
Inline хорошо ложится на сценарии, где юзеру нужно быстро вставить контент в чужой чат, не открывая бота:
- Поиск гифок, стикеров, мемов (классика —
@gif,@pic). - Поиск по базе знаний компании — менеджер в чате с клиентом набирает
@helpbot тариф pro. - Ссылки и шаблоны —
@templates КП на сайт. - Калькуляторы —
@calcbot ипотека 5 млн 20 лет. - Микро-платежи —
@tipbot 100 stars.
Не подходит для длинных диалогов, заявок, FSM-сценариев — там нужен обычный чат с ботом.
Включение inline
В @BotFather → /mybots → выбираете бота → Bot Settings → Inline Mode → Turn On. Дополнительно настраивается placeholder («Введите запрос…») и feedback collection (отправка статистики о выбранных результатах).
Без включения в BotFather бот не получит ни одного inline_query, даже если код готов.
Обработка inline_query
from aiogram import Router
from aiogram.types import (
InlineQuery, InlineQueryResultArticle, InputTextMessageContent
)
router = Router()
@router.inline_query()
async def search(query: InlineQuery):
text = query.query.strip().lower()
if not text:
results = await top_articles(limit=10)
else:
results = await search_articles(text, limit=20)
answer = [
InlineQueryResultArticle(
id=str(a.id),
title=a.title,
description=a.summary[:80],
thumbnail_url=a.thumb,
input_message_content=InputTextMessageContent(
message_text=f"<b>{a.title}</b>\n\n{a.url}",
parse_mode="HTML",
),
)
for a in results
]
await query.answer(
results=answer,
cache_time=60,
is_personal=False,
next_offset=str(len(results)) if len(results) == 20 else "",
)
cache_time — Telegram кеширует результат на N секунд для одинакового query, чтобы не дёргать бота при каждом нажатии клавиши. is_personal=True — кеш только для конкретного юзера (нужно, если результаты зависят от профиля).
Пагинация inline
Telegram запрашивает следующую пачку через тот же inline_query, передавая offset, который вы вернули в next_offset:
@router.inline_query()
async def search(query: InlineQuery):
offset = int(query.offset or 0)
results = await search_articles(query.query, limit=20, offset=offset)
await query.answer(
results=build_results(results),
next_offset=str(offset + 20) if len(results) == 20 else "",
cache_time=10,
)
Пустой next_offset — конец списка. Telegram перестанет запрашивать дальше.
Типы результатов
Bot API поддерживает 20+ типов inline-результатов. Самые ходовые:
| Тип | Когда использовать |
|---|---|
InlineQueryResultArticle | произвольный текст с заголовком |
InlineQueryResultPhoto | фото из URL |
InlineQueryResultGif | гифка |
InlineQueryResultVideo | видео |
InlineQueryResultAudio | аудио |
InlineQueryResultVoice | голосовое |
InlineQueryResultDocument | файл (PDF, ZIP) |
InlineQueryResultLocation | геолокация |
InlineQueryResultContact | контакт |
InlineQueryResultGame | игра HTML5 |
Для медиа можно передавать как *_url (Telegram скачивает сам), так и *_file_id (если файл уже в Telegram — мгновенная отправка).
Кеширование на стороне бота
Bot API кеш cache_time отдаёт повторные запросы из своего кеша, не дёргая бота. Но для тяжёлых поисков по БД (Elasticsearch, PostgreSQL FTS) полезен и собственный кеш в Redis:
async def search_articles(text: str, limit=20, offset=0):
key = f"inline:{text}:{offset}:{limit}"
cached = await redis.get(key)
if cached:
return json.loads(cached)
results = await db.full_text_search(text, limit=limit, offset=offset)
await redis.set(key, json.dumps(results), ex=300)
return results
Это критично при росте: 10000 inline-запросов в минуту без кеша легко положат БД.
chosen_inline_result: что выбрал юзер
Чтобы знать, какой результат юзер реально отправил, подпишитесь на chosen_inline_result. Telegram присылает этот апдейт только если бот включил Inline Feedback в BotFather.
from aiogram.types import ChosenInlineResult
@router.chosen_inline_result()
async def on_chosen(result: ChosenInlineResult):
await analytics.track(
"inline_chosen",
user_id=result.from_user.id,
result_id=result.result_id,
query=result.query,
)
Это золотая аналитика: вы видите, какие запросы конвертируются в реальные отправки, и можете оптимизировать выдачу.
Inline-платежи через Stars
С 2024 года через inline можно отправлять InlineQueryResultArticle с кнопкой «Купить за 50 Stars». При клике отправляется invoice-сообщение, юзер платит, бот ловит successful_payment как обычно.
result = InlineQueryResultArticle(
id="tip_50",
title="Чаевые 50 Stars",
input_message_content=InputInvoiceMessageContent(
title="Чаевые автору",
description="Спасибо за крутой контент!",
payload="tip_50",
provider_token="",
currency="XTR",
prices=[LabeledPrice(label="Tip", amount=50)],
),
)
Так работают @tribute, @donate и другие чаевые-боты.
Топ-5 ошибок при inline
- Забывают включить inline в BotFather —
inline_queryне приходит. - Не возвращают
next_offset— Telegram прекращает пагинацию. - Используют
cache_time=300для персонализированных результатов безis_personal=True— юзеры видят чужие. - Не подписываются на
chosen_inline_result— нет аналитики. - Шлют тяжёлые URL-картинки без
thumbnail_url— превью грузится 5+ секунд.
Итого
Inline-режим — это самый виральный канал для бота: каждое отправленное сообщение работает как реклама. Технически нужно включить inline в BotFather, обрабатывать inline_query с кешем 60+ секунд, корректно пагинировать через next_offset и подключить chosen_inline_result для аналитики. Хорошо встроенный inline даёт +30–50% к органическому росту бота за счёт виральности «via @bot».
Частые вопросы
Можно ли использовать inline без подписки на бота?
Да. Юзер вызывает @yourbot query в любом чате — даже если он никогда не запускал вашего бота. Это уникальное свойство inline: не требует /start. Имя бота юзер должен знать сам, поэтому в маркетинге обязательно показывайте полный handle с @.
Какой максимум результатов отдавать на один запрос?
Лимит Bot API — 50 результатов на ответ. На практике 10–20 хватает в 95% случаев: юзер прокручивает максимум 5–7 первых вариантов. Больше 50 → отдавайте через пагинацию next_offset.
Как сделать inline-результаты персонализированными?
Установите is_personal=True в answer_inline_query — Telegram не будет шарить кеш результатов между юзерами. Внутри обработчика читайте query.from_user.id и стройте выдачу по профилю: например, в @helpbot показывать только статьи, доступные конкретной компании.
Есть ли лимит на количество inline_query?
Bot API не штрафует за частые inline_query сами по себе, но действует общий лимит 30 сообщений в секунду (включая answer_inline_query). Если бот популярный (1000+ запросов в секунду), нужен внутренний кеш и батчинг — иначе возможны 429.
Можно ли в inline-результат добавить кнопки?
Да, через reply_markup: InlineKeyboardMarkup. Кнопки увидит получатель сообщения, и при нажатии прилетит обычный callback_query боту. Это отличный способ соединить вирусность inline с интерактивом callback.
Какой `cache_time` оптимален?
Для статических данных (топ-статьи, мемы, шаблоны) — 300–600 секунд. Для динамических (актуальные предложения, остатки) — 30–60 секунд. Для персонализированных с is_personal=True — обычно 60 секунд хватает: за это время юзер уже сделал выбор или закрыл чат.
Как продвигать бота через inline?
Главный приём — показывать в каждом сообщении бренд: «via @yourbot» отображается автоматически, но в самом тексте можно добавить призыв «Найди ещё в @yourbot». Также работают шаблоны с UTM в URL, чтобы считать, какие inline-сообщения приводят новых юзеров. Виральность 1.4–2.0 (один юзер приводит 1–2 новых) — нормальная цель.