【LINE API】Messaging API+PHP SDKで対話アプリを作ってみた!

どうも!ヒグッティ(X→ヒグッティ@システムエンジニア)です!
今回は、LINE API(Messaging API)を使ってLINEで対話するBOTを作成していきます。WebhockでサーバにリクエストしてPHPで処理してユーザにテキストを返す簡単なアプリです!!メッセージを複数送信する方法も解説しています!!

完成のイメージ

イメージは以下となります。ユーザの返信をwebhockで処理するサーバに通信して返答内容を作成して返信します

アプリ作成までの流れ

  • LINEアカウントの設定(Webhock)
  • Webhock先のサーバでのプログラミング(PHP)

こんな感じでやっていきます!

前提

今回はPHPのプログラミングをメインに解説します。LINEアカウントの作成方法やWebhockの設定などは事前に完了している想定です。

LINEアカウントの設定

まず初めに、BOTを作成するためにはLINEアカウントを作成しWebhockを設定しなければなりません。Webhockの設定は以下のように設定してください。
LINE Developersにログインして以下の画面で確認してください。

作成したMessaging APIの設定を開いて以下の画面でWebhockを確認。

あとはチャネルトークンとチャネルアクセストークンの値も確認してメモしておきましょう!PHPでBOTからユーザに返答するときに必要になります。

以下がチャネルトークンの記載場所です。

LINE Developers > 対象のMessaging API > チャネル基本設定 > チャネルシークレット

以下がチャネルアクセストークンの記載場所です。

LINE Developers > 対象のMessaging API > Messaging API設定 > チャネルアクセストークン

ここまでできたらLINEアカウントの設定は完了です。

Webhock先でのPHP

今回はWebhock先のサーバとしてレンタルサーバを借りました。他には、AWSのEC2だったりAPIGATEWAY+LamdbaだったりGCPとかでもありだと思います。注意としてはhttps通信しか許可されていません!!

使うPHPなどのバージョンは以下になります。今回はComposerなどでのインストール作業は割愛します。詳細は公式のgithubを参照してください。

https://github.com/line/line-bot-sdk-php?tab=readme-ov-file

https://line.github.io/line-bot-sdk-php/

  • PHP 8.1.27
  • linecorp/line-bot-sdk 9.3系

アプリのイメージとしてはユーザが何か文字列を送ってきたら、その内容に応じて特定の文字列を返信するようなアプリとなります。

では実際にソースコードを見ていきましょう!!

<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
// webhookの確認
require("../vendor/autoload.php");

use Monolog\Logger;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Handler\NativeMailerHandler;
use LINE\Clients\MessagingApi\Model\ReplyMessageRequest;
use LINE\Clients\MessagingApi\Model\TextMessage;
use LINE\Clients\MessagingApi\Model\TemplateMessage;
use LINE\Clients\MessagingApi\Model\URIAction;
use LINE\Clients\MessagingApi\Model\PostbackAction;
use LINE\Clients\MessagingApi\Model\MessageAction;
use LINE\Clients\MessagingApi\Model\ButtonsTemplate;
use LINE\Constants\TemplateType;
use LINE\Constants\ActionType;
use LINE\Constants\HTTPHeader;
use LINE\Parser\EventRequestParser;
use LINE\Webhook\Model\MessageEvent;
use LINE\Webhook\Model\PostBackEvent;
use LINE\Webhook\Model\FollowEvent;
use LINE\Parser\Exception\InvalidEventRequestException;
use LINE\Parser\Exception\InvalidSignatureException;
use LINE\Webhook\Model\TextMessageContent;
use GuzzleHttp\Client;
use LINE\Clients\MessagingApi\Api\MessagingApiApi;
use LINE\Clients\MessagingApi\Configuration;
use LINE\Clients\MessagingApi\Model\PushMessageRequest;
use LINE\Constants\MessageType;

$channelSecret = 'Your channelSecret';
$channelAccessToken = '+Your channelAccessToken';
$client = new Client();
$config = new Configuration();
$config->setAccessToken($channelAccessToken);
$messagingApi = new MessagingApiApi(
  client: $client,
  config: $config,
);

// Monolog
$app = 'webhook';
$log = new Logger($app);
$rotatingFileHandler = new RotatingFileHandler("../log/webhook.log", 20, Logger::DEBUG);
$log->pushHandler($rotatingFileHandler);

