はじめに
みなさま LINE は使われていますでしょうか? LINE の国内利用者数は9,000万人以上(2022年8月)と発表されており、国内の方であれば主な連絡手段は LINE になるのかなと思います。僕の祖父(78歳)も登録してるくらい 使いこなしてはない ですから、登録してない方がめずらしいですよね。
このような状況ですとクライアントから、 LINE を使った集客やシステム連携のご相談をいただきます。
そのため今回は Laravel × LINE Messaging API を使った LINE とシステムの連携、機能実装を進めていく中で気づいたことや注意点をまとめていきます。
参照リンク
https://developers.line.biz/ja/reference/messaging-api/
https://developers.line.biz/ja/
実装イメージ
実装していく機能で すが、LINEとWEBアプリでメッセージのやりとりができるものを作っていきます。
1. LINEからWEBアプリへメッセージを送信
flowchart LR
subgraph "LINE"
A[LINEアカウント/メッセージ送信] -->|"API"| B[LINE Messaging Api]
end
subgraph "WEBサーバー"
B -->|"webhhok"| C[Laravel / DB]
end
subgraph "WEBアプリ"
C -->|"同期通信"| D[ブラウザ / メッセージ表時]
end
2. WEBアプリからLINEへメッセージを送信
flowchart RL
subgraph "WEBアプリ"
A[ブラウザ/メッセージ送信]
end
subgraph "WEBサーバー"
A -->|"httpリクエスト"| B[Laravel / DB]
end
subgraph "LINE"
B -->|"API"| D[LINE Messaging Api] -->|"API"| E[LINEアカウント/メッセージ受信]
end
初期設定
LINE Developesの設定
LINE Messaging API を使うにあたり LINE Developers で設定が必要です。
公式のガイド や こちらの記事 を参考に設定を進めてください。
https://developers.line.biz/ja/docs/messaging-api/getting-started/#using-console
https://biz.addisteria.com/laravel_line_integration/
Webhook
は以下のように設定をお願いします。 Laravel 側では /line/webhook/message
でエンドポイントを設定します。
https
でないと Webhook
は使えないので注意してください。
ちなみに自分は ngrok
を使って https
化、 Webhook
の検証をしました。
https://biz.addisteria.com/ngrok-windows/
ngrok
でWebhookのURLを設定すると以下のようになります。
https://ec08-122-249-204-181.jp.ngrok.io/line/webhook/message
Laravelにパッケージのインストール
LINE Developers でチャネルの登録や新規プロバイダーの作成が済んだら Laravel に line-bot-sdk-php
パッケージをインストールします。
composer require linecorp/line-bot-sdk
機能実装1
まずはベースとなる処理を実装していきます。
Botアカウントにメッセージが送られたら、特定のメッセージを返信するようにします。
CSRFの例外設定
Webhook
は POST
メソッドでリクエストが来るので line/*
のルートを CSRF
の対象外に設定しておきます。
class VerifyCsrfToken extends Middleware
{
 /**
 * The URIs that should be excluded from CSRF verification.
 *
 * @var array<int, string>
 */
 protected $except = [
 'line/*'
 ];
}
環境変数の設定
LINE Developersで設定したチャンネルID、チャンネルシークレット、チャンネルアクセストークンを .env
に設定します。
LINE_MESSAGE_CHANNEL_ID=チャンネルID
LINE_MESSAGE_CHANNEL_SECRET=チャンネルシークレット
LINE_MESSAGE_CHANNEL_TOKEN=チャンネルアクセストークン
.env
環境変数を読み込むため config/services.php
を設定します。
return [

		~ 省略 ~ 

 'line' => [
 'message' => [
 'channel_id'=>env('LINE_MESSAGE_CHANNEL_ID'),
 'channel_secret'=>env('LINE_MESSAGE_CHANNEL_SECRET'),
 'channel_token'=>env('LINE_MESSAGE_CHANNEL_TOKEN')
 ]
 ],
];
ルーティングの設定
LINE Developersで設定したWebhookのURLに合わせルーティングを設定します。
Route::post('/line/webhook/message', 'App\Http\Controllers\LineWebhookController@message')->name('line.webhook.message');
LINEBotの継承クラスを準備
このクラスは line-bot-sdk-php
パッケージと LINE Messaging API のエンドポイントに差異があったり、関数が用意されていない場合に使います。そのためパッケージに元々ある関数だけで実装できるようであれば、このクラスはなくても大丈夫です。
クラス内でやることはシンプルで、 LINEBot
クラスを継承してオーバーライド or 関数を作成して、必要な処理を追加していきます。
/app
の配下に Services
フォルダを作成して LineBotService.php
を設置します。
※参考としていくつか関数を用意しておきます。
<?php
namespace App\Services;

