Создание Telegram бота на PHP #5: работа с хуками

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

В новом уроке мы с вами поговорим о настройке хуков и напишем свой первый обработчик команд.

В первом уроке я вам рассказывал что такое хуки, давайте повторим:

Hooks (Хуки) — это способ общения с программой, по средствам отправки данных от сервера — клиенту. То есть при определённых изменениях в программе, сервер (приложение) будет отправлять данные на указанный URL скрипта клиента.

Например. Каждый раз когда пользователи будут писать сообщения боту, данные о сообщениях будут отправляться на указанный скрипт, где вы сможете записать сообщения в БД или отправить ответ.

Полный список всех записей курса находится на сайте https://prog-time.ru/course_cat/telegram-bot-basic/ или в публикациях на Хабр https://habr.com/ru/users/Prog-Time/posts/

Для регистрации хука нужно выполнить 2 правила:

  • разместить скрипт на виртуальный сервер (хостинг)

  • домен на который будут отправляться запросы, должен иметь SSL сертификат и работать через HTTPS соединение

Если ваш хостинг соответствует данным требованиям, то давайте займёмся регистрацией хука для Telegram бота.

Регистрация хука для Telegram бота

Для регистрации хука нам нужно отправить запрос с методом setWebhook(), которому в качестве параметра url мы должны передать ссылку на скрипт обработчик. В моём случае это просто php скрипт.

Вот пример запроса:

$token = "5340791844:AAEXXDduvInvQrlykV91USOQSevrPVU";

$getQuery = array(
     "url" => "https://prog-time.ru/tg_script/index.php",
);
$ch = curl_init("https://api.telegram.org/bot". $token ."/setWebhook?" . http_build_query($getQuery));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, false);

$resultQuery = curl_exec($ch);
curl_close($ch);

echo $resultQuery;

Если запрос прошёл успешно, то вы получите следующий ответ:

{
  "ok": true,
  "result": true,
  "description": "Webhook was set"
}

Теперь давайте проверим работу нашего обработчика. Сообщения приходят POST-запросом, с типом application/json. Получить его в PHP можно следующим образом:

$data = file_get_contents('php://input');
$data = json_decode($data, true);

Разбор параметров передаваемых через Hooks

Давайте теперь посмотрим что приходит на наш скрипт при отправке простого текстового сообщения боту.

Здесь есть небольшая проблема! Скрипты будут выполняться в рандомный момент и если мы не запишем данные, то они пропадут в пустоту. Для записи ответа вы можете использовать БД или как я, просто записать массив в файл txt.

Для записи строки я буду использовать дополнительную, самописную функцию writeLogFile()

Моя функция принимает 2 параметра:

  • первый параметр, строка для записи. В нашем случае это JSON строка.

  • второй параметр используется для очистки файла и перезаписи. Если данный параметр имеет значение false, то в файл дописывается информация.

function writeLogFile($string, $clear = false){
    $log_file_name = __DIR__."/message.txt";
    if($clear == false) {
	$now = date("Y-m-d H:i:s");
	file_put_contents($log_file_name, $now." ".print_r($string, true)."\r\n", FILE_APPEND);
    }
    else {
	file_put_contents($log_file_name, '');
        file_put_contents($log_file_name, $now." ".print_r($string, true)."\r\n", FILE_APPEND);
    }
}

Полный код для записи информации в файл будет выглядеть следующим образом.

function writeLogFile($string, $clear = false){
    $log_file_name = __DIR__."/message.txt";
    if($clear == false) {
		$now = date("Y-m-d H:i:s");
		file_put_contents($log_file_name, $now." ".print_r($string, true)."\r\n", FILE_APPEND);
    }
    else {
		file_put_contents($log_file_name, '');
        file_put_contents($log_file_name, $now." ".print_r($string, true)."\r\n", FILE_APPEND);
    }
}

$data = file_get_contents('php://input');
writeLogFile($data, true);

