PHP
laravel
bot
LINEmessagingAPI

LineBotを作るときの雛形 for Laravel

はじめに

Laravel上にLINE Messaging APIを使ってよくBotを作るので、その雛形を公開します。

LINE Messaging APIについて

公式読むと何ができるか大体判ると思います。
公式ドキュメント

が、なぜか以前書いた記事への流入が未だにそこそこあるので、以下も参照すると良いのかも。
LINE Messaging APIでできることのまとめ

LINE Messaging APIのはじめかた

アカウントを作るのですが、自分で書く体力はないので、以下を見ながら作ってみてください。
判りやすいです。
1時間でLINE BOTを作るハンズオン (資料+レポート) in Node学園祭2017 #nodefest

無料アカウントでできる範囲は、相手からイベントを受け取ったときの応答のみです。
こちら(Bot)側からメッセージを送りたい場合は、有料アカウントを申し込む必要があります。
で、このページでは、一応その辺も扱います。

作るもの

  • 友達登録時にpushメッセージに必要なIDを取ってDBに保存&リプライを送る
  • メッセージを受信したら、何か送り返す(あんまり時間ないので、エコーするだけになるかも)

環境

OS:Windows10 Pro
PHP:7.1
Laravel:5.5

準備

  • LINE DevelopersサイトのMessaing APIのユーザーページで、
    Channel Secretとアクセストークンを控えておく

作ってみる

プロジェクト作成

コマンド
composer create-project --prefer-dist laravel/laravel line-bot-laravel-template

LINE-SDKを取込む

LINE SDKが提供されているので、composerで取り込む

コマンド
composer require linecorp/line-bot-sdk

DBの設定

とりあえずSQLiteを使うように設定する。
他のDB使う場合は、その設定でもOK。

.env
DB_CONNECTION=sqlite
その他のDB_XXXXは全部削除

database配下に、database.sqliteファイルを作る

.envファイルにChannel Secretとアクセストークンを書く

.env
LINE_CHANNEL_SECRET=XXXXXXXXXXXXXXXXXXXXXXXXXXX
LINE_ACCESS_TOKEN=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=

LINE Botのファサードを作る

めんどくさい手順があるわけではないですが、仕事で作るときって大抵めんどくさい手順がある&こういう外部ライブラリの使い方をメンバー全員が把握する必要もないため、簡略化するためにファサードで隠蔽します。

\App\Provider\AppServiceProvider
/**
 * Register any application services.
 *
 * @return void
 */
public function register()
{
    // LINE BOT
    $this->app->bind('line-bot', function ($app, array $parameters) {
        // $parametersを見て、SECRETとかTOKENをDBとかNoSQLから取ってくることが多い
        return new LINEBot(
            new LINEBot\HTTPClient\CurlHTTPClient(env('LINE_ACCESS_TOKEN')),
            ['channelSecret' => env('LINE_CHANNEL_SECRET')]
        );
    });

}
\App\Facades\LineBot
protected static function getFacadeAccessor()
{
    return 'line-bot';
}

DBマイグレーション

LINEで友達登録したユーザーを登録するテーブルを作る

コマンド
php artisan make:migration create_line_friends_table

中身は以下。
LINE IDの他にLINEの表示名も取って入れとく。

CreateLineFriendsTable
/**
 * Run the migrations.
 *
 * @return void
 */
public function up()
{
    Schema::create('line_friends', function (Blueprint $table) {
        $table->increments('id');
        $table->string('line_id', 64)->unique();
        $table->string('display_name', 64);
        $table->timestamps();
    });
}
コマンド
php artisan migrate

Eloquentモデル作成

今までネームスペース入れて生成できなかったと思ってたけど、できた。(昔からできた?確かめるのめんどいから誰か教えてください)

コマンド
php artisan make:model "Models\LineFriend"

fillableを設定しておく

App\Models\LineFriend
protected $fillable = ['line_id', 'display_name'];

コントローラ

やること

  • LINEサーバからのWebhookを受け取るコントローラ。
  • LINEサーバからのリクエストかを検査する。
  • webhookはイベントがまとめて送られてくる場合があるため、foreachで廻す必要がある。
    イベントは他にもありますが、ルーム用のものだったり、基本的に不要なので入れてないです。
  • イベントごとにサービスクラス生成してやりたい処理やらせれる。
  • 処理が終わったらいい加減に返信します。
\app\Http\Controllers\Api\LineBotController
/**
 * callback from LINE Message API(webhook)
 * @param Request $request
 * @throws \LINE\LINEBot\Exception\InvalidSignatureException
 */