use LINE\LINEBot;
use LINE\LINEBot\HTTPClient;

class LineBotService extends LINEBot
{
 /** @var string */
 private $channelSecret;
 /** @var HTTPClient */
 private $httpClient;

 public function __construct(
 HTTPClient $httpClient,
 array $args
 ) {
 parent::__construct($httpClient, $args);
 $this->httpClient = $httpClient;
 $this->channelSecret = $args['channelSecret'];
 }

 // 例:送信されたメッセージを取得するAPI
 public function getMessageContent($messageId)
 {
 return $this->httpClient->get('https://api-data.line.me/v2/bot/message/' . urlencode($messageId) . '/content');
 }

 // 例:LINEのグループ情報を取得するためのAPI
 public function getGroupSummary($groupId)
 {
 return $this->httpClient->get('https://api.line.me/v2/bot/group/' . urlencode($groupId) .'/summary');
 }
}
Webhook用コントローラの作成
以下のコマンドでコントローラーを作成します。
php artisan make:controller LineWebhookController
LineBotService.php
を使う想定で実装を進めます。
まずは LINE からメッセージが送信された場合に、シンプルにメッセージを返信するコードを書いていきます。
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Services\LineBotService as LINEBot;
use LINE\LINEBot\HTTPClient\CurlHTTPClient;

class LineWebhookController extends Controller
{
 public function message(Request $request) {
 $data = $request->all();
 $events = $data['events'];

 $httpClient = new CurlHTTPClient(config('services.line.message.channel_token'));
 $bot = new LINEBot($httpClient, ['channelSecret' => config('services.line.message.channel_secret')]);

 foreach ($events as $event) {
 $response = $bot->replyText($event['replyToken'], 'メッセージ送信完了');
 }
 return;
 }
}
これだけで LINE から来たメッセージに対して返信ができるようになりました。
LINE Developers を開き Messaging API
タブを選択すると、QRコードが表示されているので読み取ってBotアカウントを友達に追加しましょう。
Botアカウントにメッセージを送信して メッセージ送信完了
と返信が来たら実装完了です。
通信がうまくいかない場合はWebhookのURLを確認してみてください。 ngrok
を使っている場合は起動しているかなども。
機能実装2
今回はWEBアプリと LINE でメッセージのやり取りをすることが目的なので、先ほどのコードに処理を追加していきます。
messagesテーブルの作成
やり取りしたメッセージを保存するため messages
テーブルを作成します。以下のコマンドを実行してマイグレーションファイルを作成してください。
php artisan make:migration CreateMessagesTable
テーブルの中身は以下のように設定します。 line_message_id
のみ null許容
しておきます。
public function up()
 {
 Schema::create('messages', function (Blueprint $table) {
 $table->id();
 $table->string('line_user_id')->comment('LINEユーザーID');
 $table->string('line_message_id')->nullable()->comment('LINEメッセージID');
 $table->string('text')->comment('テキスト');
 $table->timestamps();
 });
 }