try {
  $log->info('webhook開始');
  $headers = getallheaders();
  //JSONの内容を取得
  $body = file_get_contents('php://input');
  if (!empty($headers['x-line-signature'])) {
    $signature = $headers['x-line-signature'];
  }
  $parsedEvents = EventRequestParser::parseEventRequest($body, $channelSecret, $signature);
  // 1つのWebhookに複数のWebhookイベントオブジェクトが含まれる場合があるため、繰り返し処理を行う。
  foreach ($parsedEvents->getEvents() as $event) {
    $log->info($event);
    // ユーザ名の取得
    $userId = $event['source']['userId'];
    $log->info('ユーザID:' . $userId);
    $profile = $messagingApi->getProfile($userId);
    $log->info('ユーザー名: ' . $profile['displayName']);
    $userName = $profile['displayName'];

    $reply_message_txt_question = '';
    $reply_message_txt_question_obj = '';
    if ($event instanceof FollowEvent) { //友達登録の場合(ブロック解除でも動作する)
      // 初回だけ2秒スリープ 初回のあいさつメッセージが送信されるため少し待つ
      sleep(2);
      // 初回の質問文
      $reply_message_txt_question = sprintf('%sさんに質問です。好きな動物を以下から選んで返信してください。
ねこ、いぬ、へび', $userName);
      $reply_message_txt_question_obj = (new TextMessage(['text' => $reply_message_txt_question]))->setType('text');

    } elseif ($event instanceof MessageEvent) { //メッセージ受信の場合
      $receive_message_text = '';
      // 受信メッセージのテキストを抽出
      $receive_message = $event->getMessage();
      $receive_message_text = $receive_message->getText();
      $log->info('返信テキスト: ' . $receive_message_text);

      if($receive_message_text === 'らーめん'){
        $reply_message_txt = (new TextMessage(['text' => 'やっぱりラーメンですよね!アンケートは以上です!!']))->setType('text');
        $messagingApi->replyMessage(new ReplyMessageRequest(
          [
            'replyToken' => $event->getReplyToken(),
            'messages' => [
              $reply_message_txt,
            ],
          ],
        ));
        return;
      }
      $reply_message = $receive_message_text . 'ですね。ありがとうございます!';
      $reply_message_txt = (new TextMessage(['text' => $reply_message]))->setType('text');

      $reply_message_txt_question = sprintf('%sさんに質問です。好きな食べ物を以下から選んで返信してください。
      ラーメン、チャーハン、カレー', $userName);
      $reply_message_txt_question_obj = (new TextMessage(['text' => $reply_message_txt_question]))->setType('text');

      $messagingApi->replyMessage(new ReplyMessageRequest(
        [
          'replyToken' => $event->getReplyToken(),
          'messages' => [
            $reply_message_txt,
            $reply_message_txt_question_obj,
          ],
        ],
      ));
      return;
  
    } else {
      $log->info('処理対象外のイベントを受信しました。');
      continue;
    }

    $messagingApi->replyMessage(new ReplyMessageRequest(
      [
        'replyToken' => $event->getReplyToken(),
        'messages' => [
          $reply_message_txt_question_obj,
        ],
      ],
    ));
  }
} catch (Exception $e) {
  $log->error($e);
} finally {
  $log->info('webhook終了');
}

ちなみにラインではこんな感じになります。

ソースコードが結構長いですね、、冗長な感じですが、ご勘弁を、、ではどこで何をしているのか解説していきます。

Messaging APIの作成

まずは以下の解説です!以下の書き方で、Messaging APIとの接続を作成するイメージです。

$channelSecret = 'Your channelSecret';
$channelAccessToken = '+Your channelAccessToken';
$client = new Client();
$config = new Configuration();
$config->setAccessToken($channelAccessToken);
$messagingApi = new MessagingApiApi(
  client: $client,
  config: $config,
);

ちなみに引数のclientはnullでも動きます。ここら辺は公式ドキュメントも英語だけで使い方のサンプルも少ないので、書き方的にはおまじない見たいですね~チャネルアクセストークンはLINE DEVELOPERで確認した値を入れてください!!

LINE APIからのイベント取得

次はLINEから送られてきたイベントを取得する処理です。

  $headers = getallheaders();
  $body = file_get_contents('php://input');
  if (!empty($headers['x-line-signature'])) {
    $signature = $headers['x-line-signature'];
  }
  $parsedEvents = EventRequestParser::parseEventRequest($body, $channelSecret, $signature);
  // 1つのWebhookに複数のWebhookイベントオブジェクトが含まれる場合があるため、繰り返し処理を行う。
  foreach ($parsedEvents->getEvents() as $event) {

LINEのMessagingAPIからはjsonでデータをやり取りするので、jsonを受け取るために「$body = file_get_contents('php://input’);」をしているイメージです。

そして鬼門は以下の書き方だと思います。

$parsedEvents = EventRequestParser::parseEventRequest($body, $channelSecret, $signature);

parseEventRequestメソッドの引数で$signatureがよくわからないのは私だけですかね?なので調べました!!

ざっくり説明すると 、このメッセージはLINE APIのMessagingAPIから飛んできたメッセージなのか?を検証する必要があります。LINEになりすまして、webhockを悪用する人がいるかもしれないので、そ~ゆ~人たちを排除するための仕組です。ヘッダーの中から「x-line-signature」を取得します。このシグネチャがLINEからのメッセージかを判定するための署名?暗号方式?的なものです。 x-line-signature が正しければ送られてきたjsonデータを復号できます!!

細かいことは、説明できませんが、とにかく parseEventRequestメソッドの引数は(jsonの値、チャネルトークン、シグネチャ)でメソッドを呼ぶとイベントをオブジェクトに変換してくれます。

ユーザ情報の取得

次はユーザ情報にアクセスして名前を取得するコードです。

    $userId = $event['source']['userId'];
    $log->info('ユーザID:' . $userId);
    $profile = $messagingApi->getProfile($userId);
    $log->info('ユーザー名: ' . $profile['displayName']);
    $userName = $profile['displayName'];

これはそのままコピーしてもらえれば動きます!!見たまんまだと思います!$event['source’]['userId’]の中にユーザIDが格納されているので、ユーザIDを取得。$profile = $messagingApi->getProfile($userId);でユーザIDを元にユーザのプロファイル情報を取得します。$profile['displayName’]でユーザの名前を取得できます!

ユーザに返信

次はイベントの判別のコードとユーザに返信するコードです。

    if ($event instanceof FollowEvent) { //友達登録の場合(ブロック解除でも動作する)
      // 初回だけ2秒スリープ 初回のあいさつメッセージが送信されるため少し待つ
      sleep(2);
      // 初回の質問文
      $reply_message_txt_question = sprintf('%sさんに質問です。好きな動物を以下から選んで返信してください。
ねこ、いぬ、へび', $userName);
      $reply_message_txt_question_obj = (new TextMessage(['text' => $reply_message_txt_question]))->setType('text');

~省略~
    $messagingApi->replyMessage(new ReplyMessageRequest(
      [
        'replyToken' => $event->getReplyToken(),
        'messages' => [
          $reply_message_txt_question_obj,
        ],
      ],
    ));

特に大事なことは「if ($event instanceof FollowEvent) {」ですね!イベントのオブジェクトがフォローイベントかどうか判定しています。
フォローイベント以外には以下いろいろあります。

  • MessageEvent (メッセージの受信)
  • FollowEvent (フォローした時)
  • UnfollowEvent (フォローを解除した時)
  • UnsendEvent (メッセージを送信できない時)

(new TextMessage(['text’ => $reply_message_txt_question]))->setType('text’);で返信する文字列を設定しています。あとは$messagingApi->replyMessage(new ReplyMessageRequest( にreplyToken、messagesを設定すれば、ユーザに文字列を返信してくれます。

ちなみにユーザにメッセージを2つ送信したい時は以下の書き方で実現できます。

      $messagingApi->replyMessage(new ReplyMessageRequest(
        [
          'replyToken' => $event->getReplyToken(),
          'messages' => [
            $reply_message_txt,
            $reply_message_txt_question_obj,
          ],
        ],
      ));

'messages’に配列で文字列を追加するとその分だけ、ユーザにメッセージを送信できます。上記の書き方だと2件のメッセージ($reply_message_txtと$reply_message_txt_question_obj)を送信しています。

まとめ

実装した感想としては、ネットの記事が少なくてかなり難航したイメージです。LINE API周りのドキュメントも少なくて公式ドキュメントも具体例が書いてないので正直わからないことばかりでした、、PHP SDKを使ったやり方やcurlを使ったやり方が散見しているので、自分が何を使って作りたいのかを意識しないとネットの情報に飲み込まれますね、、次は画像とかボタンを使ったLINEアンケートみたいなものを実装しようかな~

スポンサーリンク