После отправки сообщения боту, данные были отправлены на наш скрипт и мы записали их в лог файл.

Теперь выведем полученную информацию на страницу

echo file_get_contents(__DIR__."/message.txt");

Вот что мы получаем. Это объект в котором записана информация о созданном сообщение, мы видим данные о пользователе, данные о чате, дата отправления и текст сообщения.

{
  "update_id": 803290892,
  "message": {
    "message_id": 41,
    "from": {
      "id": 1424646511,
      "is_bot": false,
      "first_name": "Илья",
      "last_name": "Лящук",
      "username": "iliyalyachuk",
      "language_code": "ru"
    },
    "chat": {
      "id": 1424646511,
      "first_name": "Илья",
      "last_name": "Лящук",
      "username": "iliyalyachuk",
      "type": "private"
    },
    "date": 1659098034,
    "text": "Новое тестовое сообщение"
  }
}

Данные при нажатие на кнопку в чате

Если пользователь нажал на кнопку, то на скрипт также будет отправлен запрос с данными о пользователе и о кнопке.

Отличительной особенностью таких запросов является то что главный ключ message заменяется на callback_query, а сам массив message будет находиться внутри.

Получить код кнопки на которую было произведено нажатие, можно из callback_query -> data.

{
  "update_id": 803290921,
  "callback_query": {
    "id": "6118810175780540321",
    "from": {
      "id": 1424646511,
      "is_bot": false,
      "first_name": "Илья",
      "last_name": "Лящук",
      "username": "iliyalyachuk",
      "language_code": "ru"
    },
    "message": {
      "message_id": 113,
      "from": {
        "id": 5340791844,
        "is_bot": true,
        "first_name": "test_prog_time",
        "username": "test_prog_time_bot"
      },
      "chat": {
        "id": 1424646511,
        "first_name": "Илья",
        "last_name": "Лящук",
        "username": "iliyalyachuk",
        "type": "private"
      },
      "date": 1659335238,
      "text": "Тестовое сообщение",
      "reply_markup": {
        "inline_keyboard": [
          [
            {
              "text": "YOUR BUTTON LABEL TEXT",
              "callback_data": "test_123"
            }
          ]
        ]
      }
    },
    "chat_instance": "4661722712167232747",
    "data": "test_123"
  }
}

Данные при отправке изображения

Теперь давайте посмотри данные которые приходят при отправке изображения в чат, от пользователя.

{
  "update_id": 803290893,
  "message": {
    "message_id": 42,
    "from": {
      "id": 1424646511,
      "is_bot": false,
      "first_name": "Илья",
      "last_name": "Лящук",
      "username": "iliyalyachuk",
      "language_code": "ru"
    },
    "chat": {
      "id": 1424646511,
      "first_name": "Илья",
      "last_name": "Лящук",
      "username": "iliyalyachuk",
      "type": "private"
    },
    "date": 1659099213,
    "photo": [
      {
        "file_id": "AgACAgIAAxkBAAMqYuPYTHnTFqNQZ3DB5B-f_MovPOMAArm9MRud5CFLxgi3BP6dpsoBAAMCAANzAAMpBA",
        "file_unique_id": "AQADub0xG53kIUt4",
        "file_size": 1863,
        "width": 90,
        "height": 90
      },
      {
        "file_id": "AgACAgIAAxkBAAMqYuPYTHnTFqNQZ3DB5B-f_MovPOMAArm9MRud5CFLxgi3BP6dpsoBAAMCAANtAAMpBA",
        "file_unique_id": "AQADub0xG53kIUty",
        "file_size": 30064,
        "width": 320,
        "height": 320
      },
      {
        "file_id": "AgACAgIAAxkBAAMqYuPYTHnTFqNQZ3DB5B-f_MovPOMAArm9MRud5CFLxgi3BP6dpsoBAAMCAAN5AAMpBA",
        "file_unique_id": "AQADub0xG53kIUt-",
        "file_size": 133230,
        "width": 880,
        "height": 880
      },
      {
        "file_id": "AgACAgIAAxkBAAMqYuPYTHnTFqNQZ3DB5B-f_MovPOMAArm9MRud5CFLxgi3BP6dpsoBAAMCAAN4AAMpBA",
        "file_unique_id": "AQADub0xG53kIUt9",
        "file_size": 138716,
        "width": 800,
        "height": 800
      }
    ]
  }
}

