るいすときのこの物語

オタクエンジニアの雑記

LINE BOT API 実は友達追加時にもCallbackされてた & 署名確認をしよう。


前回こんな記事を書きました。 田村ゆかりさん公式サイトの通知を LINE BOT API で作ってみた。 #yukarin

この記事では友達追加をしてもらった際にメッセージを送ってもらって mid(ユーザーID)を保存するという処理だったのですが、友人が 「友達追加時にもCallbackされるからメッセージ送らなくてもmid保存できるよ」と教えてもらいました。

 

試しに中身を覗いてみた

{
    "result": [
        {
            "content": {
                "message": null, 
                "opType": 4, 
                "params": [
                    "", 
                    null, 
                    null
                ], 
                "reqSeq": 0, 
                "revision": 350
            }, 
            "createdTime": 1461579375186, 
            "eventType": "138311609100106403", 
            "from": "", 
            "fromChannel": , 
            "id": "", 
            "to": [
                ""
            ], 
            "toChannel": 1462173514
        }
    ]
}

注目するのは "opType": 4 です。 友達追加時は 4 ブロック時は 8 が返ってきます。

LINE Developers にも記載されてました...。

FireShot Capture 42 - LINE Developers - BOT API - API refere_ - https___developers.line.me_bot-api

 

友達追加時に、midを保存。 ブロック時にmidを削除といったことが可能ですね。

ということでPHPで友達追加時に相手のmidを保存するコードは以下に。

<?php
/**
 * $otType は 登録時とブロック時にリクエストが飛んで来る。
 * 4 = 友だち追加
 * 8 = ブロック
 */
$f_mids = file_get_contents('./mids');
$content = file_get_contents('php://input');
$content = json_decode($content);
$opType = $content->result[0]->content->opType;
$mid = trim($content->result[0]->from);

/*
 * 最初の登録
 */
if ($opType === 4 && strpos($f_mids, $mid) === false) {
    $text = "ご登録が完了しました。\r\n田村ゆかりさん公式サイトが更新される度にLINEでお知らせいたします。";
    $response_format_text = ['contentType' => 1, "toType" => 1, "text" => $text];
    $post_data = [
        "to" => [$mid],
        "toChannel" => "1383378250",
        "eventType" => "138311608800106203",
        "content" => $response_format_text
    ];

    toPost($post_data);
    file_put_contents('mids', $mid . "\r\n", FILE_APPEND | LOCK_EX);
    die();
}

この一々保存するの面倒なので一斉送信できるAPIを公開してくだしゃい

 

ふとコードを書き直してる時にセキュリティやばくね?ってなって思ってた時に署名とかこないの?って思ったら署名乗ってくるみたいですね...。 Qiitaとか9割方、署名確認してないです。

 

LINEからのアクセスか検証する

LINEからのアクセスの場合、ヘッダーに X-LINE-CHANNELSIGNATURE があり これをbase64デコードしたもの + LINEきたらjsonの内容を LINE BUSINESS CENTER で確認できる ChannelSecret をキーとしたHMAC方式SHA256アルゴリズムのハッシュ値が 合致すればOKということです。

/* LINE からのアクセスか検証 */
$channel_secret = "<チャンネルシークレット>";
$headers = getallheaders();
$content = file_get_contents('php://input');

if (base64_decode($headers['X-LINE-CHANNELSIGNATURE']) === hash_hmac('sha256', $content, $channel_secret, true)) {
    $f_mids = file_get_contents('./mids');
    $opType = $content->result[0]->content->opType;
    $mid = trim($content->result[0]->from);
} else {
    die();
}

 

ちなみに nginx だとPHPの getallheaders 関数が使えないので

function getallheaders()
{
    $headers = '';
    foreach ($_SERVER as $name => $value) {
        if (substr($name, 0, 5) == 'HTTP_') {
            $headers[strtoupper(str_replace(' ', '-', ucwords(str_replace('_', ' ', substr($name, 5)))))] = $value;
        }
    }
    return $headers;
}

よく見るこんなのを使います。