Материал является вольным переводом статьи Build A Telegram Bot with Laravel and BotMan с сайта scotch.io.
В данном материале для создания Telegram-бота мы используем известную библиотеку BotMan (botman.io).
На рисунке вы можете видеть результат работы созданного по этому материалу Telegram-бота. Здесь по команде /random бот вернул ссылку на случайную картинку собаки:
Естественно, кроме этой команды, бот будет понимать и другие.
Начнем.
В первую очередь установим Botman Studio. Если вы не знаете, что это, то знайте, Botman Studio – это стандартное Laravel-приложение с уже включенной в него библиотекой Botman. Так что если вы уже работали с Laravel ранее, то большинство действий из этого материала будет для вас знакомо.
А теперь продолжим, и создадим новый проект командой:
composer create-project --prefer-dist botman/studio ilovedogs
После того, как все установится проверьте работает ли оно. Для этого наберите в браузере: [адрес вашего сайта]/botman/tinker (например, у меня было так: site1.com/botman/tinker), и в результате вы должны увидеть следующую страницу:Здесь нас интересует первая ссылка Tinker. Перейдя по ней, вы увидите поле ввода, через которое можно «пообщаться» с ботом. Например, если ввести слово «Hi», в ответ вы получите «Hello!».Проверили, что все работает, можем продолжить.
В этом проекте нам нужно, чтобы бот отвечал на следующие типы команд:
- запрос ссылки на случайную картинку из всех пород собак (команда /random)
- запрос ссылки на случайную картинку с указанием породы (например, /b dachshund)
- запрос ссылки на случайную картинку с указанием породы и подпороды (например, /s hound:afghan)
Также добавим простую возможность диалога, где можно выбрать желаемое действие из нескольких вариантов (команда Start conversation):Кроме возможности диалога, нужно еще предусмотреть вариант, когда человек будет вводить какие-то неизвестные команды:Это весь функционал, который нам предстоит реализовать. Читайте дальше, и вы узнаете, как это сделать.
Запрос ссылки на случайную картинку из всех пород собак (команда /random)
Для того, чтобы получить случайную картинку собаки из всех пород собак пользователю нужно будет ввести команду /random. Научим наш бот правильно реагировать на эту команду.
Откроем файл routes/botman.php и добавим туда новую строчку:
$botman->hears('/random', 'App\Http\Controllers\AllBreedsController@random');
Далее создадим новый контроллер с помощью команды:
php artisan make:controller AllBreedsController
Его содержимое должно выглядеть следующим образом:
<?php
namespace App\Http\Controllers;
use App\Services\DogService;
use App\Http\Controllers\Controller;
class AllBreedsController extends Controller
{
/**
* Controller constructor
*
* @return void
*/
public function __construct()
{
$this->photos = new DogService;
}
/**
* Return a random dog image from all breeds.
*
* @return void
*/
public function random($bot)
{
// $this->photos->random() is basically the photo URL returned from the service.
// $bot->reply is what we will use to send a message back to the user.
$bot->reply($this->photos->random());
}
}
Здесь мы используем класс DogService (app/services/DogService.php), который будет отвечать за запросы к Dog API и возвращении полученной ссылки на картинку с собакой. Содержимое этого файла должно быть следующим:
<?php
namespace App\Services;
use Exception;
use GuzzleHttp\Client;
class DogService
{
// The endpoint we will be getting a random image from.
const RANDOM_ENDPOINT = 'https://dog.ceo/api/breeds/image/random';
/**
* Guzzle client.
*
* @var GuzzleHttp\Client
*/
protected $client;
/**
* DogService constructor
*
* @return void
*/
public function __construct()
{
$this->client = new Client;
}
/**
* Fetch and return a random image from all breeds.
*
* @return string
*/
public function random()
{
try {
// Decode the json response.
$response = json_decode(
// Make an API call an return the response body.
$this->client->get(self::RANDOM_ENDPOINT)->getBody()
);
// Return the image URL.
return $response->message;
} catch (Exception $e) {
// If anything goes wrong, we will be sending the user this error message.
return 'An unexpected error occurred. Please try again later.';
}
}
}
Запрос ссылки на случайную картинку с указанием породы (например, /b dachshund)
Добавим новую строчку в файл routes/botman.php:
$botman->hears('/b {breed}', 'App\Http\Controllers\AllBreedsController@byBreed');
Далее откроем контроллер AllBreedsController, который мы создали ранее и добавим в него новый метод:
/**
* Return a random dog image from a given breed.
*
* @return void
*/
public function byBreed($bot, $name)
{
// Because we used a wildcard in the command definition, Botman will pass it to our method.
// Again, we let the service class handle the API call and we reply with the result we get back.
$bot->reply($this->photos->byBreed($name));
}
Определим используемый здесь метод byBreed() в сервисном классе DogService, который мы создали ранее:
/**
* Fetch and return a random image from a given breed.
*
* @param string $breed
* @return string
*/
public function byBreed($breed)
{
try {
// We replace %s in our endpoint with the given breed name.
$endpoint = sprintf(self::BREED_ENDPOINT, $breed);
$response = json_decode(
$this->client->get($endpoint)->getBody()
);
return $response->message;
} catch (Exception $e) {
return "Sorry I couldn\"t get you any photos from $breed. Please try with a different breed.";
}
}
Также не забудьте добавить константу с api-ссылкой для получения картинки по названию породы в начало класса DogService:
// The endpoint we will hit to get a random image by a given breed name.
const BREED_ENDPOINT = 'https://dog.ceo/api/breed/%s/images/random';
Запрос ссылки на случайную картинку с указанием породы и подпороды (например, /s hound:afghan)
Добавим новую строчку в файл routes/botman.php:
$botman->hears('/s {breed}:{subBreed}', 'App\Http\Controllers\SubBreedController@random');
Далее создадим новый контроллер с помощью команды:
php artisan make:controller SubBreedController
Его содержимое должно быть следующим:
<?php
namespace App\Http\Controllers;
use App\Services\DogService;
use App\Http\Controllers\Controller;
class SubBreedController extends Controller
{
/**
* Controller constructor
*
* @return void
*/
public function __construct()
{
$this->photos = new DogService;
}
/**
* Return a random dog image from all breeds.
*
* @return void
*/
public function random($bot, $breed, $subBreed)
{
$bot->reply($this->photos->bySubBreed($breed, $subBreed));
}
}
Определим используемый здесь метод bySubBreed() в сервисном классе DogService, который мы создали ранее:
/**
* Fetch and return a random image from a given breed and its sub-breed.
*
* @param string $breed
* @param string $subBreed
* @return string
*/
public function bySubBreed($breed, $subBreed)
{
try {
$endpoint = sprintf(self::SUB_BREED_ENDPOINT, $breed, $subBreed);
$response = json_decode(
$this->client->get($endpoint)->getBody()
);
return $response->message;
} catch (Exception $e) {
return "Sorry I couldn\"t get you any photos from $breed. Please try with a different breed.";
}
}
Также не забудьте добавить константу с api-ссылкой для получения картинки по названию породы и подпороды в начало DogService:
// The endpoint we will hit to get a random image by a given breed name and its sub-breed.
const SUB_BREED_ENDPOINT = 'https://dog.ceo/api/breed/%s/%s/images/random';
Диалог с выбором желаемого действия (команда Start conversation)
В файле routes/botman.php уже есть строчка, отвечающая за начало диалога. Найдите метод hears() с параметром ‘Start conversation’ и замените второй параметр контроллера на следующий: ‘App\Http\Controllers\ConversationController@index’. У вас должно получиться так:
$botman->hears('Start conversation', 'App\Http\Controllers\ConversationController@index');
Далее, с помощью artisan-команды создайте новый контроллер:
php artisan make:controller ConversationController
Его содержимое должно быть следующим:
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Conversations\DefaultConversation;
class ConversationController extends Controller
{
/**
* Create a new conversation.
*
* @return void
*/
public function index($bot)
{
// We use the startConversation method provided by botman to start a new conversation and pass
// our conversation class as a param to it.
$bot->startConversation(new DefaultConversation);
}
}
Здесь мы используем класс DefaultConversation, файл для которого нужно создать в папке app/Conversations. Эта папка уже должна быть в структуре проекта, поэтому остается только создать в ней новый файл DefaultConversation.php. Его содержимое должно быть следующим:
<?php
namespace App\Conversations;
use BotMan\BotMan\Messages\Incoming\Answer;
use BotMan\BotMan\Messages\Outgoing\Question;
use BotMan\BotMan\Messages\Outgoing\Actions\Button;
use BotMan\BotMan\Messages\Conversations\Conversation;
class DefaultConversation extends Conversation
{
/**
* First question to start the conversation.
*
* @return void
*/
public function defaultQuestion()
{
// We first create our question and set the options and their values.
$question = Question::create('Huh - you woke me up. What do you need?')
->addButtons([
Button::create('Random dog photo')->value('random'),
Button::create('A photo by breed')->value('breed'),
Button::create('A photo by sub-breed')->value('sub-breed'),
]);
// We ask our user the question.
return $this->ask($question, function (Answer $answer) {
// Did the user click on an option or entered a text?
if ($answer->isInteractiveMessageReply()) {
// We compare the answer to our pre-defined ones and respond accordingly.
switch ($answer->getValue()) {
case 'random':
$this->say((new App\Services\DogService)->random());
break;
case 'breed':
$this->askForBreedName();
break;
case 'sub-breed':
$this->askForSubBreed();
break;
}
}
});
}
/**
* Ask for the breed name and send the image.
*
* @return void
*/
public function askForBreedName()
{
$this->ask('What\'s the breed name?', function (Answer $answer) {
$name = $answer->getText();
$this->say((new App\Services\DogService)->byBreed($name));
});
}
/**
* Ask for the breed name and send the image.
*
* @return void
*/
public function askForSubBreed()
{
$this->ask('What\'s the breed and sub-breed names? ex:hound:afghan', function (Answer $answer) {
$answer = explode(':', $answer->getText());
$this->say((new App\Services\DogService)->bySubBreed($answer[0], $answer[1]));
});
}
/**
* Start the conversation
*
* @return void
*/
public function run()
{
// This is the boot method, it's what will be excuted first.
$this->defaultQuestion();
}
}
Неизвестные команды
Осталось предусмотреть вариант, когда пользователь вводит какие-то неизвестные команды, которые бот не знает. Снова откроем файл routes/botman.php и добавим новую строчку:
$botman->fallback('App\Http\Controllers\FallbackController@index');
Создадим новый контроллер командой:
php artisan make:controller FallbackController
Его содержимое должно быть следующим:
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
class FallbackController extends Controller
{
/**
* Respond with a generic message.
*
* @param Botman $bot
* @return void
*/
public function index($bot)
{
$bot->reply('Sorry, I did not understand these commands. Try: \'Start Conversation\'');
}
}
На этом программистская часть закончена, осталось создать новый бот в Telegram, и привязать его к нашему проекту.
Установка Telegram-драйвера
В первую очередь нужно установить Telegram-драйвер в проект:
composer require botman/driver-telegram
Создание нового Telegram-бота
Открываем Telegram и через поиск ищем BotFather. После чего пишем: /newbot. Далее выбираем имя для бота (name), я использовал имя BotmanTest. Затем выбираем пользовательское имя (username), оно должно быть уникальным. После этого вы получите api-токен, который нужно указать в файле .env проекта, для этого добавьте следующую строчку в конец файла:
TELEGRAM_TOKEN=YOUR_TOKEN
И замените YOUR_TOKEN на полученный api-токен.
Открываем доступ извне к проекту с помощью ngrok
Чтобы не выкладывать свой тестовый проект на отдельный сервер в сети, воспользуемся утилитой ngrok. С помощью этой утилиты ваш проект на localhost станет доступен по отдельному адресу в Интернете. Если у вас еще не установлен ngrok, используйте официальную страницу (https://ngrok.com/download) для установки.
После того, как вы установите ngrok запустите Laravel-сервер:
php artisan serve
После чего перейдите в папку с ngrok и выполните команду:
./ngrok http 8000
Теперь ваш проект будет доступен из сети по адресам, которые указаны в строчках Forwarding:Для работы Telegram-бота нужен https-адрес, поэтому используйте его.
Связываем наш проект с Telegram
Для того, чтобы связать наш проект с Telegram, используйте Postman или CURL для выполнения следующей команды:
curl -X POST -F 'url=https://{YOU_URL}/botman' https://api.telegram.org/bot{TOKEN}/setWebhook
YOU_URL – https-адрес из ngrok; TOKEN – это TELEGRAM_TOKEN, который вы указали ранее в файле .env.
Если вы все сделали правильно, то команда должна вернуть следующий результат:
{
"ok": true,
"result": true,
"description": "Webhook was set"
}
На этом все, результат работы в Telegram приведен на картинке: