D言語でLINE bot APIをつかってオウム返しbotを書いてみた(簡単な`send`と`receive`の解説も)

今回はD言語でLINE bot APIを使ってオウム返しbotを書いてみました。

あらかじめ、LINE bot APIでAPI Key(Channel IDとかSecretとかのこと)を取得しておいてください(先着10,000人だった気がするのでもう取得できないかもしれないけど...)

まー、やることは単純で

* HTTPSでcallbackを受け取ることが出来るサーバー(or Reverse proxy)を用意する
* POSTリクエストをcallbackとして受け取るコードをかく
* できる

という感じです。

で、ここで問題は1番目の`HTTPSでcallbackを受け取ることが出来るサーバー(or Reverse proxy)を用意する`じゃないかなぁとおもいます。
というのも、僕の調べによると`Let's Encrypt`のSSL証明書は使えないらしいです(試していませんが、`Twitter`や`Qiita`でそのような情報を多く目にしました)。

で、僕は [localtunnle](https://localtunnel.me) というサービスをつかってなんとかしました。
これは、自分のマシンのポートへproxyしてくれるURLを動的に発行してくれます。(適当な説明なので詳しくは自分で調べてください)
まー、適当に調べれば出来ると思うので、適当に3000ポートをproxyするようにしてください。

とりあえず、以下では
`LINE bot API`の設定で`callback`を`https://僕のサーバーのドメイン:443/`とし、`443`を`3000`に飛ばすようにしたものとして話を進めます。

今から`LINE bot API`に関する簡単な説明をしますが、間違っているかもしれないので詳細については公式のReferenceを参照してください。

`LINE bot API`はメッセージ等の`send`と`receive`をするときに、それぞれ異なる処理をしないといけません(当然といえば当然)
それで、`receive`について、`Twitter`の`UserStream`などと違い、`LINE bot API`では`bot`が何かを受け取った時に前述した`callback`に対して`JSON`が入った`POST`リクエストが飛んできます。
ですので、`POST`リクエストを待ち受けるサーバープログラムを書けば`receive`出来ます。


さて、とりあえず、`receive`については`POST`を待ち受けるだけなので特に何も考えずに`vibe.d`でサーバーを書けばD言語で`receive`できそうですね。

コードとしては`URLRouter`に`/`に対する`POST`をハンドルするようにハンドラの関数を登録する感じになりますね。


で、次はメッセージの`send`のほうです。
どうすればいいか、ですが
End Point `https://trialbot-api.line.me/v1/events`に対して`POST`リクエストを送ります。
ここでは、`POST`リクエストの送信について`std.net.curl`の`post`関数を用います。
まず、以下のパラメータをリクエストヘッダにセットします。

      "Content-Type"                 : "application/json; charset=UTF-8",
      "X-Line-ChannelID"             : LINE_CHANNEL_ID,
      "X-Line-ChannelSecret"         : LINE_CHANNEL_SECRET,
      "X-Line-Trusted-User-With-ACL" : LINE_CHANNEL_MID
 
コードとしては、次のようになりますね。

```d
  auto http = HTTP();
  foreach (key, value; 
    [
      "Content-Type"                 : "application/json; charset=UTF-8",
      "X-Line-ChannelID"             : LINE_CHANNEL_ID,
      "X-Line-ChannelSecret"         : LINE_CHANNEL_SECRET,
      "X-Line-Trusted-User-With-ACL" : LINE_CHANNEL_MID
    ]) {
    http.addRequestHeader(key, value);
  }
  ```

それで、リクエストボディに`仕様に沿ったJSON`を突っ込んで`POST`すればいいので、次にその`仕様に沿ったJSON`を作りましょう。

```json:
{
  "to": [送信先のID],
  "toChannel": 1383378250,
  "eventType": "138311608800106203",
  "content":{
    "contentType": 1,
    "toType": 1,
    "text": 送信するメッセージ
  }
}
```

上に示したのが`仕様に沿ったJSON`です。
`toChannel`と`eventType`などのパラメータは`message`を`send`する場合の値なので固定です。
肝心なのは`送信先のID`ですね。これは所謂`LINEのID`, つまり`@XXX`みたいな形式のものではありません。内部のIDとなっていて、自分のLINEアカウントのそれを確認するには一度botに対してPOSTすることで、送られてきた`json`の`from`をみるしかないです(多分)。

でまー、そんな感じで上の`JSON`を`sendJSON`とし、`https://trialbot-api.line.me/v1/events`を`serverURL`とします。これで準備は整ったので`POSTリクエスト`を送信しましょう。

```D:
  post(serverURL, sendJSON, http);
```

さて、こんな感じで`send`が出来るようになりました。
今までの事をまとめて、送られてきたメッセージを鸚鵡返しするコードを考えると下のようなコードが完成します。\(これは、Gistに張ったコードのコピーです 該当Gist: [https://gist.github.com/alphaKAI/5d8bea1e637e3720617033aa5148fa0c](https://gist.github.com/alphaKAI/5d8bea1e637e3720617033aa5148fa0c) \)


```D:
import vibe.d;

import std.stdio,
       std.conv,
       std.json;
import std.net.curl;

enum LINE_CHANNEL_ID     = "YOUR CHANNNEL ID",
     LINE_CHANNEL_SECRET = "YOUR CHANNNEL SECRET",
     LINE_CHANNEL_MID    = "YOUR CHANNNEL MID";

void sendRequestWithJson(string sendJSON) {
  enum serverURL = "https://trialbot-api.line.me/v1/events";

  auto http = HTTP();
  foreach (key, value; 
    [
      "Content-Type"                 : "application/json; charset=UTF-8",
      "X-Line-ChannelID"             : LINE_CHANNEL_ID,
      "X-Line-ChannelSecret"         : LINE_CHANNEL_SECRET,
      "X-Line-Trusted-User-With-ACL" : LINE_CHANNEL_MID
    ]) {
    http.addRequestHeader(key, value);
  }

  writeln("send -> ", sendJSON);
  post(serverURL, sendJSON, http);
}

void sendMessageTo(string to, string message) {
  writeln("to -> ", to);
  writeln("messagee -> ", message);
  string sendJSON = `{"to": [` ~ "\"" ~ to ~ "\"" ~ `], "toChannel": 1383378250, "eventType": "138311608800106203", "content":{"contentType": 1, "toType": 1, "text": "` ~ message ~ `" }}`;

  sendRequestWithJson(sendJSON);
}

void botProc(HTTPServerRequest req, HTTPServerResponse res) {
  writeln("Receive!");
  foreach (result; parseJSON(req.json.to!string).object["result"].array) {
    auto content = result.object["content"];
    string from  = content.object["from"].str;
    string text  = content.object["text"].str;

    sendMessageTo(from, "さーばーえこーにゃん！ : " ~ text);
    // or : sendMessageTo(from, "server echo : " ~ text);
  }
}

shared static this() {
  auto router = new URLRouter;
  router.post("/", &botProc);

  auto settings = new HTTPServerSettings;
  settings.port = 3000;
  listenHTTP(settings, router);

  logInfo("Boot!");
}
```

```D:
enum LINE_CHANNEL_ID     = "YOUR CHANNNEL ID",
     LINE_CHANNEL_SECRET = "YOUR CHANNNEL SECRET",
     LINE_CHANNEL_MID    = "YOUR CHANNNEL MID";
```

を自分のものに置き換えれば動きます。
このコードの動かし方は上に張ったGistの`Installation.md`を参照してください。

動かしてみるとこんな感じ:

<blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">D言語でLINE botつくった(まだオウム返しする機能しか実装してないけど) <a href="https://t.co/FQb9ueL5mY">pic.twitter.com/FQb9ueL5mY</a></p>&mdash; α改 (@alpha_kai_NET) <a href="https://twitter.com/alpha_kai_NET/status/718835949908537344">April 9, 2016</a></blockquote> <script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>


とりあえず、オウム返しbotが出来たので 以前僕が書いた`てふてふ`(Twitter bot)を移植すれば おみくじ機能とか天気機能とか色々をLINE botで実現できそうです(単純な移植は簡単だと思うのでチョロ〜っと手を動かすだけになりそう。だけど、移植元のコードに対して納得が行かない部分が多いので書きなおすことになりそう)。


ではでは、今回はこの辺で。