Messageモデルの作成
続いてモデルを作成します。コマンドを実行してファイルを編集してください。
php artisan make:model Message
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Message extends Model
{
 use HasFactory;

 protected $fillable = [
 'line_user_id',
 'line_message_id',
 'text',
 ];
}
$eventsの中身
LineWebhookController.php
にメッセージを保存する処理を追加していきますが、その前に Webhook
で送られてくるリクエストの中身を確認しておきましょう。
$events = $data['events'];
で取得している $events
の中には以下の配列が入っています。
※発生したイベントの数に応じて配列も増えるので注意してください。
array (
 'type' => 'message', // イベントタイプ message以外にもjoin・memberJoined・follow等がある
 'message' => array (
 'type' => 'text', // text以外にimage(画像)・sticker(スタンプ)がある
 'id' => '16759029429384', // LINEのメッセージID
 'text' => 'テキスト', // LINEから送信したメッセージ
 ),
 'webhookEventId' => '01GCKECY18W8Q8B438GTYCCHYG',
 'deliveryContext' => 
 array (
 'isRedelivery' => false,
 ),
 'timestamp' => 1662804981373,
 'source' => 
 array (
 'type' => 'user',
 'userId' => 'U9d85fbafd6e192e2616c783a20666d9c', // LINEのユーザーID
 ),
 'replyToken' => '189df3de294d47f8b33f2d908c04008c', // メッセージ返信のために必要なトークン
 'mode' => 'active',
)
イベントタイプごとに送られてくるパラメータが変わるので、各ケースに応じて処理を用意する必要がありますが、この記事では イベントタイプ
が message
かつ メッセージタイプ
が text
のものにのみ処理を設定します。
LineWebhookControllerを編集
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Services\LineBotService as LINEBot;
use LINE\LINEBot\HTTPClient\CurlHTTPClient;
use App\Models\Message; // 追記

class LineWebhookController extends Controller
{
 public function message(Request $request) {
 $data = $request->all();
 $events = $data['events'];

 $httpClient = new CurlHTTPClient(config('services.line.message.channel_token'));
 $bot = new LINEBot($httpClient, ['channelSecret' => config('services.line.message.channel_secret')]);

 foreach ($events as $event) {
 // メッセージの保存処理を追記
 Message::create([
 'line_user_id' => $event['source']['userId'],
 'line_message_id' => $event['message']['id'],
 'text' => $event['message']['text'],
 ]);
 // 自動返信が不要であれば削除
 // $response = $bot->replyText($event['replyToken'], 'メッセージ送信完了');
 }

 return;
 }
}
これでLINEから送られたメッセージがデータベースに保存されるようになりました。
次は保存したメッセージをWEBアプリ側で確認できるよう一覧機能を実装していきます。
ルーティングの追加
以下、2つのルーティングを追加します。
Route::get('/messages', 'App\Http\Controllers\MessageController@index')->name('message.index');
Route::get('/messages/{lineUserId}', 'App\Http\Controllers\MessageController@show')->name('message.show');
メッセージ用コントローラの作成
まずはコマンドを実行してコントローラを作成しましょう。
php artisan make:controller MessageController
MessageController.php
には index
と show
アクションを用意します。
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Message;

class MessageController extends Controller
{
 public function index(Request $request) {
 $lineUsers = Message::groupBy('line_user_id')->get('line_user_id');
 return view('message.index', ['lineUsers' => $lineUsers]);
 }

 public function show(Request $request) {
 $messages = Message::where('line_user_id', $request->lineUserId)->get();
 return view('message.show', ['lineUserId' => $request->lineUserId, 'messages' => $messages]);
 }
}
index
アクションではメッセージを送信したユーザーの一覧を表示し、 show
アクションではユーザーに紐づくメッセージを表示します。
ビューファイルの用意
/resources/views
の配下に message
フォルダを作成して index.blade.php
と show.blade.php
を設置し、それぞれ以下のように設定します。
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
 <head>
 <meta charset="utf-8">
 <meta name="viewport" content="width=device-width, initial-scale=1">
 <title>LINEユーザー</title>
 </head>
 <body class="antialiased">
 LINEユーザー一覧
 <ul>
 @foreach($lineUsers as $lineUser)
 <li>
 <a href="{{ route('message.show', ['lineUserId' => $lineUser->line_user_id]) }}">
 {{ $lineUser->line_user_id }}
 </a>
 </li>
 @endforeach
 </ul>
 </body>
