鯖構築運用雑記

管理してる鯖関連とか技術とかの雑記をメモしておく。

簡単なLINE BOT をPHPで作った話

ブログを書くのがすごく久しぶりな気がしますぞい。 思えば、社会人になってからの初ブログ。うわぁ…

ことの始まり

アンチLINEな私が、算数ができない*1同期のために、LINE BOT を使って簡単な「朝礼順番誰やねんBOT」を作りました。

特段新しいこともなかったのですが、やったことの無いWebプログラミングだったので、そこで躓いたところとかを軽くまとめようかと。*2

対象の方々

  • とにかく、動けばいいやという人
  • 細かい仕様とかどうでもいいから、やり方だけ知りたい人
  • 面倒事は嫌いな人

とにかく、細かい仕組みとか動きとか仕様を気にする方々は、他のブログへGOしてください。
ここで書かれていることは、動くけど、適当な事ですので (伏線)

今回やりたいこと

1. メッセージに反応

主に、順番を先送りにしたり、決定したりするようなやり取り用に使用。
事前に設定した、callbackURLに対して、色々と飛んで来るので、それの対処をすることになる

2. PUSHのみ実行

指定の時間になったら、PUSH(POST)していく系の処理。
これがBOTとのやり取りの最初の一手となるので用意しておく。

結局の流れ

書いていてよくわからなかったので、まとめてみました。

         2. PUSH messages       +-----------------+
 +------------------------------+                 |
 |                              |    Web Server   |
 |                              |    + PHP        |
 |   +-------------------------->                 |
 |   |     1. catch messages    +-----------------+
 |   |        + Reply messages
 |   |
 |   |
 |   |
 |   |
+v---v---------+    PUSH messages       +-------------------+
|              +------------------------>                   |
| LINE BOT API |                        |                   |
|              |                        |  LINE Client APP  |
|              <------------------------+                   |
|              |    post messages       +-------------------+
+--------------+

とにかく、メッセージを流し込んだり、特定の言葉に反応する様な機能をつけたいのです。

実際につけたい機能

  • PUSH は通常のテキスト形式
  • リプライは、テキスト または 選択肢ボタン を送る

という、ボタンを使っていくスタイル。言葉で返すより楽 かつITが上手く使えないLINE大好き人間たちにも扱えそう なので

動かす環境

  • Web サーバ (好きなものを)
  • PHP (WebHook が受け取れれば何でも可)

最低限、これぐらいでしょうか。

Botでしたいことによってですが、PUSHでメッセージのみ送りたいなら、
curlだけでも(やろうと思えば)出来ますので、なにも必要ないという状況も可能っちゃ可能。

実際の環境

  • Nginx(リバースプロキシ) : HTTPS 処理はこちらで実施済み
  • Apache : LINE BOT のフロント役
  • PHP : BOTを実装してるやつ

シンプルなLAMP (※今回はMySQL は使ってないですが) の前にリバースプロキシを設置してるだけです。

PHP を扱うなら、Apacheが楽なので採用 *3

前準備

結構、前準備が大変だったりする

Web サーバ のHTTPS

Let’s Encrypt 辺りでSSL証明書を発行して、Webサーバに設置すれば問題ないでしょう letsencrypt.jp

頭悪い私でも設置できたので、解説は他のサイトに任せるとして、簡単な流れだけおさらいしておきます。

  1. ドメインを取得して、自IPを登録しておく
  2. Let’s Encrypt 設定用スクリプトをサーバに落とす
  3. 起動前準備
    • 予め動いているWebサーバを止める
    • Webサーバを導入していない場合は、ファイヤーウォールで80と443を開けておく + ポートフォワーディング設定を同様に。
  4. Let’s Encrypt 設定用スクリプト実行
  5. 特定の場所に保存されている証明書を使用しているWebサーバに設定する

後は、お好みで更新用cronでも設定してください。 今回はその辺りは略。他にも沢山いい記事あるので私がやることもない気がするので。