После получения данного массива мы можем сохранить отправленное изображение на своём сервере. Для этого нам нужно с помощью метода getFile получить полный путь к изображению, передав ему в качестве параметра file_id.

Полный код для сохранения будет выглядеть так:

/* токен */
$token = "5340791844:AAEXXDduvInvQrlWHRXykV91USOQSevrPVU";

/* массив с параметрами запроса */
$getQuery = array(
    "file_id" => "AgACAgIAAxkBAAMqYuPYTHnTFqNQZ3DB5B-f_MovPOMAArm9MRud5CFLxgi3BP6dpsoBAAMCAAN5AAMpBA",
);
$ch = curl_init("https://api.telegram.org/bot". $token ."/getFile?" . http_build_query($getQuery));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_HEADER, false);

$resultQuery = curl_exec($ch);
curl_close($ch);

/* записываем ответ в формате PHP массива */
$arrDataResult = json_decode($resultQuery, true);

/* записываем URL необходимого изображения */
$fileUrl = $arrDataResult["result"]["file_path"];

/* формируем полный URL до файла */
$photoPathTG = "https://api.telegram.org/file/bot". $token ."/" . $fileUrl;

/* забираем название файла */
$arrFilePath = explode("/", $fileUrl);
$newFilerPath = __DIR__ . "/img/" . $arrFilePath[1];

/* сохраняем файл на сервер */
file_put_contents($newFilerPath , file_get_contents($photoPathTG));

Скрипт для ответа на запросы через Хук

Получив представление о работе хуков, давайте теперь напишем полноценного бота, который будет отвечать на сообщения. Бот будет отвечать на текстовые сообщения, отправлять изображения по запросу и сохранять изображения пользователей.

Поехали…

Токен бота запишем в константу TG_TOKEN

define("TG_TOKEN", "5340791844:AAEXXDdu324vInvQrlWHyk8V91USOQSevrPVU");

Для удобства я создал специальные функции для отправки типовых запросов на сервер Telegram. Созданные функции принимают в качестве первого аргумента массив с параметрами запроса.

/* для отправки текстовых сообщений */
function TG_sendMessage($getQuery) {
    $ch = curl_init("https://api.telegram.org/bot". TG_TOKEN ."/sendMessage?" . http_build_query($getQuery));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_HEADER, false);
    $res = curl_exec($ch);
    curl_close($ch);

    return $res;
}

/* для отправки изображений */
function TG_sendPhoto($arrayQuery) {
    $ch = curl_init('https://api.telegram.org/bot'. TG_TOKEN .'/sendPhoto');
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $arrayQuery);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_HEADER, false);
    $res = curl_exec($ch);
    curl_close($ch);

    return $res;
}

/* для получения данных о файле */
function TG_getFile($arrayQuery) {
    $ch = curl_init("https://api.telegram.org/bot". TG_TOKEN ."/getFile?" . http_build_query($arrayQuery));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_HEADER, false);
    $res = curl_exec($ch);
    curl_close($ch);

    return $res;
}

Так же я добавил дополнительную функцию, которая выводит список всех файлов из директории. Данная функция будет отдавать массив с файлами, для последующего отправления рандомного файла в чат.

function list_files($path) {
    if ($path[mb_strlen($path) - 1] != '/') {
	$path .= '/';
    }
 
    $files = array();
    $dh = opendir($path);
    while (false !== ($file = readdir($dh))) {
	if ($file != '.' && $file != '..' && !is_dir($path.$file) && $file[0] != '.') {
	    $files[] = $file;
	}
    }
 
    closedir($dh);
    return $files;
}

