Продвинутое
Inline mode
Inline mode позволяет пользователям вызывать бота через @username в любом чате. Разбираем включение через BotFather, обработку inline_query, типы результатов (article, photo, video), пагинацию через next_offset и chosen_inline_result.
Inline mode позволяет пользователям вызывать бота в любом чате через @username запрос. Бот получает inline_query, формирует список результатов и возвращает их через answerInlineQuery. Пользователь выбирает результат — он вставляется в поле ввода как сообщение от его имени.
Включение Inline mode
В BotFather выберите бота → Bot Settings → Inline Mode → Turn on. Можно задать placeholder (подсказку в поле ввода). После включения бот начнет получать апдейты типа inline_query.
Структура inline_query
Апдейт содержит:
id— уникальный идентификатор запроса (string, до 64 байт)from— пользователь, сделавший запросquery— текст запроса (может быть пустым)offset— смещение для пагинации (string)chat_type— тип чата, из которого сделан запрос (optional)location— местоположение пользователя (optional, если запрошено)
Ответ: answerInlineQuery
Метод принимает массив results (до 50 элементов) и опциональные параметры:
cache_time— кэширование результата на стороне клиента (сек, дефолт 300)is_personal— результаты персональны для пользователяnext_offset— токен для следующей страницы (пагинация)switch_pm_text/switch_pm_parameter— кнопка «Перейти в бота»
Хелпер telegramApi() описан в главе Первый запрос: getMe.
<?php
function answerInlineQuery(string $inlineQueryId, array $results, array $options = []): array {
$payload = array_merge([
'inline_query_id' => $inlineQueryId,
'results' => json_encode($results),
], $options);
return telegramApi('answerInlineQuery', $payload);
}
Типы результатов (InlineQueryResult)
Каждый результат — объект с обязательным type и id (уникальный в пределах ответа). Основные типы:
- article — текстовое сообщение с заголовком, описанием,
input_message_content(InputTextMessageContent) - photo — фото по URL (
photo_url,thumb_url) - video — видео (
video_url,mime_type,thumb_url) - document — файл (
document_url,mime_type) - audio, voice, location, venue, contact, game, sticker
Для article и типов без файла используется input_message_content — объект с полями message_text, parse_mode, entities, link_preview_options.
Пример: поиск статей
Допустим, у нас есть локальный массив статей. Обрабатываем inline_query внутри processUpdate() (глава Long polling).
<?php
function handleInlineQuery(array $update): void {
$iq = $update['inline_query'];
$query = mb_strtolower(trim($iq['query'] ?? ''));
$offset = (int)($iq['offset'] ?? 0);
$limit = 20;
// Пример данных — в продакшене это БД/поисковый движок
$articles = [
['id' => '1', 'title' => 'PHP 8.3: новинки', 'text' => 'JIT, typed class constants, json_validate...'],
['id' => '2', 'title' => 'Telegram Bot API 7.0', 'text' => 'Stars, Mini Apps, reply parameters...'],
['id' => '3', 'title' => 'PSR-18 HTTP Client', 'text' => 'Стандарт интерфейсов для HTTP-клиентов.'],
];
$filtered = array_filter($articles, fn($a) => $query === '' ||
mb_stripos($a['title'], $query) !== false ||
mb_stripos($a['text'], $query) !== false);
$filtered = array_values($filtered);
$page = array_slice($filtered, $offset, $limit);
$results = array_map(function ($art) {
return [
'type' => 'article',
'id' => $art['id'],
'title' => $art['title'],
'description' => mb_substr($art['text'], 0, 100),
'input_message_content' => [
'message_text' => "<b>{$art['title']}</b>\n{$art['text']}",
'parse_mode' => 'HTML',
],
];
}, $page);
$nextOffset = (count($filtered) > $offset + $limit) ? (string)($offset + $limit) : '';
answerInlineQuery($iq['id'], $results, [
'cache_time' => 60,
'next_offset' => $nextOffset,
'is_personal' => true,
]);
}
Обработка выбранного результата
Когда пользователь выбирает результат, приходит апдейт chosen_inline_result с полями:
result_id— id выбранного результатаfrom— пользовательquery— исходный запросinline_message_id— id отправленного сообщения (для редактирования черезeditMessageTextсinline_message_id)
Это полезно для аналитики и для последующего редактирования вставленного сообщения.
<?php
function handleChosenInlineResult(array $update): void {
$cir = $update['chosen_inline_result'];
// Логируем выбор, можно обновить счётчик популярности
error_log("Chosen: {$cir['result_id']} by user {$cir['from']['id']} query: {$cir['query']}");
// Если нужно отредактировать вставленное сообщение позже:
// $inlineMessageId = $cir['inline_message_id'];
// telegramApi('editMessageText', [
// 'inline_message_id' => $inlineMessageId,
// 'text' => 'Обновлённый текст',
// 'parse_mode' => 'HTML',
// ]);
}
Интеграция в процесс обновлений
Добавьте ветку в processUpdate():
<?php
function processUpdate(array $update): void {
if (isset($update['message'])) {
handleMessage($update['message']);
} elseif (isset($update['callback_query'])) {
handleCallbackQuery($update['callback_query']);
} elseif (isset($update['inline_query'])) {
handleInlineQuery($update);
} elseif (isset($update['chosen_inline_result'])) {
handleChosenInlineResult($update);
}
}
Важные нюансы
- Результаты должны возвращаться быстро — таймаут ответа ~1-2 сек. Для тяжёлых запросов используйте
next_offsetи фоновые воркеры. cache_time> 0 позволяет клиенту не дергать бота при повторном вводе того же запроса.is_personal: true— результаты не кэшируются глобально, только для конкретного пользователя.- Максимум 50 результатов за один ответ. Для больших выборок — пагинация через
next_offset. - Inline mode работает в группах, каналах (если бот админ) и личных чатах.
Дальше: Безопасность