</html>
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
 <head>
 <meta charset="utf-8">
 <meta name="viewport" content="width=device-width, initial-scale=1">
 <title>LINEメッセージ</title>
 </head>
 <body class="antialiased">
 <div>
 LINEユーザーID {{ $lineUserId }}
 </div>
 <ul>
 @foreach($messages as $message)
 <li>
 LINEメッセージ {{ $message->text }}
 </li>
 @endforeach
 </ul>
 </body>
</html>
上記の実装で LINE から送信されたメッセージが表示できるようになるので、ブラウザでローカルのURLを叩いて確認してみてください。
機能実装3
ここまでの実装で LINE メッセージの受信と表示ができました。最後にWEBアプリから各 LINE アカウントに対してメッセージが送信できるよう実装していきます。
ルーティングの追加
以下のルーティングを追加します。
Route::post('/message/{lineUserId}', 'App\Http\Controllers\MessageController@create')->name('message.create');
メッセージ用コントローラの編集
MessageController.php
に以下のuse宣言とアクションを追加します。
// use宣言追加
use App\Services\LineBotService as LINEBot;
use LINE\LINEBot\HTTPClient\CurlHTTPClient;
use LINE\LINEBot\MessageBuilder\TextMessageBuilder;

// アクション追加
public function create(Request $request) {
 Message::create([
 'line_user_id' => $request->lineUserId,
 'text' => $request->message,
 ]);

 $httpClient = new CurlHTTPClient(config('services.line.message.channel_token'));
 $bot = new LINEBot($httpClient, ['channelSecret' => config('services.line.message.channel_secret')]);

 $textMessageBuilder = new TextMessageBuilder($request->message);
 $response = $bot->pushMessage($request->lineUserId, $textMessageBuilder);

 return redirect(route('message.show', ['lineUserId' => $request->lineUserId]));
}
メッセージをDBへ保存し、パッケージで用意されたクラスを使ってLINEアカウントへメッセージを送信します。
show.blade.phpを編集
メッセージがWEBアプリ・ LINE どちらから送られてきたか判定するため if
文を設定します。条件は、 line_message_id
が空か否かです。
またメッセージを送信するためのフォームを設置します。
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
 <head>
 <meta charset="utf-8">
 <meta name="viewport" content="width=device-width, initial-scale=1">
 <title>LINEメッセージ</title>
 </head>
 <body class="antialiased">
 <div>
 LINEユーザーID {{ $lineUserId }}
 </div>
 <ul>
 @foreach($messages as $message)
 <li>
 @if(empty($message->line_message_id))
 WEBアプリメッセージ {{ $message->text }}
 @else
 LINEメッセージ {{ $message->text }}
 @endif
 </li>
 @endforeach
 </ul>
 <form method="post" action="{{ route('message.create', ['lineUserId' => $lineUserId]) }}">
 @csrf
 <input type="text" name="message">
 <button type="submit">送信</button>
 </form>
 </body>
