LaravelでLINEにチャットボットをつくる(QRコード作成)

さてさて、このところ「PC+リアルな機械」を使った記事をいくつか公開してきました。

実を言うとまだ衝動買いしてしまったパソコン機器がいくつか残っているので、それらを使った記事も書いていこうとは思っているのですが、さすがにちょっと同じテーマが続きすぎかなと感じていたので、今回は一切パソコン機器を使わないデジタル・オンリーな記事をお届けしたいと思います。

ということで今回の内容ですが、これも機会があればとずっと考えていた「LINEのチャットボット」Laravelで作ってみます。

正直なところLINEはスマホ・ユーザーならほぼ全ての人が使っているといってもいいアプリなので、もしかすると「いまさら感」があるかもしれませんが、作成する内容で便利なサービスが提供できると思います。

そして、今回開発する機能は、

「QRコード・ジェネレータ」

です。

そうです。
LINEにメッセージを送信するとQRコードの画像つくって返信してくれるというものです。

できればLINEが使える学生の皆さんに夏休みの自由研究なんかで使ってもらえると嬉しいです(私は、はじめはオールコピペ賛成派です。感動が学習意欲をかき立てるはずなので😊✨)

「自由研究って楽しいですよね」

開発環境: Laravel 5.8

先に試してみる

今回実際に開発したLINEボットを以下から試すことができます。

なお、今後もこのボットは公開し続けるので良かったら今後も使っていってください。(人気がなかったらこっそり終了するかもですが・・・😅)

【QRコードをつくる and 読む】

LINE Messaging APIを使えるようにする

LINE Developersにアカウントを作る

LINEでBOTに必要なLINE Messaging APIを使えるようにするために、まずLINE Developersにアカウントを作成しましょう。

まず、LINE Developersにアクセスして画面右上にある「ログイン」ボタンをクリックします。

すると、ログインページが表示されます。
今回は(すでにお持ちの)LINEのアカウントを使ってログインしますので、「LINEアカウントでログイン」ボタンをクリック。

すると、以下のようなフォームが表示されますが、今回はQRコードでログインしますので、「QRコードログイン」をクリック。

ボタンをクリックするとQRコードが表示されるので、(どのアプリでもいいので)QRコードを読みとり、取得されたURLにアクセスします。

するとLINEのアプリが起動し、認証コードを入力するよう求められますのでPC側に表示されるコードを入力して自動ログインしましょう。

プロバイダーを登録する

ログインしたらページ右上に表示されている「新規プロバイダー作成」ボタンをクリック。(ちなみにプロバイダーとは、サービスの提供者のコトです)

すると以下のフォームが表示されるので、「開発者名」「メールアドレス」を入力して「この内容で作成する」ボタンをクリック。

続いて確認画面になるので、「作成する」をクリック

これで、プロバイダーの登録が完了しました。

Messaging APIを有効にする

では続いて、本題のMessaging APIの登録です。
プロバイダー登録が完了したページに以下のようにMessaging APIのチャネルを登録する部分がありますので、この「チャネル作成する」をクリックします。

フォームには色々と入力ボックスがあるのですが、今回はテストですので必須入力の以下4つだけを入力します。

  • アプリ名 ・・・ 作成するBOTの名前
  • アプリ説明 ・・・ BOTの内容
  • 大業種、小業種 ・・・ カテゴリ
  • メールアドレス ・・・ メアド

入力が終わったら「入力内容を確認する」をクリック。

同意内容がポップアップされるので「同意する」をクリック。

入力した内容を確認し、さらに利用規約の同意にチェックをいれて「作成」ボタンをクリックします。

これで、チャネルの作成も完了しました。

Messaging APIに必要な情報を取得する

では、作成されたチャネルをクリックしてAPIのアクセスに必要な内容を取得しておきましょう。

作成したチャネルをクリックすると色々と情報が表示されるのですが、その中に「Channel Secret」という部分があります。これがAPIの秘密鍵です。

Laravelの.envを開いて以下のように登録しておきましょう。

LINE_CHANNEL_SECRET=(あなたの秘密鍵)

続いて、アクセストークンですが、初期状態ではトークンは発行されていないので、「再発行」をクリック。

以下のようなポップアップが表示されるので、そのまま「再発行」をクリック。(文章を読むとトークンは最大24時間しか使えないかと勘違いしそうですが、これは「古いトークンがあとどれだけ有効になるか」という意味のようです)

すると、アクセストークンが発行されます。

このアクセストークンもLaravel.envに以下のように記載しておきましょう。

LINE_ACCESS_TOKEN=(あなたのアクセストークン)

ウェブフックを有効にする

BOTが自動返信できるようにするには、ウェブフックが使えるようになっていないといけませんので、以下のように「Webhook送信」「Webhook URL」を登録しておきましょう。

これでLINE側での作業は完了です。

Laravel側の作業

ではここからはLaravelLINEから送信されてきたメッセージを受け取るコードを作っていきます。

パッケージをインストールする

