Laravel透過Line Message API實做echo bot

2020/06/14

建立Provider

前往Line Develop Console頁面

點選New Provider

Screenshot_20200614_122954.png

接著選擇Create Message API Channel

Screenshot_20200614_123225.png

 

設定好一些Bot的基本資料

像是名稱、大頭貼、Channel名稱、敘述等

Screenshot_20200614_123507.png

 

設定Webhook

Screenshot_20200614_124450.png

因為Webhook URL必須是對外網址而且是HTTPS

如果在local開發可使用ngrok

 

將Line Developer Console的Channel Access TokenChannel Secret保留下來

 

先測試Webhook是否能通過驗證

可以先開一支api回傳http status code 200

並從Line Developer Console上點選Webhook Verify按鈕

確認此api是否能通

 

另外可以先log出請求的格式先看個大概

<?php

namespace Modules\Line\Http\Controllers;
use Illuminate\Http\Request;
use Modules\Base\Http\Controllers\BaseController;

class LineHookController extends BaseController
{
    public function hooks(Request $request)
    {
        $params = $request->all();
        logger(json_encode($params, JSON_UNESCAPED_UNICODE));
        return response('hello world', 200);
    }
}

 

log出來的請求參數

可以看到就是長的像官方文件的Webhook event object這樣

Screenshot_20200614_163534.png

 

驗證請求

Screenshot_20200614_154821.png

到目前為止

這隻接收webhook的api只要成功發佈出去

已經人讓任何人來call了

因此我們必須依照文件的signature validation建議來驗證所有來的請求

上面也有各種語言的範例

 

不過因為我是使用Line官方的PHP SDK line-bot-sdk-php

SDK驗證的部份都包好而且還有範例

所以可以很簡單的來做驗證

<?php

namespace Modules\Line\Http\Controllers;
use Illuminate\Http\Request;
use LINE\LINEBot;
use Modules\Base\Http\Controllers\BaseController;
use LINE\LINEBot\HTTPClient\CurlHTTPClient;
use Modules\Line\Constant\LineHookHttpResponse;

class LineHookController extends BaseController
{
    public function hooks(Request $request)
    {
        $httpClient = new CurlHTTPClient(env('LINE_CHANNEL_ACCESS_TOKEN'));
        $bot = new LINEBot($httpClient, [
            'channelSecret' => env('LINE_CHANNEL_SECRET')
        ]);

        $signature = $request->header(LINEBot\Constant\HTTPHeader::LINE_SIGNATURE);
        if(!$signature) {
            return $this->http403(LineHookHttpResponse::SIGNATURE_INVALID);
        }

        try {
            $bot->parseEventRequest($request->getContent(), $signature);
        } catch (LINEBot\Exception\InvalidSignatureException $exception) {
            return $this->http403(LineHookHttpResponse::SIGNATURE_INVALID);
        } catch (LINEBot\Exception\InvalidEventRequestException $exception) {
            return $this->http403(LineHookHttpResponse::EVENTS_INVALID);
        }

        $events = $request->events;
        foreach ($events as $event) {
           logger(json_encode($event));
        }
        return $this->http200('anchor');
    }
}

 

驗證這段寫完之後

記得要再點選Webhook Verify按鈕確認Code是否有問題

 

實做接收訊息並回覆功能

到目前為止我們已經能夠安全的接收到訊息了

因此我們可以將Webhook event object物件加以利用

這時候可以透過Message event的replyToken

丟給透過的SDK建立出來的$bot物件來發送訊息

 

先設定一個假設情境

當我們收到訊息內容有"台灣"的時候

系統要自動回覆"南波萬"

 

<?php

namespace Modules\Line\Http\Controllers;
use Illuminate\Http\Request;
use LINE\LINEBot;
use Modules\Base\Http\Controllers\BaseController;
use LINE\LINEBot\HTTPClient\CurlHTTPClient;
use Modules\Line\Constant\LineHookHttpResponse;