LINE Messaging API への登録

自分のLINEアカウントと紐付けの形になりますが、LINE Messaging API へ登録をします。

私言うこともなく、たくさんのサイトでやり方が懇切丁寧に乗っているので、そちらをご参照ください。

まあ、基本日本語なので困らなかったです。

qiita.com

qiita.com

こちらも概要だけ。

  1. LINE Messaging API を始めるから色々と入力する
  2. Developer Trial に登録する
  3. LINE@ Manager にて、BOTの各種設定。
  4. API鍵の発行と保管

API鍵の発行場所を探すのに難儀した気がするので、一応場所をメモ。

作ったbotの設定画面 > アカウント設定 > Bot設定 > ステータス枠内の"LINE Developersで設定する "

まあ、私が解説のサイトで見逃した気がするだけなのですが…

API鍵の取得までできれば、準備完了です。

1. 特定メッセージ反応BOT

なんか、こういうのを表現するのにいい名前があった気がしますが…

全体の流れ

基本的な機構を考えると、以下の流れになるかと思います。

  1. メッセージを受け取る
  2. 特定の文字列のみ(または含まれる)かどうかを判定
  3. 文字列に応じて、メッセージ(返したいモノ)をLINEへ送り返す

書くだけなら簡単。ただし慣れてなかったり、
私みたいな APIヨクワカラナイ人間 には実現が大変だったのです。

オウム返しからつくる

とにかく、メッセージを受け取れるという部分の確認と返し方の基本を学ぶべく、
他サイトでさんざん乗っているオウム返しを作っていく事にします。

やり方もソースもたくさんあるので、自分に合うものを探してください。

今回は、PHPでやるので、以下ページの様なコードをパク参考にして作成します。

LINEbot でオウム返し 2017 PHP版

www.qwintet.co.jp

参考サイトのコードを丸コピして、APIキーを入れればすれば動くので、ここでは詳細は略

オウム返し + 正誤判定をボタンでやる

オウム返しで動くことを確認したら、次はボタンを出したくなりました。

上記参考サイトの下のサイトで書いて有ることを少し書き換えればできそうなので、やってみることに。

で、実際に書いたコード (みたいなもの) はこちら。

<?php
header("charset=UTF-8");

$accessToken = '<取得したAPIキー>';

$jsonString = file_get_contents('php://input');
error_log($jsonString);
$jsonObj = json_decode($jsonString);

// 取得メッセージと送り返す先の指定
$message = $jsonObj->{"events"}[0]->{"message"};
$replyToken = $jsonObj->{"events"}[0]->{"replyToken"};
$messages =  $message->{"text"}
// 受信メッセージ + ボタンでの正誤判定
//テキストタイプ
$messageData = [
'type' => 'text',
'text' => "送信したメッセージ : $messages"
];

// 確認ダイアログタイプ
$messageData2 = [
    'type' => 'template',
    'altText' => '確認ダイアログ',  // ここの文字列が、LINEの通知に表示されるはず
    'template' => [
        'type' => 'confirm',
        'text' => '送ってきたメッセージはこれで間違えないですか?',
        'actions' => [
            [
                'type' => 'message',
                'label' => '問題なし',
                'text' => '問題なし'
            ],
            [
                'type' => 'message',
                'label' => '問題あり',
                'text' => '問題あり'
            ],
        ]
    ]
];

// 返送情報の作成と送信
$response = [
    'replyToken' => $replyToken,
    'messages' => [$messageData,$messageData2]  // 複数送信時は、ここを増やすこと
];
$ch = curl_init('https://api.line.me/v2/bot/message/reply');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($response,JSON_UNESCAPED_UNICODE));
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    'Content-Type: application/json; charser=UTF-8',
    'Authorization: Bearer ' . $accessToken
));
$result = curl_exec($ch);
error_log($result);
curl_close($ch);

実際に動かしてないので動く保証は皆無です。

ポイント

送信するタイプの指定

