На данную тему написано много статей на Хабре и просто в интернете. И я расскажу о своем опыте работы с телеграм ботом и моментами, которые «в лоб» не удалось решить.

Создание бота


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

Если в описанной инструкции ничего не понятно, то вот пошаговая инструкция
  1. находим в телеграм бота BotFather и добавляем себе в контакт лист
  2. смотрим доступные команды бота с помощью команды /help


  3. выбираем /newbot и далее, следуя инструкции, выполняем необходимые действия (следующая картинка взята из google)




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

Связываем бота с приложением\сайтом


Начинается самое интересное, а также именно тут я столкнулся с первой проблемой.
Первым делом выбираем библиотеку на php по созданию бота. Я свой выбор остановил на этой библиотеке, так как мне она показалась самой удобной.

После подключения библиотеки нужно организовать взаимодействие бота с вашим сайтом\приложением. Организовать это взаимодействие можно с помощью вебхуков.

Вебхук — это своего рода ретранслятор, который все запросы от бота будет передавать на адрес, указанный при регистрации вебхука. Зарегистрировать вебхук очень просто, нужно просто отправить запрос вида https: //api.telegram.org/bot~token~/setWebhook?url=https: //example.ru/path, где
https: //example.ru/ — это ссылка на ваш сайт, куда будет перенаправлять бот запросы.
~token~ — это токен, который вы получили при регистрации своего бота.
path — это часть url, на которую будут приходить обращения.

И вот именно тут возникает проблема. Оказывается вебхук можно зарегистрировать только в случае, если сайт находится на https. Если же ваш сайт на http, то зарегистрировать вебхук вам не получится.

Но выход есть, если вы не хотите покупать сертификат
Вы можете воспользоваться сервисом Let’s Encrypt
Переходим в раздел getting startted и следуем инструкции.

Написание кода бота


Теперь же приступаем к программированию. После того, как взаимосвязь организована, можно начинать писать логику нашего бота.

Разработчики telegram, для того чтобы пользователям было проще работать с ботами, просят всех разработчиков реализовывать поддержку следующих команд:

/start — начинает общение с пользователем (например, отправляет приветственное сообщение). В эту команду также можно передавать дополнительные аргументы.
/help — отображает сообщение с помощью по командам. Оно может представлять собой короткое сообщение о вашем боте и список доступных команд.

Все что нам нужно сделать, это в контроллере вашего приложения\сайта написать следующий код:

$token = "токен";
$bot = new \TelegramBot\Api\Client($token);
// команда для start
$bot->command('start', function ($message) use ($bot) {
    $answer = 'Добро пожаловать!';
    $bot->sendMessage($message->getChat()->getId(), $answer);
});

// команда для помощи
$bot->command('help', function ($message) use ($bot) {
    $answer = 'Команды:
/help - вывод справки';
    $bot->sendMessage($message->getChat()->getId(), $answer);
});

$bot->run();

Теперь если в окне телеграм бота написать /help, то будет выведен текст:

Команды:
/help — вывод справки


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

Так как команды могут принимать аргументы, то мы эту возможность используем. Например, мы сделаем так, чтобы наш бот, на команду hello Вася, отвечал сообщением: Привет, Вася.

Для этого следует написать следующий код:

$bot->command('hello', function ($message) use ($bot) {
    $text = $message->getText();
    $param = str_replace('/hello ', '', $command);
    $answer = 'Неизвестная команда';
    if (!empty($param))
    {
    	$answer = 'Привет, ' . $param;
    }
    $bot->sendMessage($message->getChat()->getId(), $answer);
});

Получается все очень просто и быстро.

Заносим список команд бота


Для того, чтобы бот мог выдавать интерактивную справку



необходимо боту BotFather сообщить список команд.
Сделать это можно с помощью его команды /setcommands

Велосипедство


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

Значит нужно сделать так, чтобы бот запоминал команду, которую вы вводите. Это можно сделать с помощью любого хранилища (MySQL, memcached, redis, tarantool, Postgres, etc)
Все что нужно, это запоминать предыдущий ввод пользователя.

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

Вот на этом этапе у меня снова возникла трудность, так как я не нашел в библиотеке возможности получить команду до вызова метода command. Также мне не понравилось то, что вся логика будет заперта в рамках одного контроллера приложения.

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

Для начала мы описываем точку входа в контроллер

function main()
{
	$telegram = new Telegram();
	// подключаем хранилище
	$storage = new Storage();
	// получаем объект сообщения
	$message = $telegram->getMessage();
	// получаем команду, если ее передали
	$command = $message->getCommand();
	// получаем текст, если его передали (тут лежит все, что не является командой)
	$text = $message->getParams($command);
	// если команда пустая, то мы проверяем, есть ли у пользователя на предыдущем шаге вызов команды и восстановливаем ее
	if (empty($command))
	{
	  $command = $storage->restoreCommand($message->getChat()->getId());
	}
	// запоминаем команду, котрую ввел пользователь
	$storage->storeCommand(
	    $message->getChat()->getId(),
	    $command
	);
	// логика подключения нашего метода для котроллера
	$this->chooseMethod($command, $message, $text);
}

