PHP版の公式SDKを使ってLINE Messaging APIベースのBotを作ってみた

# はじめに

LINEのMessaging APIが発表されましたね。
LINEが以前リリースしていたBotAPIはトライアル的な扱いであったのに対して、Messaging APIは正式にリリースされて仕様が安定したバージョン。

https://devdocs.line.me/ja/#messaging-api
によると、今回使うPHP以外にもJavaやGoなどの言語向けに公式のSDKが提供されていて、APIのコアな仕様がわかっていなくてもSDKを使えば簡単にLINE上でサービスを提供できるようになってます。

今回は、[Togetter](http://togetter.com)というサービスで運用していたLINE@のアカウントをBot化させる目的でMessaging APIを触ってみたので、ハマったポイントを考慮してコード付きで解説したいと思います。

# つくるもの
- 公式LINE@アカウントに話しかけると人気記事を5つカルーセル型で表示してくれる。
- 次の5つを見たい場合はボタンを押して取得する。

![写真_2016-10-18_2_27_30.png](https://qiita-image-store.s3.amazonaws.com/0/53041/e11d7983-1d76-ce4a-9ce9-b7881d534fdc.png)

# 前提
- PHPだよ
- 公開可能なWebサーバがあるよ
- https必須だよ
- 発言に対して反応するだけの機能を作るよ

Messaging APIでは管理画面でLINE側のサーバから呼び出されるエントリーポイントを指定する必要があり、そこにJSON形式でメッセージの内容がPostされてくる。
そのエントリーポイントを公開し、且つhttpsで用意する必要があるため上記のような前提条件を満たす必要があります。

# LINE@アカウントを用意

https://business.line.me/ja/services/bot
![Messaging_APIのご紹介___LINE_Business_Center.png](https://qiita-image-store.s3.amazonaws.com/0/53041/b7ed8366-efc2-58d7-6730-48526f8519f8.png)

まずはLINE@アカウントを作成しましょう。今回から過去に作ったLINE@アカウントをBot化させることも可能になったので、TogetterではすでにあったアカウントをBot化させました。ちなみに、Bot化すると既存のLINE@アカウントとしては使えなくなるようです。

# Messaging API権限を付与

Messaging APIをLINE@アカウントに紐付けるとアカウントリストにMessaging APIを利用するための設定画面へのリンクが追加されます。
![アカウントリスト___LINE_Business_Center.png](https://qiita-image-store.s3.amazonaws.com/0/53041/7e2c64fd-8cd0-1275-f1dc-afb5675b6c8a.png)


# トークン取得とエントリーポイントの設定

上記の画面からLINE Developersのサイトに遷移するとMessaging APIを扱うための設定をするための専用ページが表示されます。
ChannelSecretとChannelAccessTokenをメモして、WebhookURLを設定しましょう(https必須！)。

![LINE_developers.png](https://qiita-image-store.s3.amazonaws.com/0/53041/92a3b899-ecf4-3b48-a1b2-abee145643c7.png)
と
![LINE_developers.png](https://qiita-image-store.s3.amazonaws.com/0/53041/6d8de67a-85a6-4673-c458-8877928a0d2c.png)

ちゃんとWebhookURLへ疎通しているか、Verifyをクリックして確認しておきましょう。

# エントリーポイントの実装

いよいよサーバ側の実装です。エントリーポイントには、JSONがPOSTされてくるだけなのでそれが受け取れればどんな実装でも構いません。

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

公式のPHP用SDKの中にサンプルがあって基本的にそれを参考に実装をしていきます。普段、使っているフレームワークがあればそれを使っても簡単に実装できます。
まずはSDKをプロジェクトにインストールします。

```

composer require linecorp/line-bot-sdk

```

次にSDKを利用してエントリーポイントの実装を行います。

```php
// エントリーポイントへのリクエストをこの処理で受け取る
// リクエストヘッダー内のシグネチャを得る
$signature = $request->headers->get(HTTPHeader::LINE_SIGNATURE);

$bot = new LINEBot(new CurlHTTPClient('ChannelAccessToken'), [
            'channelSecret' => 'ChannelSecret',
        ]);

try {
    // Bodyと$signatureから内容をVerifyして成功すればEventを得られる
    $events = $bot->parseEventRequest($request->getContent(), $signature);
} catch (Exception $e) {
}
```

ちなみに、テストで動作させてみたい場合は、シグネチャを自分で手に入れる必要がありますが、データとChannelSecretからhash値を生成すれば良いようです。SDKのサンプルからコピペです。

```
// テスト用のシグネチャ生成用
// echo base64_encode(hash_hmac('sha256', $request->getContent(), 'ChannelSecret', true));
```

テスト用のJsonは一番下に貼っておきます。

# 応答可能なEventのみを得る

ソースコードを確認するとEventにも種類が幾つかあることがわかります。
https://github.com/line/line-bot-sdk-php/tree/master/src/LINEBot/Event
通常のメッセージ系だけでなくフォロー／アンフォローなど管理系のEventもあるみたいです。
が、今回は会話とConfirmのアクション結果が得られればいいので、`MessageEvent`か`PostbackEvent`のみを取得するようにします。

```
$message_events = [];
foreach ($events as $event) {
    if (!($event instanceof MessageEvent) && !($event instanceof PostbackEvent)) {
        continue;
    }
    $message_events[] = $event;
}
```

今回は発言内容に応じて返信内容を変えたりしないので、ひとまず返信対象となるEventが手に入れば大丈夫です。`MessageEvent`はテキストやスタンプ、画像投稿などさらにタイプが分かれますがいわゆるLINE上の通常のメッセージですね。`PostbackEvent`はボタンやConfirmなどユーザのクリックで発生して任意のデータを送ってくれるEventですね。今回はConfirmの「はい」「いいえ」で設定したデータがCallbackされてきます。

# カルーセル型のメッセージを作る

今回から新たに追加されたタイプのメッセージで5つまで横に画像やリンクを並べられるので、メディアなどで人気記事やEC系で人気商品の検索結果などを並べるのに向いてそうです。

今回はTogetterのまとめを5つ並べてみます。

![写真_2016-10-18_2_27_30.png](https://qiita-image-store.s3.amazonaws.com/0/53041/c4b34d9b-6624-b0db-9e30-8280f2eeedef.png)


```php
$columns = []; // カルーセル型カラムを5つ追加する配列
foreach ($lists as $list) {
    // カルーセルに付与するボタンを作る
    $action = new UriTemplateActionBuilder("クリックしてね", /* まとめのURL */ );
    // カルーセルのカラムを作成する
    $column = new CarouselColumnTemplateBuilder("タイトル(40文字以内)", "追加文", /* 画像のURL(httpsのみ) */, [$action]);
    $columns[] = $column;
}
// カラムの配列を組み合わせてカルーセルを作成する
$carousel = new CarouselTemplateBuilder($columns);
// カルーセルを追加してメッセージを作る
$carousel_message = new TemplateMessageBuilder("メッセージのタイトル", $carousel);
```

SDKが提供してくれているクラスを読みながら実装しましたが、基本的に型が定義されているので、型に従って実装していくだけです。これだけでカルーセル型のメッセージを作れました。
引っかかったのは、タイトルは40文字制限があって、これを超える場合はエラーになってしまいます。あと、画像もhttpsじゃないと投稿できません。このあたりはAPIドキュメントにちゃんと書いてあるのですが細かくて見逃していました。ただし、LINE側のエラーメッセージが適切なのでなんでエラーになっているのかはすぐに分かるので助かりました。

# Confirm型のメッセージを作る

Confirm型のメッセージを作るにはボタンを作成する必要があります。それを実現するために使うのが`Postback`型のようです。

![写真_2016-10-18_2_27_30.png](https://qiita-image-store.s3.amazonaws.com/0/53041/167af740-843d-933a-073d-56c8f72c84ae.png)

```php
// 「はい」ボタン
$yes_post = new PostbackTemplateActionBuilder("はい", "page={$page}");
// 「いいえ」ボタン
$no_post = new PostbackTemplateActionBuilder("いいえ", "page=-1");
// Confirmテンプレートを作る
$confirm = new ConfirmTemplateBuilder("メッセージ", [$yes_post, $no_post]);
// Confirmメッセージを作る
$confirm_message = new TemplateMessageBuilder("メッセージのタイトル", $confirm);
```
Postbackの引数でQueryぽい形でデータを渡しています。はいを押すと次のページ番号が、いいえの場合は-1が返ってくる仕様にしています。

# メッセージを送信する

あとは上で作成したカルーセル型メッセージとConfirm型メッセージを1つのメッセージにして投げるだけです。`MultiMessageBuilder`を使えば複数のメッセージを1つのメッセージにできるので大変便利でした。

特に、Messaging APIは、送られてきたメッセージに対して返信するのは無料という仕様が追加されているようなので、1つのメッセージに対して複数のメッセージを連続で送れるので表現の幅が広がりますね。
その際、返信であることを証明するためにメッセージにリプライTokenを付ける必要があるようなのですが、これが`$event`に含まれているのでこれを取得して使います。

```php
$message = new MultiMessageBuilder();
$message->add($carousel_message);
$message->add($confirm_message);
// リプライTokenを付与して返信する
$res = $bot->replyMessage($event->getReplyToken(), $message);
```
`$res`の中にAPIの呼び出し結果が格納されるのでエラーなど動作を確認するならここをprintしてチェックしましょう。

ここまででようやくメッセージに対して応答することができるようになりました。次はPostbackイベントからデータを受けてページングをしてみたいと思います。

# エントリーポイント自体は200で正常終了させる

LINEサーバに呼び出されたエントリーポイントはステータスコード200番で正常終了させましょう。私は空のJson`{}`を返却しています。何も返さなくても良いかもしれません。

# Postback経由でデータを受け取る

Confirmに設定したボタンをユーザがクリックするとエントリーポイントにPostbackイベントが流れてきます。Postbackに`page=2`といった文字列を設定しているのでParseしてデータを受け取ってみましょう。

```php
$page = 1;
if ($event instanceof PostbackEvent) {
    // $eventがPostbackEvent型だったらデータを取る
    $query = $event->getPostbackData();
    if ($query) {
        // Querystringをパースして配列に戻す
        parse_str($query, $data);
        if (isset($data["page"])) {
            $page = $data["page"];
        }
    }
}
```
これでページ番号を受け取ることができました。あとは`$page`を元に処理を変えればページングの完成です。この方式を使えば、基本的にどんな処理でも実現できますね。本気でやるならルーティング処理なんかを実装してしまうと簡単かもしれませんね。

# ひとまず完成

以上でLINE@のBot化は成功です。返信しかできませんが、いろいろなメッセージタイプがあるので表現の幅が増えた感じがします。あと返信だけなら無料なのがうれしいです。
Messaging APIの仕様は[こちら](https://devdocs.line.me/ja/)をチェックするのが良いと思いますが、SDKのコードを読むほうが構造は理解しやすいかもです。

# LINEのアプリは最新に

特にiPhoneだと最新のLINEアプリでないと正常動作しないようなので、常に最新にしちゃいましょう。

# 実際に動いているのを確認する

TogetterのLINEアカウントに上記の実装をしているので実際に動くのは友達登録してみてください。
https://line.me/R/ti/p/%40jxs7473s

![togetterqr.png](https://qiita-image-store.s3.amazonaws.com/0/53041/6fdf1c24-0d22-0ef3-1d09-a8ede16dba62.png)

# LINEBotを作って1000万をゲット
LINE主催でLINEBotを作って1000万円がゲットできるコンテストが開催されるようです。締切は来年の1月なのでまだまだ時間はあります。
https://botawards.line.me/ja/

結構簡単に作れるし、表現の制限が厳しいからこそアイデアの絞りがいがあるなーと思いました。作っていて、いろいろと面白そうなボットのアイデアが思いついたので何か作ってみたいです。国内で圧倒的に使われているアプリで大ヒットするBotが作れたら凄いことになりそうです。いま現在、日本国内向けのBotを作るならLINEが一番いいと思いました。

どんなBotが登場してくるかいまから楽しみです。

# 付録

通常メッセージ用のメッセージ

```json
{
 "events":[
  {
   "type":"message",
   "timestamp":12345678901234,
   "source":{
    "type":"user",
    "userId":"userid"
   },
   "replyToken":"replytoken",
   "message":{
    "id":"contentid",
    "type":"text",
    "text":"message"
   }
  }
 ]
}
```

Postback用のメッセージ

```json
{
 "events":[
  {
   "type":"postback",
   "timestamp":12345678901234,
   "source":{
    "type":"user",
    "userId":"userid"
   },
   "replyToken":"replytoken",
   "postback":{
    "data":"page=1"
   }
  }
 ]
}
```

上記、SDK内のサンプルから引用