</html>
動作検証
WEBアプリのフォームにメッセージを入力して送信ボタンを押下します。Botアカウントから入力したメッセージが届きます。
続いて LINE からメッセージを送信して、ブラウザをリロードします。WEBアプリ・ LINE から送信したメッセージがそれぞれ表示されていれば実装完了です。
linecorp/line-bot-sdkの困りごと
現状、Weアプリと LINE でのメッセージのやり取りの機能しかありません。それだけでは機能不足なので、ここから要望に合わせ機能を追加していくかと思います。
すると LINE Messaging API にはエンドポイントがあるのに linecorp/line-bot-sdk
にそれに対応する関数が無いことや、それっぽい関数はあるがエンドポイントが公式と違うことで正しく処理を実行できないことがあり、ハマりました。
ここでは記事の序盤で用意して、 放置していた ずっと触れずにいた LineBotService.php
を使った対応方法を記載していきます。
どうハマったのか?
まずやりたかったこととしては、 LINE から送られてきた画像をサーバーに保存してWEBアプリで表示するということでした。
LINE から画像を送信した際には、 Webhook
が叩かれイベント情報は送られてくるのですが、ファイルの情報自体は含まれていないので、 LINE のメッセージIDを元にファイル情報を取得する必要があります。
/vendor/linecorp/line-bot-sdk/src/LINEBot.php
にそれっぽい関数があり、 LINE Messaging API のエンドポイントともパスが同じだったので、以下の関数を使って実装してました。
public function getMessageContent($messageId)
{
 return $this->httpClient->get($this->endpointBase . '/v2/bot/message/' . urlencode($messageId) . '/content');
}
ただ画像情報はおろかメッセージ情報もとれず返ってくるのはなぜか404…
いろいろ調べているうちに サブドメインが違う!
と気づきます。
そこでちょっと強引ですが LINEBot
クラスを継承した LineBotService
クラスを用意して、オーバーライドすることでエンドポイントをまるまる指定することにしました。
public function getMessageContent($messageId)
{
 return $this->httpClient->get('https://api-data.line.me/v2/bot/message/' . urlencode($messageId) . '/content');
}
画像の保存処理
LineBotService
クラスを使うことで画像情報を取得することができるようになったので、 LineWebhookController.php
の message
関数に画像の保存処理を追加していきます。
画像のパスや元々のファイル名等は、別途処理を追加してDBに保存してください。
また、メッセージのイベントタイプに合わせ switch
文でそれぞれ処理を分けます。
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Services\LineBotService as LINEBot;
use App\Models\Message;
use LINE\LINEBot\HTTPClient\CurlHTTPClient;
use Illuminate\Support\Facades\Storage;

class LineWebhookController extends Controller
{
 public function message(Request $request) {
 $data = $request->all();
 $events = $data['events'];

 $httpClient = new CurlHTTPClient(config('services.line.message.channel_token'));
 $bot = new LINEBot($httpClient, ['channelSecret' => config('services.line.message.channel_secret')]);

 foreach ($events as $event) {
 switch ($event['message']['type']) {
 case 'text':
 Message::create([
 'line_user_id' => $event['source']['userId'],
 'line_message_id' => $event['message']['id'],
 'text' => $event['message']['text'],
 ]);
 // $response = $bot->replyText($event['replyToken'], 'メッセージ送信完了');
 break;

 case 'image':
 $response = $bot->getMessageContent($event['message']['id']);
 if ($response->isSucceeded()) {
 $contentType = $response->getHeader('content-type');
 $arrayContentType = explode('/', $contentType);
 $ext = end($arrayContentType);
 $path = 'public/line/' .$event['message']['id'] .'.' .$ext;
 Storage::put($path, $response->getRawBody());
 Storage::url($path);
 } else {
 error_log($response->getHTTPStatus());
 }
 break;

 case 'sticker':
 // スタンプが送信された場合
 break;
 }
 }
 return;
 }
}
ここまでできたら LINE から画像を送信してみてください。 /storage/app/public/line
の配下に画像が保存されいるはずです。
他にも実装したい機能はたくさん出てくるかと思います。まずは公式のエンドポイントを確認してみください。
それに対応した関数がなければ LineBotService
クラスに関数を作ってあげれば、要件を満たして実装できるかなと思います。
さいごに
LINE と連携したサービス展開は一般的なものとなっています。そのため LINE と連携するにあたり、独自の設定や機能を追加したいという需要も増えていくかと思いますので、是非参考にしてください。
ではまた次の記事で!