如何使用Laravel和BotMan构建Telegram机器人

来自菜鸟教程
跳转至:导航、​搜索

介绍

自动化机器人是一种根据用户请求向用户提供定制数据的方式。 Laravel 和 Botman 框架提供了创建有用机器人的工具。 在本教程中,您将使用 Dog API 为爱狗人士创建一个 Telegram 机器人,如下所示:

安装 Laravel 和 Botman

创建这个 Bot 的第一步是安装 LaravelBotman。 但在此之前,让我们先快速了解一下 Botman 是什么以及它是如何工作的:

BotMan 是一个与框架无关的 PHP 库,旨在简化为多个消息传递平台(包括 Slack、Telegram、Microsoft Bot Framework、Nexmo、HipChat、Facebook Messenger 和微信)开发创新机器人的任务。

$botman->hears('Bot, What’s the best Web Development training website?', function (BotMan $bot) {
    $bot->reply('DigitalOcean for sure. Give me a harder question!!');
});

安装 Botman Studio

Marcel Pociot,Botman 的创建者,已经通过创建 Botman Studio 为我们节省了一些时间,这是一个与 Botman 和其他软件一起使用的即用型和最新的 Laravel 应用程序包括测试工具(稍后介绍)。

继续创建一个新项目:

composer create-project --prefer-dist botman/studio ilovedogs

现在我们已经全新安装了 Laravel 和 Botman,让我们检查一下是否一切正常。 打开终端并运行以下命令:

php artisan botman:tinker

如果您输入“Hi”并且 Bot 回复“Hello”,那么您就可以开始了。

创建命令

我们的 Bot 应该能够响应不同的消息类型,这是我们将要实现的功能的列表,但当然您可以随时添加您希望 Bot 收听的任何其他命令:

  • 发送所有品种的随机狗照片。
  • 按品种发送随机狗照片。
  • 按品种和子品种发送随机狗照片。
  • 进行对话并提供帮助。
  • 响应无法识别的命令。

让我们清除routes/botman.php文件,从头开始。

发送所有品种的随机狗照片

为了从 Bot 接收随机的狗照片,您必须向它发送 /random,这就是我们告诉它响应该确切命令的方式:

在您的 routes/botman.php 文件中:

<?php

use App\Conversations\StartConversation;

$botman = resolve('botman');

$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) 的实例,它将负责对我们的端点进行 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 {breed} 和上面一样,我们打开 routes/botman.php 文件并通过添加以下行告诉 Bot 侦听该命令:

$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.";
    }
}

并且不要忘记将上面使用的端点 const 添加到同一个文件中:

// 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 {breed}:{subBreed}

$botman->hears('/s {breed}:{subBreed}', 'App\Http\Controllers\SubBreedController@random');

创建控制器:

php artisan make:controller SubBreedController

我们将定义 random 方法,如下所示:

<?php

namespace App\Conversations;

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));
    }
}

我们将所需的端点和方法添加到我们的 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';
/**
 * 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.";
    }
}

进行对话并提供帮助

对话是您在构建机器人时通常使用的,文档将其描述为:

对于聊天机器人,您可能不想简单地对单个关键字做出反应,而是可能需要使用对话从用户那里收集信息。 假设您希望您的聊天机器人为您的应用程序用户提供优雅的用户入职体验。

让我们通过将此行添加到我们的 routes/botman.php 文件来创建我们的对话:

$botman->hears('Start conversation', 'App\Http\Controllers\ConversationController@index');

生成控制器:

php artisan make:controller ConversationController

并在该类中定义一个 index 方法:

<?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);
    }
}

如果您使用的是 Botman Studio,您应该已经在 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();
    }
}

响应无法识别的命令:

最后,我们需要让用户知道他们何时发送了我们的 Bot 无法识别的消息,我们可以通过使用回退方法来做到这一点。 打开你的 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 集成了。 为此,我们需要拉取 Botman 提供的 Telegram 驱动程序:

composer require botman/driver-telegram

创建电报机器人

我们成功地创建了我们的机器人,定义了命令并对其进行了测试,现在是时候创建一个 Telegram 机器人了。 打开应用程序并搜索 BotFather,输入 /newbot,输入您的机器人的名称和用户名,一切顺利。

将此添加到您的 .env 文件中,并将 YOUR_TOKEN 替换为 Telegram 给您的令牌:

TELEGRAM_TOKEN=YOUR_TOKEN

安装和运行 ngrok

因为 Telegram 需要一个有效且安全的 URL 来设置 webhook 并接收来自您的用户的消息,我们将使用 ngrok,或者您可以将您的应用程序部署在服务器上并设置 SSL 证书,但对于演示,我们将坚持使用 ngrok。 向他们的 下载页面 鞠躬,然后单击与您的操作系统匹配的下载按钮。

现在 cd 进入您的应用程序文件夹并运行 php artisan serve

是时候运行 ngrok,cd 到 ngrok 所在的文件夹并运行 ./ngrok http 8000

将机器人链接到 Telegram

最后一步是将我们的应用程序链接到我们之前创建的 Telegram Bot,为此我们将向该 URL 发出 POST 请求并传递为我们生成的 URL ngrok:

 https://api.telegram.org/bot{TOKEN}/setWebhook

您可以通过运行以下命令使用 Postman 或 CURL 执行此操作:

curl -X POST -F 'url=https://{YOU_URL}/botman' https://api.telegram.org/bot{TOKEN}/setWebhook

如果你正确地做到了,你应该会收到这个准确的 JSON 响应:

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

在 Telegram 上进行测试

  • 发送所有品种的随机狗照片:
  • 按品种发送随机狗照片:
  • 按其品种和子品种发送随机狗照片:
  • 进行对话并提供帮助:
  • 响应无法识别的命令:

结论

我希望您发现本教程很有用,如果您跟随并创建了自己的 Bot。