Ниже напишем конструкцию, которую мы использовали для отлова хуков. Бот будет сразу отвечать на команды, поэтому нам не нужно записывать информацию в лог файл.

В переменные $textMessage записывает текст сообщения, а в переменную $chatId записываем id чата.

$data = file_get_contents('php://input');

$arrDataAnswer = json_decode($data, true);
$textMessage = mb_strtolower($arrDataAnswer["message"]["text"]);
$chatId = $arrDataAnswer["message"]["chat"]["id"];

Ниже мы проверяем наличие файла в сообщение. Если пользователь отправил файл, то мы его сохраняем в папку с картинками.

Здесь желательно прописать более сложный обработчик для проверки типа файла, но сейчас, чтобы не затягивать видео, я просто буду проверять наличие файла в сообщение.

if(!empty($arrDataAnswer["message"]["photo"])) {
    $documentData = array_pop($arrDataAnswer["message"]["photo"]);
}
else if(!empty($arrDataAnswer["message"]["document"])) {
    $documentData = array_pop($arrDataAnswer["message"]["document"]);
}

Далее мы прописываем проверку на текст сообщения и в случае нужного текста отправляем ответное сообщение.

Если пользователь отправил «Привет», то мы в ответ отправляем сообщение «Привет! Есть фото для меня?». Данное сообщение отправляется с помощью ранее созданной функции TG_sendMessage().

if($textMessage == 'привет') {
    $textMessage_bot = "Привет! Есть фото для меня";

    $arrayQuery = array(
	'chat_id' 		=> 1424646511,
	'text'			=> $textMessage_bot,
	'parse_mode'	=> "html",
    );
    TG_sendMessage($arrayQuery);
}

Ниже пропишем подобный код для запроса изображения. Если пользователь отправил «хочу фото», то мы выбираем рандомное изображение и отправляем его пользователю с помощью функции TG_sendPhoto().

else if($textMessage == 'хочу фото') {
    $textMessage_bot = "Вот, держи!";

    $listFile = list_files(__DIR__ . "/img/");

    $max = count($listFile) - 1;
    $randIdFile = rand(0, $max);

    $filePath = __DIR__ . "/img/" . $listFile[$randIdFile];

    $arrayQuery = array(
         'chat_id' => $chatId,
	  "photo" => new CURLFile($filePath),
	  "caption" => "Вот твоё фото!"
    );
    TG_sendPhoto($arrayQuery);

} 

Далее пропишем код для сохранения любых, отправленных в чат, изображений.

if(!empty($documentData)) {

    $arrayQuery = array(
	"file_id" => $documentData["file_id"],
    );
    $resultQuery = TG_getFile($arrayQuery);

    /* записываем ответ в формате PHP массива */
    $arrDataResult = json_decode($resultQuery, true);

    /* записываем URL необходимого изображения */
    $fileUrl = $arrDataResult["result"]["file_path"];

    /* формируем полный URL до файла */
    $photoPathTG = "https://api.telegram.org/file/bot". TG_TOKEN ."/" . $fileUrl;

    /* забираем название файла */
    $arrFilePath = explode("/", $fileUrl);
    $newFilerPath = __DIR__ . "/img/" . $arrFilePath[1];

    /* сохраняем файл на сервер */
    file_put_contents($newFilerPath , file_get_contents($photoPathTG));

    $arrayQuery = array(
	'chat_id' => 1424646511,
	'text' => "Отличное фото! Я его, пожалуй, сохраню",
	'parse_mode' => "html",
    );
    TG_sendMessage($arrayQuery);

}

Ну и на последок, давайте пропишем ещё 2 условия. Первое условие будет отправлять кнопку в чат, а второе условие будет проверять нажатие на кнопку и отправлять дополнительное сообщение.