API へのリファレンスは下記URLに沢山乗っているので、ここを参考にすると、色々なパターンの物が作れます。

LINE API Reference

ここまでわかりやすい公式リファレンスは初めてかもしれません。感謝感激。

送りたいメッセージタイプの指定は、この部分でやっている事になります

<?php

()

//テキストタイプ
$messageData = [
'type' => 'text',
'text' => "送信したメッセージ : $message"
];

// 確認ダイアログタイプ
$messageData2 = [
    'type' => 'template',
    'altText' => '確認ダイアログ',
    'template' => [
        'type' => 'confirm',
        'text' => '送ってきたメッセージはこれで間違えないですか?',
        'actions' => [
            [
                'type' => 'message',
                'label' => '問題なし',
                'text' => '問題なし'
            ],
            [
                'type' => 'message',
                'label' => '問題あり',
                'text' => '問題あり'
            ],
        ]
    ]
];

一つの変数(連想配列) に、色々と引数を渡していることになっています

つまり、メッセージタイプを、

  • 普通のテキストにする場合は、'type' => 'text'
  • 2択式ボタンにする場合は、'type' => 'confirm'

と宣言してから、それぞれ必要な物を追記していきます。

必要なものは、APIリファレンスに書いてあるので、そちらをご参照ください。 書き方は連想配列で大丈夫そうでした。*4

複数メッセージ送信するには

地味にわからなかったのはこの部分。
答えがわかると、一発ですが…

<?php

()

$response = [
    'replyToken' => $replyToken,
    'messages' => [$messageData,$messageData2]  // 複数送信時は、ここを増やすこと
];

$response の配列内、 'messages' => [] に、送りたいメッセージデータ(の連想配列)を渡せば送れました。

特定の文字列に反応させる

まあ、とにかく送られてきたメッセージの中身を含むか完全一致で判定するだけなので詳しくはソースを見てくださいって感じです。

<?php
header("charset=UTF-8");

$accessToken = '<取得したAPIキー>';

$jsonString = file_get_contents('php://input');
error_log($jsonString);
$jsonObj = json_decode($jsonString);

// 取得メッセージと送り返す先の指定
$message = $jsonObj->{"events"}[0]->{"message"};
$replyToken = $jsonObj->{"events"}[0]->{"replyToken"};
$messages =  $message->{"text"}


// strpos で含まれている文字列の検出
if ((strpos($message,'ふええ')) !== false) { 
    // 受信メッセージ + ボタンでの正誤判定
    //テキストタイプ
    $messageData = [
        'type' => 'text',
        'text' => $messages
    ];

    //2択式タイプ
    $messageData2 = [
        'type' => 'template',
        'altText' => '確認ダイアログ',
        'template' => [
            'type' => 'confirm',
            'text' => 'ふええはふくまれていましたか?',
            'actions' => [
                [
                    'type' => 'message',
                    'label' => 'はい',
                    'text' => 'はい'
                ],
                [
                    'type' => 'message',
                    'label' => 'いいえ',
                    'text' => 'いいえ'
                ],
            ]
        ]
    ];

// 完全一致はこのパターン
} elseif ($message == 'いいえ') {
    // テキストタイプ
    $messageData = [
        'type' => 'text',
        'text' => "確認しました。"
    ];

    // テキストタイプ
    $messageData2 = [
        'type' => 'text',
        'text' => "ごめんなさい"
    ];

} elseif ($message == 'はい') {
    // テキストタイプ
    $messageData = [
        'type' => 'text',
        'text' => "確認しました。"
    ];

    $messageData2 = [
        'type' => 'text',
        'text' => "ありがとうございました。"
    ];


}

$response = [
    'replyToken' => $replyToken,
    'messages' => [$messageData,$messageData2]
];
// error_log(json_encode($response));

$ch = curl_init('https://api.line.me/v2/bot/message/reply');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($response,JSON_UNESCAPED_UNICODE));
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    'Content-Type: application/json; charser=UTF-8',
    'Authorization: Bearer ' . $accessToken
));
$result = curl_exec($ch);
error_log($result);
curl_close($ch);