LINEは公式にPHP向けパッケージを公開してくれているので、これをインストールしておきます。

composer require linecorp/line-bot-sdk

また、QRコードを作成パッケージもインストールします。

composer require endroid/qr-code

QRコード保存用のフォルダをつくる

取得したテキストから作ったQRコードを一時的に保存しておくためのフォルダを作っておきましょう。今回は/public/qr_codeに保存します。(書き込み権限もつけておいてください)

ルートをつくる

今回、LINEからアクセスされるURLをhttps://*********/qr-botとしますので、以下のようになります。

※ ちなみにWebhookはPOSTで送信されてきます。

Route::post('qr-bot', 'QrBotController@reply');

CSRF(クロスサイトリクエストフォージェリ)対策を解除する

ご存知のとおりLaravelは他のサイトから不正にアクセスするCSRFの対策としてPOSTPUTなどの通信には毎回ランダムな文字列(トークン)を作ってこの文字列が一致するかどうかをチェックしてくれています。

ただ、LINEからのウェブフックだとこのトークンを作ることはできませんので、先ほどつくったルートだけCSRF対策を解除しておきましょう。

app/Http/Middleware/VerifyCsrfToken.phpを開いて$exceptに先ほどのURLを追加します。

<?php

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;

class VerifyCsrfToken extends Middleware
{
    /**
     * Indicates whether the XSRF-TOKEN cookie should be set on the response.
     *
     * @var bool
     */
    protected $addHttpCookie = true;

    /**
     * The URIs that should be excluded from CSRF verification.
     *
     * @var array
     */
    protected $except = [
        'qr-bot'
    ];
}

これで、LINEのコントロールパネルから「接続確認」をクリックした際にHTTPステータスコードが200であれば以下のように表示されます。

コントローラーをつくる

次に専用のコントローラーを作ります。
以下のコマンドを実行してください。

php artisan make:controller QrBotController

すると、app/Http/Controllers/QrBotController.phpが作成されるので以下のように変更してください。

<?php

namespace App\Http\Controllers;

use Endroid\QrCode\QrCode;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use LINE\LINEBot;
use LINE\LINEBot\HTTPClient\CurlHTTPClient;
use LINE\LINEBot\MessageBuilder\ImageMessageBuilder;
use LINE\LINEBot\Event\MessageEvent;
use LINE\LINEBot\Event\MessageEvent\TextMessage;

class QrBotController extends Controller
{
    public function reply(Request $request) {

        $channel_secret = env('LINE_CHANNEL_SECRET');
        $access_token = env('LINE_ACCESS_TOKEN');
        $request_body = $request->getContent();
        $hash = hash_hmac('sha256', $request_body, $channel_secret, true);
        $signature = base64_encode($hash);

        if($signature === $request->header('X-Line-Signature')) {   // LINEからの送信を検証

            $client = new CurlHTTPClient($access_token);
            $bot = new LINEBot($client, ['channelSecret' => $channel_secret]);

            try {

                $events = $bot->parseEventRequest($request_body, $signature);

                foreach ($events as $event) {

                    if($event instanceof MessageEvent && $event instanceof TextMessage) {   // テキストメッセージの場合

                        $text = $event->getText();              // LINEで送信されたテキスト
                        $reply_token = $event->getReplyToken(); // 返信用トークン

                        // QRコード作成
                        $filename = Str::random() .'.png';
                        $path = public_path('qr_code/'. $filename);
                        $qrCode = new QrCode($text);
                        $qrCode->writeFile($path);

                        // 画像メッセージで返信
                        $url = url('qr_code/'. $filename);
                        $replying_message = new ImageMessageBuilder(
                            $url,
                            $url
                        );
                        $bot->replyMessage($reply_token, $replying_message);

                    }

                }

            } catch (\Exception $e) {}

        }

    }
}

やっていることは、まずアクセスがあった場合にそれが本当にLINEからのアクセスなのかどうかをチェックしています。

そして、それが検証されたら次に送信されてきたテキストを取得し、さらにそのテキストを使ってQRコードを作成します。

最後に、作成されたQRコードのURLをLINE側に返信メッセージとして送信して完了です。

※ ちなみにImageMessageBuilderは画像本体を送信しているわけではなく、単にURLを送信しているにすぎません。そのため、作成したQRコードを削除してしまうとメッセージがうまく表示されなくなってしまいます。

テストしてみる

では、テストしてみましょう。

チャネルの詳細ページの下の方に以下のようなQRコードを用意してくれていますので、このQRコードをアプリなどで読みとってアクセスします。

すると作成したBOTLINEで友達になれるので何か文字を送信してみます。(今回はこのブログのURLです)

はい!
自動でQRコードが返ってきました。

成功です😊✨

ちなみに:自動返信を無効にする

 

LINE Messaging APIでは、デフォルト設定として必ず以下のようなメッセージが自動で返信されるようになっています。

ここまででは、本当にLINEとのやりとりができているかどうかをチェックするために残しておきましたが、テストが成功したらもう不要ですので、次の手順で無効にしておきましょう。