Теперь рассмотрим один из методов.

function getnewtext(Message $telegram, $text)
{
	// если не передали текст, то выведем сообщение с разъяснением
	if (empty($text))
	{
	  $answer = "Введите слово или несколько слов для поиска. Именно по ним будет происходить поиск 5 свежих новостей.";
	  $telegram->sendMessage($telegram->getChat()->getId(), $answer);
	}
	else
	{
		// основаня логика
		$tgNews = new TelegramNews();
		$arData = $tgNews->getByWord($text, 'new');
		if (empty($arData))
		{
			$answer = 'Ничего не найдено';
		}
		else
		{
			$answer = common_setViewGetContent('telegram/get', [
			    'data' => $arData
			]);
		}
		$telegram->sendMessage($telegram->getChat()->getId(), $answer);
	}
}

Так стало все в разы приятнее, интерактивнее и удобнее.



Спасибо, что дочитали статью до конца. Поиграть с живым ботом, который работает в режиме диалога можно тут.

UPD: боту добавлена возможность отдавать фото на некоторые виды запросов. Например на
/gettable — возвращает результирующую таблицу спортивных событий
/getevents — возвращает события спортивных мероприятий

Комментарии (12)


  1. L0NGMAN
    27.01.2018 22:05

    Почему не выбрали эту библиотеку? github.com/php-telegram-bot/core
    И подскажите чем удобнее тот который выбрали, что бы учесть и исправить


    1. BoShurik
      27.01.2018 23:02

      Не скажу за автора, но я отказался из-за странной системы команд: невозможно пробросить свои зависимости в конструктор, как следствие — сложно интегрировать с существующим сайтом/приложением. Это больше похоже на фреймворк для создания бота, чем на библиотеку :)


      1. L0NGMAN
        27.01.2018 23:06

        Спасибо. Есть в планах и надеюсь до стабильной версии успеем рефакторить. Хочу внести DI. Если есть ещё советы, буду рад услишать


        1. ilyaplot
          29.01.2018 10:38

          А вы имеете какое-то отношение к этому репозиторию? Я в данный момент в свободное время добавляю Doctrine для работы не только с MySQL, но и с PostgreSQL, например.


          1. CybernatiC
            29.01.2018 17:43

            Он есть один из разработчиков


          1. L0NGMAN
            30.01.2018 11:22

            Да, я основатель проекта и один из мейнтеинеров. Правда последнее время я очен занят другими проектами но скоро планирую возобновить активную работу над проектом


      1. PQR
        28.01.2018 00:48

        Точно такая же история — сначала взял эту библиотеку, но потом запарился вписывать свою логику в предполагаемую систему команд, хочется более гибкую/простую библиотеку, без навязанной архитектуры


    1. vitalykhy Автор
      28.01.2018 11:51

      Изначально мне казалось, что в этой библиотеке очень просто и удобно реализовать логику. То есть написал функцию на определенную команду и описал внутри весь функционал. Но когда понадобилось дописать общую логику для всех команд — возникли проблемы (ниже по тексту комментаторы все верно отписали BoShurik PQR )

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

      PS: на самом деле описать свою логику очень просто (я имею ввиду библиотеку для телеграм). Самый простой вариант на коленке займет около часов 2. Просто посмотреть вывод php://input от телеграм и описать логику структуры. Далее можно использовать наброски кода, что я привел в статье в главе Велосипедство.


      1. BoShurik
        28.01.2018 12:33

        У меня в свое время получилось что-то вроде этого: https://github.com/BoShurik/telegram-bot-example. Отличие в том что я запоминаю не только команду, но и данные которые ввел пользователь, т.о. можно, например, заполнить заявку пошагово


    1. opiy
      29.01.2018 10:04

      Есть еще одна либа, которая сразу приходит на ум botman.io


    1. PaulZi
      29.01.2018 16:26

      Ещё бесит в вашей либе, что сообщения отправляются статично без привязки к конкретному боту, т. е. `Request::sendPhoto()`, то есть одновременно двух ботов в коде нельзя держать. Это очень печально. Раз мы создаём экземпляр бота, отправляться должно через этот экземпляр:
      ```php
      $bot = new Longman\TelegramBot\Telegram($bot_api_key, $bot_username);
      $bot->sendPhoto();
      ```


  1. ETCDema
    28.01.2018 19:52

    Хорошее начало, но я бы рекомендовал при усложнении логики бота вместо реализации на контроллерах использовать State Machine т.к. в этом случае можно будет делать более сложную логику общения, реагировать на inline buttons в сообщениях и изменять/дополнять отдельные сообщения, хранить состояние не только в рамках чата/отдельного пользователя, но и отдельных сообщений и работать как через вебхуки, так и через опрос обновлений (отделить способ доставки обновлений от логики обработки).