Запрос для отправки кнопок создаём аналогично запросу со словом «Привет». По запросу мы будем отправлять 2 кнопки с callback_data — but_1 и but_2.

if($textMessage == 'отправь кнопки') {
    $textMessage_bot = "Вот твои кнопки! Нажимай";

    $arrayQuery = array(
	'chat_id' => 1424646511,
	'text'	=> $textMessage_bot,
	'parse_mode'	=> "html",
	'reply_markup' => json_encode(array(
	    'inline_keyboard' => array(
		array(
		    array(
			'text' => 'Кнопка 1',
			'callback_data' => 'but_1',
		    ),

		    array(
			'text' => 'Кнопка 2',
			'callback_data' => 'but_2',
		    )
		),
	    ),
	)),
    );  
    TG_sendMessage($arrayQuery);
}

Теперь давайте пропишем проверку нажатия на кнопки. Здесь нам нужно записать в переменную $dataBut код нашей кнопки, чтобы по нему в дальнейшем делать проверку. В переменную $textMessage и $chatId мы так же записываем текст сообщения и id пользователя, только в этот раз достаём эти данные из массива с ключом callback_query.

Ниже проверяем код нажатой кнопки и отправляем простое текстовое сообщение в ответ.

if($arrDataAnswer["callback_query"]) {
    $dataBut = $arrDataAnswer["callback_query"]["data"];
    $textMessage = mb_strtolower($arrDataAnswer["callback_query"]["message"]["text"]);
    $chatId = $arrDataAnswer["callback_query"]["message"]["chat"]["id"];

    if($dataBut == "but_1") {
	$arrayQuery = array(
	    'chat_id' => 1424646511,
	    'text' => "Ты нажал на 'КНОПКА 1'",
	    'parse_mode' => "html",
	);
	TG_sendMessage($arrayQuery);
    }
    else if($dataBut == "but_2") {
	$arrayQuery = array(
	    'chat_id' => 1424646511,
	    'text' => "Ты нажал на 'КНОПКА 2'",
	    'parse_mode' => "html",
	);
	TG_sendMessage($arrayQuery);
    }
}

Подведём итоги! В новом уроке, мы с вами научились обрабатывать запросы от Телеграма к серверу и прописали свой простой обработчик. Аналогичным образом вы можете прописать ответы на любые команды.

Так же, я хочу сказать, что в ближайшее время выйдет курс по разработке интернет-магазина в Telegram, разработанный на основе материалов из этого курса. Новый курс будет состоять только из практических материалов. По окончанию данного курса вы сможете создавать полноценные интернет-магазины в Telegram с каталогом, карточками товаров, корзиной и формой для оформления заказа. Все данные о заказах будут записываться в базу данных. По итогу вы сможете разработать отличное решение, которое легко продать как готовый кейс на фриланс биржах.

Скорее всего ссылка на данный курс уже находится в описание, поэтому переходите и изучайте данный материал.

Источник: https://habr.com/ru/articles/697016/


Интересные статьи

Интересные статьи

Вы когда-нибудь задавались вопросом: “Какая польза от yield в PHP?”. Позвольте мне избавить вас от поиска в Google; Я с удовольствием раскрою вам пару ключевых моментов о yield:
Это краткая заметка о культуре в разных кооперативах, в которых пришлось по работать (картинки из подобного места, хоть и не мои) Кратко о себе: 2 вышки - 1ая Учитель рис...
Во второй части цикла рассматривались основы языка программирования PowerShell, а сейчас стоит разобраться с использованием написанного на нем кода для задач администрирования. Самый очевидны...
Успех «Нетфликса» обеспечили высокие технологии, но за ними стоит целая философия, которая сделала эту философию эффективной. Систему, которая заставляет миллионы людей одержимо кликать по красно...
Telegram тестирует новый вариант обхода блокировок — маскировка трафика под обычный TLS (https). Предистория: Попытки заблокировать Telegram происходят в разных странах, первый вариант бло...