「メッセージありがとうございます」
「申し訳ございませんが、このアカウントから個別に返信することはできません」
「次回の配信をお楽しみに」

アクセストークンなどを取得したチャネルの詳細ページに「自動応答メッセージ」という項目があるので、ここの「設定はこちら」リンクをクリック。

すると別ページが開かれるので、応答メッセージをオフにしてください。(保存は自動でやってくれます)

追記:QRコード読みとり機能もつけました(2019.09.02)

その後、せっかくなら逆にQRコードが送信されたら内容を読み取って返信するようにしたいなと思いましたので、QrBotController.phpを更に改造して実装みました。

※ なお、QRコードの読みとりにはzbarを使っていますが、その詳しい内容はネットカフェ向けにコミックをどこまで読んだか記録するLINEボットをつくったをご覧ください。

<?php

namespace App\Http\Controllers;

use Endroid\QrCode\QrCode;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use LINE\LINEBot;
use LINE\LINEBot\Event\MessageEvent\ImageMessage;
use LINE\LINEBot\HTTPClient\CurlHTTPClient;
use LINE\LINEBot\MessageBuilder\ImageMessageBuilder;
use LINE\LINEBot\Event\MessageEvent;
use LINE\LINEBot\Event\MessageEvent\TextMessage;

class QrBotController extends Controller
{
    public function reply(Request $request) {

        $channel_secret = env('LINE_CHANNEL_SECRET');
        $access_token = env('LINE_ACCESS_TOKEN');
        $request_body = $request->getContent();
        $hash = hash_hmac('sha256', $request_body, $channel_secret, true);
        $signature = base64_encode($hash);

        if($signature === $request->header('X-Line-Signature')) {   // LINEからの送信を検証

            $client = new CurlHTTPClient($access_token);
            $bot = new LINEBot($client, ['channelSecret' => $channel_secret]);

            try {

                $events = $bot->parseEventRequest($request_body, $signature);

                foreach ($events as $event) {

                    if($event instanceof MessageEvent) {   // テキストメッセージの場合

                        $reply_token = $event->getReplyToken(); // 返信用トークン

                        if($event instanceof TextMessage) { // QRコード作成

                            $text = $event->getText();              // LINEで送信されたテキスト

                            // QRコード作成
                            $filename = Str::random() .'.png';
                            $path = public_path('qr_code/'. $filename);
                            $qrCode = new QrCode($text);
                            $qrCode->writeFile($path);

                            // 画像メッセージで返信
                            $url = url('qr_code/'. $filename);
                            $replying_message = new ImageMessageBuilder(
                                $url,
                                $url
                            );
                            $bot->replyMessage($reply_token, $replying_message);
                            break;

                        } else if($event instanceof ImageMessage) { // QRコード読みとり

                            $replying_text = '';
                            $message_id = $event->getMessageId();
                            $response = $bot->getMessageContent($message_id);

                            if($response->isSucceeded()) {

                                $filename = $message_id .'.jpg';
                                $image_path = storage_path('app/qr_bot/'. $filename);
                                \Storage::put('qr_bot/'. $filename, $response->getRawBody());
                                $replying_text = $this->read_qr($image_path);
                                @unlink($image_path);

                            }

                            $bot->replyText($reply_token, $replying_text);    // 返信
                            break;

                        }

                    }

                }

            } catch (\Exception $e) {}

        }

    }

    private function read_qr($path) {

        $text = '';
        exec('/usr/bin/zbarimg '. $path, $results);
        $results_count = count($results);

        if($results_count > 0) {

            foreach($results as $index => $result) {

                if($index === 0) {

                    $text .= preg_replace('|QR-Code:|', '', $result);

                } else {

                    $text .= $result;

                }

                if($index < ($results_count-1)) {

                    $text .= "\n";

                }

            }

        }

        return $text;

    }
}

ちなみに: 追記2019.09.16

ちなみに、ある開発でわかったことなのですがQRコードの読みとり器によってはECI(文字コードの情報)が含まれていると以下のような先頭に余分なデータが追加されてしまうようでした。

\000026(QRコードの内容)

\000026UTF-8を意味します。

そのため、今回使ったパッケージEndroid\QrCode\QrCodeでこのECIを追加しないようにするには、以下のようにエンコードにISO-8859-1をセットしてください。

$qrCode = new QrCode('QRコードの内容');
$qrCode->setEncoding('ISO-8859-1');

おわりに

ということで今回はLINE Messaging API + Laravelでチャットボットを作成してみました。

LINEのアプリを普段使っているようでしたら、アカウントの作成も簡単ですし、Laravel側のコードもそれほど多いわけではないので比較的すぐ実装することができました。

このテクニックを応用すればLINEのユーザーIDをチェックするようにして、出先で会社のデータベースにLINEを使って検索をかけることもできますし、在庫が今現在いくつなのか?という返信もできるかもしれません。

使い方次第で夢が広がりますね😊✨

ではでは〜!

この記事が役立ちましたらシェアお願いします😊✨