class LineHookController extends BaseController
{
    public function hooks(Request $request)
    {
        $httpClient = new CurlHTTPClient(env('LINE_CHANNEL_ACCESS_TOKEN'));
        $bot = new LINEBot($httpClient, [
            'channelSecret' => env('LINE_CHANNEL_SECRET')
        ]);

        $signature = $request->header(LINEBot\Constant\HTTPHeader::LINE_SIGNATURE);
        if(!$signature) {
            return $this->http403(LineHookHttpResponse::SIGNATURE_INVALID);
        }

        try {
            $bot->parseEventRequest($request->getContent(), $signature);
        } catch (LINEBot\Exception\InvalidSignatureException $exception) {
            return $this->http403(LineHookHttpResponse::SIGNATURE_INVALID);
        } catch (LINEBot\Exception\InvalidEventRequestException $exception) {
            return $this->http403(LineHookHttpResponse::EVENTS_INVALID);
        }

        $events = $request->events;
        foreach ($events as $event) {
            // 不是訊息的event先不處理
            if($event['type'] != 'message') continue;
            $messageType = $event['message']['type'];
            $message = $event['message']['text'];

            // 不是文字訊息的類型先不處理
            if($messageType != 'text') continue;
            $match = preg_match('/台灣|臺灣|Taiwan|taiwan/', $message);
            if(!$match) continue;
            $response = $bot->replyText($event['replyToken'], '南波萬');
            if ($response->isSucceeded()) {
                logger('reply successfully');
                return;
            }
        }
        return $this->http200('anchor');
    }
}

 

測試一下

12142767169460.jpg

 

上面這段示範先用SDK的簡單的文字訊息來回應($bot->replyText)

如果要回覆其他類型的訊息(可參考文件的Message Object)

像是影片、地標、貼圖

透過SDK的各種MessageBuilder也能做到

 

做更複雜的訊息回覆

如果透過上述的一般MessageBuilder Class

一次只能回覆一筆訊息

當你回覆第二次會發現Line Server告訴你replayToken已經失效

 

因此如果我們希望系統能夠一次回覆多個訊息

可使用LINEBot\MessageBuilder\MultiMessageBuilder這個Class

透過它的add Method可以一次塞入多個MessageBuilder Class

最後將再將MutliMessageBuilder Class丟給$bot->replyMessagemethod

<?php

namespace Modules\Line\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use LINE\LINEBot;
use Modules\Base\Http\Controllers\BaseController;
use LINE\LINEBot\HTTPClient\CurlHTTPClient;
use Modules\Line\Constant\LineHookHttpResponse;

class LineHookController extends BaseController
{
    public function hooks(Request $request)
    {
        $httpClient = new CurlHTTPClient(env('LINE_CHANNEL_ACCESS_TOKEN'));
        $bot = new LINEBot($httpClient, [
            'channelSecret' => env('LINE_CHANNEL_SECRET')
        ]);

        $signature = $request->header(LINEBot\Constant\HTTPHeader::LINE_SIGNATURE);
        if(!$signature) {
            return $this->http403(LineHookHttpResponse::SIGNATURE_INVALID);
        }

        try {
            $bot->parseEventRequest($request->getContent(), $signature);
        } catch (LINEBot\Exception\InvalidSignatureException $exception) {
            return $this->http403(LineHookHttpResponse::SIGNATURE_INVALID);
        } catch (LINEBot\Exception\InvalidEventRequestException $exception) {
            return $this->http403(LineHookHttpResponse::EVENTS_INVALID);
        }

        $events = $request->events;
        foreach ($events as $event) {
            // 不是訊息的event先不處理
            if($event['type'] != 'message') continue;
            $messageType = $event['message']['type'];
            $message = $event['message']['text'];

            // 不是文字訊息的類型先不處理
            if($messageType != 'text') continue;
            $match = preg_match('/台灣|臺灣|Taiwan|taiwan/', $message);
            if(!$match) continue;

            $messageBuilder = new \LINE\LINEBot\MessageBuilder\MultiMessageBuilder();

            // 回覆文字
            $text = new LINEBot\MessageBuilder\TextMessageBuilder('南波萬');
            $messageBuilder->add($text);

            // 回覆貼圖
            $sticker = new LINEBot\MessageBuilder\StickerMessageBuilder('11537', '52002734');
            $messageBuilder->add($sticker);

            // 回覆地標
            $location = new LINEBot\MessageBuilder\LocationMessageBuilder('台灣南波萬', '哇呆灣郎啦', '24.147666', '120.673552');
            $messageBuilder->add($location);

            // 回覆相片訊息
            $image = new LINEBot\MessageBuilder\ImageMessageBuilder(
                'https://foobar.png',
                'https://foobar.tiny.png'
            );
            $messageBuilder->add($image);

            $response = $bot->replyMessage($event['replyToken'], $messageBuilder);
            if ($response->isSucceeded()) {
                logger('reply sticker successfully');
            }

            else {
                Log::warning($response->getRawBody());
                Log::warning('reply sticker failure');
            }
        }
        return $this->http200('anchor');
    }
}

 

測試結果

12142978344247.jpg