InlineKeyboardMarkup на PHP
У цьому уроці ми зібрали невеликий обробник PHP для вбудованих клавіатур Telegram. Ви:
- зібрати кнопки, які
callback_dataзнаходиться в межах ліміту API бота в 64 байти; - прийняти оновлення за допомогою
callback_query; - зателефонувати
answerCallbackQueryзняти спінер на кнопці; - оновити повідомлення в
editMessageReplyMarkup; - використовувати тонку обгортку cURL зі статусом http та перевіркою прапорця
Гаразд.
Це не повноцінний фреймворк бота: без DI, черги та секретного токена вебхука. Мета — це робочий шлях для постановки, який легко розширити.
1. Клієнт API бота
Перевірте HTTP-код і поле Гаразд в JSON. Telegram часто відповідає HTTP 200 з тілом {"ok":false,...}.
<?php
// telegram.php
function tgApi(string $method, array $params, ?string $token = null): array
{
$token = $token ?? getenv('TELEGRAM_BOT_TOKEN');
if (!$token) {
throw new RuntimeException('TELEGRAM_BOT_TOKEN is not set');
}
$url = 'https://api.telegram.org/bot' . $token . '/' . $method;
$body = http_build_query($params, '', '&');
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_CONNECTTIMEOUT => 5,
]);
$raw = curl_exec($ch);
if ($raw === false) {
$err = curl_error($ch);
curl_close($ch);
throw new RuntimeException('cURL error: ' . $err);
}
$status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status !== 200) {
throw new RuntimeException('HTTP ' . $status . ' from ' . $method);
}
$decoded = json_decode($raw, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new RuntimeException('JSON decode failed: ' . json_last_error_msg());
}
if (!is_array($decoded) || !($decoded['ok'] ?? false)) {
$desc = $decoded['description'] ?? 'unknown error';
$code = $decoded['error_code'] ?? 0;
throw new RuntimeException('Telegram ok:false (' . $code . '): ' . $desc);
}
return $decoded['result'];
}
Візьміть маркер з навколишнього середовища. Не кодуйте його жорстко і не повертайте його в текстах помилок.
2. Збірка InlineKeyboardMarkup
InlineKeyboardMarkup — JSON масив рядків кнопок. Кнопка має text та одне поле діяльності: callback_data, url, web_app від кулі switch_inline_query_*. Тут ми використовуємо callback_data — генерує callback_query на вебхук.
Жорсткий ліміт: callback_data не больше. 64 байта UTF-8 (байти, а не символи) Об 'єкти JSON майже завжди перевищують ліміт — отримати BUTTON_DATA_INVALID.
Надішліть короткий непрозорий токен і вирішіть його на сервері. Нижче l:42 / d:42 вписуються в бюджет з маржею.
<?php
// keyboards.php
require_once __DIR__ . '/telegram.php';
function likeDislikeKeyboard(int $postId, ?int $currentVote = null): array
{
// "l:1234567890" -> максимум ~12 байт
$likePayload = 'l:' . $postId;
$dislikePayload = 'd:' . $postId;
$likeText = $currentVote === 1 ? 'Нравится' : 'Лайк';
$dislikeText = $currentVote === -1 ? 'Не нравится' : 'Дизлайк';
return [
'inline_keyboard' => [
[
['text' => $likeText, 'callback_data' => $likePayload],
['text' => $dislikeText, 'callback_data' => $dislikePayload],
],
],
];
}
function sendPostWithKeyboard(int $chatId, string $text, int $postId): array
{
return tgApi('sendMessage', [
'chat_id' => $chatId,
'text' => $text,
'reply_markup' => json_encode(likeDislikeKeyboard($postId)),
]);
}
Для одноразових підтверджень зручніше bin2hex(random_bytes(7)) (14 шістнадцяткових символів): спочатку вставте відображення, а потім надішліть клавіатуру.
3. Webhook: прийом callback_query
Telegram надсилає JSON через HTTPS. Типовий callback_query:
{
"update_id": 123456789,
"callback_query": {
"id": "...",
"from": {"id": 111, "is_bot": false, "first_name": "..."},
"chat_instance": "...",
"message": {
"message_id": 17,
"chat": {"id": 111, "type": "private"},
"date": 1700000000,
"text": "..."
},
"data": "l:42"
}
}
Завжди телефонувати answerCallbackQuery із ідентифікатором запиту, а потім відредагуйте або надішліть нове повідомлення. Без відповіді кнопка обертає обертач до тайм-ауту клієнта.
<?php
// webhook.php
declare(strict_types=1);
require_once __DIR__ . '/telegram.php';
require_once __DIR__ . '/keyboards.php';
header('Content-Type: application/json');
$raw = file_get_contents('php://input');
if ($raw === false || $raw === '') {
http_response_code(400);
echo json_encode(['error' => 'empty body']);
return;
}
$update = json_decode($raw, true);
if (json_last_error() !== JSON_ERROR_NONE || !is_array($update)) {
http_response_code(400);
echo json_encode(['error' => 'bad json']);
return;
}
try {
if (isset($update['callback_query'])) {
handleCallback($update['callback_query']);
} elseif (isset($update['message']['text']) && $update['message']['text'] === '/start') {
sendPostWithKeyboard(
(int) $update['message']['chat']['id'],
'Демо-пост — нажмите кнопки.',
42
);
}
} catch (Throwable $e) {
error_log('[telegram] ' . $e->getMessage());
}
http_response_code(200);
echo '{"ok":true}';
function handleCallback(array $cq): void
{
$queryId = (string) $cq['id'];
$data = (string) ($cq['data'] ?? '');
$message = $cq['message'] ?? null;
$chatId = (int) ($message['chat']['id'] ?? 0);
$messageId = (int) ($message['message_id'] ?? 0);
if (!preg_match('/^([ld]):(\d{1,10})$/', $data, $m)) {
tgApi('answerCallbackQuery', [
'callback_query_id' => $queryId,
'text' => 'Неизвестное действие.',
'show_alert' => true,
]);
return;
}
$vote = $m[1] === 'l' ? 1 : -1;
$postId = (int) $m[2];
$current = recordVote($chatId, $postId, $vote);
tgApi('answerCallbackQuery', [
'callback_query_id' => $queryId,
'text' => 'Сохранено.',
]);
tgApi('editMessageReplyMarkup', [
'chat_id' => $chatId,
'message_id' => $messageId,
'reply_markup' => json_encode(likeDislikeKeyboard($postId, $current)),
]);
}
function recordVote(int $chatId, int $postId, int $vote): int
{
static $store = [];
$store[$chatId][$postId] = ($store[$chatId][$postId] ?? 0) === $vote ? 0 : $vote;
return $store[$chatId][$postId];
}
Три важливі моменти:
- Відповідайте на зворотний дзвінок, навіть якщо наступне редагування не вдалося. Telegram чекає на відповідь близько 30 секунд.
editMessageText/editMessageReplyMarkupможе повернути "повідомлення не змінено" з тим самим вмістом — це неможливо, а не причина для відступу.- При
parse_mode=HTMLзахистити користувацький ввід черезhtmlspecialcharsперед вставкою в HTML.
4. Виробничі примітки
- Секрет вебхука. Встановити
secret_tokenвsetWebhookі перевірте назвуX-Telegram-Bot-Api-Secret-Token. - Ідемпотентність. Магазин оброблено
update_idі відкиньте дублікати. - Ліміти О 429 спостерігати
parameters.retry_after. Коли навантаження високе, поставте вихідні дзвінки в чергу з ідентифікатором чату. - Callback_data design. Зберігати менше 64 байт. Короткий жетон краще, ніж довгий рядок.
- Deeplinks.
t.me/Bot?start=payloadспрацьовує лише наStart. Щоб відкрити приватний чат з корисним навантаженням з клавіатури, скористайтеся кнопкою URL-адреси.
5. Контрольний список
callback_data≤ 64 байт UTF-8answerCallbackQueryпротягом ~30 секунд, навіть з помилками- HTTP 200 і
"ok":trueперш ніж виклик вважатиметься успішним - Екранування HTML користувацького тексту, коли
parse_mode=HTML - Секрет веб-перехоплювача перевірено; дублікати відфільтровано за
update_id
Клавіатура при відправці, зворотний дзвінок при отриманні, відповідь плюс редагування, коротке корисне навантаження — весь цикл.
Потрібно більше, ніж фрагмент: клавіатури, міні-додатки, платежі? Почніть з botservice.biz.