Создание простого Telegram-бота с клавиатурой и inline-кнопками [Часть 2]
Два с воловиной года назад на сайте была статья о том, как сделать телеграм-бота. Сегодня я решил немного обновить используемую библиотеку и рассмотреть уже в новой части возможность добавления в бота клавиатуры с кнопками.
И сейчас я сразу начну с написания кода, ведь по сути эта статья является продолжением предыдущей (а в ней как раз-таки можно найти подробную инструкцию по подготовке и регистрации нового бота).
Архив с обновлённой библиотекой (работает на версии php >= 8.0):
Первым делом добавляем библиотеку в свой проект и создаём php-файл, в котором, собственно, и будем писать бота. После этого необходимо подключить библиотеку:
Теперь создаём экземпляр класса Bot и инициализируем переменные $data, $chat_id и $message:
$tg = \telekot\Bot::create( 'токен' ) ->initData( $data ) ->initChatId( $chat_id ) ->initMessage( $message );
Создаём массив с нужными нам кнопками:
$kbd = [ 'inline_keyboard' => [ [ [ 'text' => 'Кнопка #1', 'callback_data' => 'one' ], [ 'text' => 'Кнопка #2', 'callback_data' => 'two' ] ], [ [ 'text' => '🌄 Картинка', 'callback_data' => 'img' ] ] ] ];
Далее напишем проверку на клик по inline-кнопке. Также я добавлю блок else, в него добавим пару команд для демонстрации обновлений библиотеки:
if ( isset( $data['callback_query'] ) ) < // Тут будем обрабатывать нажатие на кнопки >else < // Тут будем обрабатывать обычные команды >
Стоит заметить, что всё содержимое массива $data при наличии callback_query нужно искать уже внутри callback_query ( $data[‘callback_query’][‘*а дальше уже как обычно..*’] ).
Ну, а теперь с помощью конструкции switch-case проверим, на какую из кнопок нажал юзер:
switch ( $data['callback_query']['data'] ) < case 'one': < $tg->sendMessage( $chat_id, 'Вы нажали на первую кнопку' ); break; > case 'two': < $tg->sendMessage( $chat_id, 'Вы нажали на вторую кнопку' ); break; > case 'img': < $tg->sendImage( 'Держи картинку', 'https://proprikol.ru/wp-content/uploads/2020/10/kartinki-ozero-45.jpeg' ); break; > >
При нажатии любую из первых двух кнопок бот просто уведомит пользователя об этом, при нажатии на третью — отправит картинку с помощью метода sendImage().
Да, как и обещал — в блок else добавляем ещё две команды:
if ( $message == '/привет' ) < $tg->reply( 'Здарова' ); > else if ( $message == '/кнопки' ) < $tg->sendMessage( $chat_id, 'Вот твои кнопки:', $kbd ); >
При вводе команды /кнопки — бот будет отправлять клавиатуру пользователю. При вводе команды /привет — здороваться с ним. Однако для первой команды я использовал метод reply(), а для второй — метод sendMessage(). Почему? Разница, на самом-то деле, небольшая: в метод sendMessage() необходимо передавать ID чата, а в метод reply() айди передавать не нужно (ответ придёт тому, кто написал боту). Поэтому в нашем случае, конечно же, удобнее и проще юзать reply().
initData( $data ) ->initChatId( $chat_id ) ->initMessage( $message ); $kbd = [ 'inline_keyboard' => [ [ [ 'text' => 'Кнопка #1', 'callback_data' => 'one' ], [ 'text' => 'Кнопка #2', 'callback_data' => 'two' ] ], [ [ 'text' => '🌄 Картинка', 'callback_data' => 'img' ] ] ] ]; if ( isset( $data['callback_query'] ) ) < switch ( $data['callback_query']['data'] ) < case 'one': < $tg->sendMessage( $chat_id, 'Вы нажали на первую кнопку' ); break; > case 'two': < $tg->sendMessage( $chat_id, 'Вы нажали на вторую кнопку' ); break; > case 'img': < $tg->sendImage( 'Держи картинку', 'https://proprikol.ru/wp-content/uploads/2020/10/kartinki-ozero-45.jpeg' ); break; > > > else < if ( $message == '/привет' ) < $tg->reply( 'Здарова' ); > else if ( $message == '/кнопки' ) < $tg->sendMessage( $chat_id, 'Вот твои кнопки:', $kbd ); > >
А вот, собственно, как работает бот:
- 5id15
- 10.08.2022
- 6 346
- 11
- 9
Простой роутер для бота на PHP
Роутер необходим для обработки запроса пользователя в бот. Он позволяет направить данные от Телеграм в нужный метод сценария. Этот пример для приема объектов типа message и callback_query.
Для быстрых ботов с малым функционалом, могу предложить вариант роутера, который решает поставленные перед ним задачи.
Для начала посмотрим какие данные приходят от Телеграм
Update от Телеграм не может сразу иметь более одного типа объекта.
Вот пример update при старте бота:
< update_id: 123456789000, message: < message_id: 1, from: < id: 123456, is_bot: false, first_name: '—', last_name: '—', language_code: 'ru' >, chat: < id: 123456, first_name: '—', last_name: '—', type: 'private' >, date: 1666004497, text: '/start', entities: [] > >
Если отправлена команда Start или любое другое текстовое сообщение в том числе и команда от клавиатуры — то это объект Message со свойством text (пример выше).
Если это медиа сообщение, то это также объект Message с соответствующими свойствами:
document — файл любого формата, в том числе и photo, video или audio, но направленные в виде файла без визуализации
В этих объектах нам будет интересны свойства:
message->[text | photo | video | audio | document | video_note | voice | location | sticker | animation]
Если мы работаем с приватным типом chat, то message->chat не всегда пригодиться, только если для проверки типа чата.
message->from — в этом объекте лежат данные пользователя, в частности его Телеграм id, можно еще использовать другие данные объекта, например для приветствия пользователя по его имени.
message->[text | photo | video | audio | document | video_note | voice | location | sticker | animation] — направленные пользователем данные
message->[entities | caption_entities] — форматирование текстового содержания сообщения
При нажатии на inline-кнопку приходит уже другой тип объекта: CallBack_Query
< update_id: 123456789001, callback_query: < id: '00001222', from: < id: 123456, is_bot: false, first_name: '—', last_name: '—', language_code: 'ru' >, message: < message_id: 11, from: < id: 123456, is_bot: false, first_name: '—', last_name: '—', language_code: 'ru' >, chat: < id: 123456, first_name: '—', last_name: '—', type: 'private' >, date: 1665488669, text: 'Текст сообщения', reply_markup: <"inline_keyboard":[[<"text":"Test Button","callback_data":"inline_click">]]> >, chat_instance: '040495084748393', data: 'inline_click' > >
Из этого объекта нам практически всегда потребуются несколько свойств:
ПРИМЕЧАНИЕ. После того, как пользователь нажмет кнопку обратного вызова, клиенты Telegram будут отображать индикатор выполнения, пока вы не вызовете answerCallbackQuery . Следовательно, необходимо отреагировать, вызвав answerCallbackQuery , даже если уведомление пользователю не требуется (например, без указания каких-либо необязательных параметров).
Из документации Телеграм [https://core.telegram.org/bots/api]
callback_query->id — это свойство нам необходимо для уведомления Телеграм через метод answerCallbackQuery.
Когда мы не уведомим Телеграм, что запрос пришел по месту назначения и обработан, то он будет засылать повторно один и тот же запрос (update) в соответсвии со своей политикой.
Также это происходит когда в момент обработки сервер ответил ошибкой.
По моему опыту повторы идут через: 1 сек, 5 сек, 20 сек, 1 мин, 2 мин, 5 мин . и т.д. в течении дня.
Повторные update от Телеграм чреваты! Например если у вас нажатие по кнопке должно быть пополнение баланса на определенную сумму, и если у вас в коде будет ошибка до выполнения уведомления или вообще отсутствие уведомления, то нажав один раз пользователь может не по своей вине пополнять свой баланс «за кадром» в течении дня, он будет рад, но для вас это будет проблемой.
callback_query->from — в этом объекте лежат данные пользователя, в частности его Телеграм id.
callback_query->message->message_id — нам может пригодиться, например для удаления сообщения, чтобы закрепленные inline-кнопки не были доступны для повторного нажатия.
callback_query->data — самое важное свойство, в нем лежат параметры для выполнения задуманной логики
Строим простой роутер на php
Телеграм направляет на установленный webHook POST запрос с update , нам необходимо получить эти данные, так как они в формате JSON, мы может спокойно их перевести или в объект stdClass() или в ассоциативный массив используя функцию json_decode.
Данные мы получим в сыром виде из потока php://input
// получаем данные от телеграм - преобразуем в объект stdClass $data = json_decode(file_get_contents("php://input"));
Так как $data это объект, то обращаться к его свойствам мы можем используя конструкцию ->
// получим update_id $update_id = $data->update_id;
Проверить существование свойства у объекта можно используя функцию isset()
// проверим существует ли свойство update_id $isSetUpdateId = isset($data->update_id);
// проверим существует ли свойство update_id $isPropertyExists = property_exists($data, "update_id");
Оба варианта вернут bool значение, с разницей при значении у свойства null если оно существует — property_exists() вернет true
$data->update_id = null; return property_exists($data, "update_id"); // true return isset($data->update_id); // false
Теперь с умением проверять наличие свойств у объекта можно смело начинать строить роутер.
/** * Простой роутер бота */ if (isset($data->message)) < // получим id чата $chat_id = $data->message->from->id; // проверим что это текстовое сообщение if (isset($data->message->text)) < // проверим что это старт бота if ($data->message->text == "/start") < // направим запрос в метод старта бота startBot($chat_id); >> elseif(isset($data->message->photo)) < // направим на сохранение photo savePhoto($chat_id, $data->message->photo); > // если это нажатие по кнопке > elseif (isset($data->callback_query)) < // получим id чата $chat_id = $data->callback_query->from->id; // получим callBackQuery_id $cbq_id = $data->callback_query->id; // получим переданное значение в кнопке $c_data = $data->callback_query->data; // спарсим значения $params = explode("_", $c_data); /** В этом месте мы в зависимости от заложенных параметров по принципу param1_param2_param3_param4 и т.д. можем направлять запрос в нужный метод */ if($params[1] === "getPrice") < price($chat_id, $cbq_id, $params); >elseif($params[1] === "getDemo") < demo($chat_id, $cbq_id, $params); >>