LINE BOTをNode.jsで外部依存モジュールを使用せずに作る


[HapiでLINE Botをサクッと試す](http://qiita.com/n0bisuke/items/507661f34da09c71115f)でやった`オウム返し`を依存モジュール無しでやってみます。

![](https://i.gyazo.com/abaf8f06efea5d9b3840c4aa1c8b1932.png)

## 先に完成コード

* Channel Secret
* Channel Access Token

を指定しましょう。

外部依存は無いので`npm i`はいりません。ちなみにNode.jsはv7.0.0です。

app.js、コピペで使えます。

```app.js
'use strict';

const http = require('http');
const https = require('https');
const crypto = require('crypto');

const HOST = 'api.line.me'; 
const REPLY_PATH = '/v2/bot/message/reply';//リプライ用
const CH_SECRET = 'xxxxxxxx'; //Channel Secretを指定
const CH_ACCESS_TOKEN = 'xxxxxx'; //Channel Access Tokenを指定
const SIGNATURE = crypto.createHmac('sha256', CH_SECRET);
const PORT = 3000;

/**
 * httpリクエスト部分
 */
const client = (replyToken, SendMessageObject) => {    
    let postDataStr = JSON.stringify({ replyToken: replyToken, messages: SendMessageObject });
    let options = {
        host: HOST,
        port: 443,
        path: REPLY_PATH,
        method: 'POST',
        headers: {
            'Content-Type': 'application/json; charset=UTF-8',
            'X-Line-Signature': SIGNATURE,
            'Authorization': `Bearer ${CH_ACCESS_TOKEN}`,
            'Content-Length': Buffer.byteLength(postDataStr)
        }
    };
    
    return new Promise((resolve, reject) => {
        let req = https.request(options, (res) => {
                    let body = '';
                    res.setEncoding('utf8');
                    res.on('data', (chunk) => {
                        body += chunk;
                    });
                    res.on('end', () => {
                        resolve(body);
                    });
        });
        
        req.on('error', (e) => {
            reject(e);
        });
        req.write(postDataStr);
        req.end();
    });
};

http.createServer((req, res) => {    
    if(req.url !== '/' || req.method !== 'POST'){
        res.writeHead(200, {'Content-Type': 'text/plain'});
        res.end('');
    }

    let body = '';
    req.on('data', (chunk) => {
        body += chunk;
    });        
    req.on('end', () => {
    	if(body === ''){
          console.log('bodyが空です。');
          return;
        }
        
        let WebhookEventObject = JSON.parse(body).events[0];        
        //メッセージが送られて来た場合
        if(WebhookEventObject.type === 'message'){
            let SendMessageObject;
            if(WebhookEventObject.message.type === 'text'){
                SendMessageObject = [{
                    type: 'text',
                    text: WebhookEventObject.message.text
                }];
            }
            client(WebhookEventObject.replyToken, SendMessageObject)
            .then((body)=>{
                console.log(body);
            },(e)=>{console.log(e)});
        }

        res.writeHead(200, {'Content-Type': 'text/plain'});
        res.end('success');
    });

}).listen(PORT);

console.log(`Server running at ${PORT}`);
```

## 解説っぽいもの

おうむ返しまでの流れはこんな感じのイメージです。

![](https://i.gyazo.com/1000444fe3ee1c7a88355a24293c523b.png)

1. `BOTのWebhookURLをLINE Messaging API側に登録`
2. ユーザーが発言`
3. `LINE Messaging APIからWebhook Event ObjectがBOTに送信される`
4. `BOT側からReply Message APIを叩きます。この時に認証情報が必要です。`
5. BOTが返信

### Webhook Event Object

https://devdocs.line.me/ja/#webhook-event-object

LINEのサーバー側から送られてくるBOTに対するイベント(メッセージを受信、友達追加など)の情報です。

### Reply Messsage API

https://devdocs.line.me/ja/#reply-message

指定した宛先にメッセージを返信するAPIです。

`Webhook Event Object`で送られてくる情報の中から`replyToken`を取得して返信宛先とします。
この時以下の認証が発生します。

#### 認証部分について

https://devdocs.line.me/ja/#webhook 

```
Signature Valdation

リクエストの送信元がLINEであることを確認するために署名検証を行わなくてはなりません。
各リクエストには X-Line-Signature ヘッダが付与されています。
X-Line-Signature ヘッダの値と、Request Body と Channel Secret から計算した Signature が同じものであることをリクエストごとに 必ず検証してください。

検証は以下の手順で行います。

Channel Secretを秘密鍵として、HMAC-SHA256アルゴリズムによりRequest Bodyのダイジェスト値を得る。
ダイジェスト値をBASE64エンコードした文字列が、Request Headerに付与されたSignatureと一致することを確認する。
```

とあるのでcryptoモジュールを使ってSIGNATUREを生成し、X-Line=Signatureヘッダーの値に指定します。

```
・
・
(中略)
・
・

const SIGNATURE = crypto.createHmac('sha256', CH_SECRET);

・
・
(中略)
・
・

        headers: {
            'Content-Type': 'application/json; charset=UTF-8',
            'X-Line-Signature': SIGNATURE,
            'Authorization': `Bearer ${CH_ACCESS_TOKEN}`
        }
        
・
・
(中略)
・
・
```

## おわりに

Node.jsでは公式LINE SDKが出て無いですが、仕組みを把握できると、Node.jsでもけっこうやりたいことがやれる気がしますね。

早くNode.jsむけのSDKがでるといいなぁ。

## 追記: 2017/01/25

リクエストヘッダーに以下の行を追加しました。

```
'Content-Length': Buffer.byteLength(postDataStr)
```

追加してないとサーバーによっては(Azure WebAppsなんですけど)うまく動作しませんでした。
ちゃんと`Content-Length`指定しましょう...汗

> https://nodejs.org/api/http.html#http_http_request_options_callback