public function callback(Request $request)
{

    /** @var LINEBot $bot */
    $bot = app('line-bot');

    $signature = $_SERVER['HTTP_'.LINEBot\Constant\HTTPHeader::LINE_SIGNATURE];
    if (!LINEBot\SignatureValidator::validateSignature($request->getContent(), env('LINE_CHANNEL_SECRET'), $signature)) {
        abort(400);
    }

    $events = $bot->parseEventRequest($request->getContent(), $signature);
    foreach ($events as $event) {
        $reply_token = $event->getReplyToken();
        $reply_message = 'その操作はサポートしてません。.[' . get_class($event) . '][' . $event->getType() . ']';

        switch (true){
            //友達登録&ブロック解除
            case $event instanceof LINEBot\Event\FollowEvent:
                $service = new FollowService($bot);
                $reply_message = $service->execute($event)
                    ? '友達登録されたからLINE ID引っこ抜いたわー'
                    : '友達登録されたけど、登録処理に失敗したから、何もしないよ';

                break;
            //メッセージの受信
            case $event instanceof LINEBot\Event\MessageEvent\TextMessage:
                $service = new RecieveTextService($bot);
                $reply_message = $service->execute($event);
                break;

            //位置情報の受信
            case $event instanceof LINEBot\Event\MessageEvent\LocationMessage:
                $service = new RecieveLocationService($bot);
                $reply_message = $service->execute($event);
                break;

            //選択肢とか選んだ時に受信するイベント
            case $event instanceof LINEBot\Event\PostbackEvent:
                break;
            //ブロック
            case $event instanceof LINEBot\Event\UnfollowEvent:
                break;
            default:
                $body = $event->getEventBody();
                logger()->warning('Unknown event. ['. get_class($event) . ']', compact('body'));
        }

        $bot->replyText($reply_token, $reply_message);
    }
}

サービス

各イベントの処理クラスを作ります。
とりあえず以下の3点を実装。
* フォロー
* メッセージの受信
* 位置情報の受信

App\Services\Line\Event
/**
 * 登録
 * @param FollowEvent $event
 * @return bool
 * @throws \Illuminate\Database\Eloquent\MassAssignmentException
 */
public function execute(FollowEvent $event)
{
    try {
        DB::beginTransaction();

        $line_id = $event->getUserId();
        $rsp = $this->bot->getProfile($line_id);
        if (!$rsp->isSucceeded()) {
            logger()->info('failed to get profile. skip processing.');
            return false;
        }

        $profile = $rsp->getJSONDecodedBody();
        $line_friend = new LineFriend();
        $input = [
            'line_id' => $line_id,
            'display_name' => $profile['displayName'],
        ];

        $line_friend->fill($input)->save();
        DB::commit();

        return true;

    } catch (Exception $e) {
        logger()->error($e);
        DB::rollBack();
        return false;
    }
}

LINEの全てのEventはBaseEventを継承しているので、getUserIdするとLINE IDが取れます。
このLINE IDは内部的なものなので、今回のBOTやLINEログインを使わないと取れないです。
なので、複合的なサービスでLINEと連携したい&LINE IDが欲しい場合は、こういうものを用意する必要があります。
まぁたとえLINE IDを入手したとしても、LINEで友達になってないと、メッセージは相手に届かないですが。

他のイベントは特に特記事項ないので、ぎっはぶ見ていただければ~。

ルーティング

webhookのURLを追加

api.php
Route::group(['namespace' => 'Api'], function () {
    Route::post('/line/callback', 'LineBotController@callback')->name('line.callback');
});

実装はこんな感じです。簡単。

使ってみる

Laravelは組込みサーバがあるので、それを使います。
LINEからの受付はサーバーが公開されている必要がありますが、ローカルで試すのにそんなもの用意するのめんどくさいのでngrok使ってTCPトンネリングさせて公開します。
ngrokなにそれ?な方は以下を見てみてください。

これで無駄な社内手続きともおさらばできる!?ngrokを使って超簡単に公開サーバを手に入れる

コマンド
php artisan serve
ngrok http localhost:8000

image.png

ngrok起動するとこんな感じに出てくるので、Forwardingのhttps://以降をコピペして、
LINE DevelopersサイトのLine Messaging APIユーザーのwebhook URLにコピペします。
今回だと以下のようになります。

image.png

辺なの出ていますが、WIndowsで疎通するとUnknownEventでこけるので、こんな感じになってますw
Linuxだとちゃんと動きます。

これで、LINEで友達追加してあげれば、自分で作ったBotが動くことが確認できると思います。

どんなBotにするかはプロジェクト次第なので、やりたいことに合わせて実装する感じですね。

おまけ

こっちからメッセージを送信する

LINEの送信はpushメッセージとmulticastっていう複数人に送り付けるものとがあります。
無料枠だとできないですが、有料枠のアカウントがあると使えるようになるので色々遊べます。
(個人には高いので、会社が契約していると助かりますね。)
ちなみに、こっちから送るだけなので、サーバーが公開されている必要もなく、LINE IDさえ分かってて友達同士なら一方的にメッセージを投げつけられます。

Push.php
$bot = app('line-bot');
$textMessageBuilder = new \LINE\LINEBot\MessageBuilder\TextMessageBuilder('送信');
$response = $bot->pushMessage($line_id, $textMessageBuilder);
Multicast.php
$bot = app('line-bot');
$textMessageBuilder = new \LINE\LINEBot\MessageBuilder\TextMessageBuilder('一斉送信');
$response = $bot->multicast($line_id_list, $textMessageBuilder);

最後に

冬休みの宿題的な感じで、自分の雛形実装を通してLINE Botをさらっと説明してみました。
ここからどのように実装していくかは結構大変だと思いますが、LINE SDKが使いやすいおかげで疎通するだけだと簡単です。
ここでは触れてませんが、LINEビーコンのイベントなんかも拾えるので、LineBotは面白い世界です。

これ見て、LineBot簡単そうだからはじめてみようと立ち上がる人が居たら嬉しいです。

ソースは以下に上げました。
https://github.com/sh-ogawa/line-bot-laravel-template