ここまでの応用 + 順番に名前が出てくる機構 を組み合わせると、いい感じに表示されるはず。

2. PUSHのみ実行

さっきまでのはリプライが起点となって送るものでした。
ここからはPUSH、例えば定期的に流し込むものなどが該当します。

送り出し先ID(groupId)を取得

Replyとは異なり、今度は送り出し先を何とかして取得する必要があります。

私は、Botをグループに招待し、その招待時のログ(/var/log/httpd/error.log)から何とかして取得しました。

取得方法は、グループに所属させた瞬間に、下記ログと共に、
groupId が出て来るはずなので、それを取得すると勝ちって感じです。

[Sat May 27 18:30:23.148373 2017] [:error] [pid 9663] [client 172.16.1.22:59169] {"events":[{"type":"join","replyToken":"69df283f534f4e138a6034486b456778","source":{"groupId":"Ca9b205af2bcfdd5932e08af74d7b52c0","type":"group"},"timestamp":1495877397202}]}

"events":[{"type":"join" で参加、それ以下は情報ということがわかります。
今回必要なのでは、"groupId":"Ca9b205af2bcfdd5932e08af74d7b52c0" です。このIDを保管しておきます。

メッセージPUSH用プログラム

LINE API リファレンスに記載通りです。前提となるAPIが変わりますが、中身の感触はほぼ変わりません。

<?php
$access_token = '<取得したAPIキー>';

$url = 'https://api.line.me/v2/bot/message/push';

// データの受信(するものないので不要?)
$raw = file_get_contents('php://input');
$receive = json_decode($raw, true);
// イベントデータのパース(不要?)
$event = $receive['events'][0];

// ヘッダーの作成
$headers = array('Content-Type: application/json',
                 'Authorization: Bearer ' . $access_token);

// 送信するメッセージ作成
$message = array('type' => 'text',
                 'text' => "はろー");

$body = json_encode(array('to' => "<取得したgroupId>",
                          'messages'   => array($message)));  // 複数送る場合は、array($mesg1,$mesg2) とする。


// 送り出し用
$options = array(CURLOPT_URL            => $url,
                 CURLOPT_CUSTOMREQUEST  => 'POST',
                 CURLOPT_RETURNTRANSFER => true,
                 CURLOPT_HTTPHEADER     => $headers,
                 CURLOPT_POSTFIELDS     => $body);
$curl = curl_init();
curl_setopt_array($curl, $options);
curl_exec($curl);
curl_close($curl);

若干変わってますが、ほぼ変わりが無いと思います。

送り出したいメッセージを変えたい場合は、

<?php
()


$message = array('type' => 'text',
                 'text' => "はろー");

の部分を書き換えればOKです。

最後に

今回は、個人的に引っかかった部分を中心に書き連ねました。

実際の構成としては、以下のようにしています。

  • callback.php (1.Reply用)
  • cal.php (順番を数える)
  • push.php (2.Push用)

push.phpは、crontab で定期的に実行するように設定してあります。

以下、実際に作成した3種類のデータです(個人情報と鍵の部分は伏せてあります)

heroku等にそのままデプロイすると動くんじゃないでしょうか?(heroku使ったことない顔)

↓callback.php

↓cal.php

↓push.php

面倒くさがりの方はご参考までに。

そもそも、朝礼なんかやめてしまえとかいうご意見、
共感できるのですが、この記事の存在価値が無くなってしまう*5のでソコだけはご勘弁を…!

*1:言ったのは私じゃなく、同じく同期です。

*2:あと、公開できるネタ記事がなさすぎるので

*3:あと、他で使ってるサーバで既に構築済みだったというのもある

*4:正直PHPヨクワカラナイなので間違えてたらごめんなさい。

*5:記事の存在価値だけです。朝礼自体の存在価値は…オットダレカキタヨウダ