LINE botを Phoenix + Elixirで試してみる 


とりあえずオウム返しをするだけのbotを作ってみようと思います。
PHPやRubyでは、すでに色々な人が記事あげてくれていて、同じようにやればできるかなと思ってやってみました。

Phoenix+Elixirはちょっと触ったことがある程度。ライブラリのHTTPoison、Poisonは初めて触ります。herokuもそんなに詳しくないし、LINEのBOT APIも初見の状態です。

## 必要なもの
* LINE BOT APIのアカウント
* Callbackを受け付けるサーバ(https必須)
* bot


## LINEのアカウント登録

1万人限定だったかと思いますが、すでに一杯になってしまったようです。。。
https://business.line.me/ja/products/4/introduction


登録すると以下の情報が発行されますので控えておきましょう。
* Channel ID
* Channel Secret
* MID

 
## Callbackを受け付けるサーバ(https必須)

### サーバー

レンタルサーバもないし、AWSに構築とか面倒だなぁと思ったらherokuでしかも無料でいけるっぽい。
こちらを参考にさせていただきました。

LINE BOT をとりあえずタダで Heroku で動かす
http://qiita.com/yuya_takeyama/items/0660a59d13e2cd0b2516

APIの呼出すために、接続元 IPをLINEに登録する必要があるけれど、
Fixieというaddonを使うことでIPを固定できるとのことです。

### Phoenix + Elixirをherokuで使う
ついでにDockerも使ってみようということで以下を参考にさせていただきました。

Dockerを使ってHerokuへPhoenixアプリをデプロイする
http://tech.misoca.jp/entry/2015/06/26/145324


```
アプリの作成
% mix phoenix.new phoenix_heroku_line_bot
% cd phoenix_heroku_line_bot

dockerプラグインのインストール
% heroku plugins:install heroku-docker

Procfileの作成
% echo "web: MIX_ENV=prod NODE_ENV=production BRUNCH_ENV=production mix phoenix.server" > Procfile

app.jsonの作成
% cat << EOF > app.json
{
  "name": "phoenix_heroku_line_bot",
  "description": "line bot sample",
  "image": "joshwlewis/docker-heroku-phoenix:latest",
  "addons": [
    "heroku-postgresql"
  ]
}
EOF

Dockerfileの作成
% heroku docker:init
% cat << EOF >> Dockerfile

# Compile elixir files for production
ENV MIX_ENV prod

# This prevents us from installing devDependencies
ENV NODE_ENV production

# This causes brunch to build minified and hashed assets
ENV BRUNCH_ENV production

# We add manifests first, to cache deps on successive rebuilds
COPY ["mix.exs", "mix.lock", "/app/user/"]
RUN mix deps.get

# Again, we're caching node_modules if you don't change package.json
ADD package.json /app/user/
RUN npm install

# Add the rest of your app, and compile for production
ADD . /app/user/
RUN mix compile && brunch build && mix phoenix.digest
EOF

```

一旦、DBは使わないので設定せずにこのままデプロイしてみる。

```
% heroku create phoenix-heroku-line-bot
% heroku docker:release --app phoenix-heroku-line-bot
% heroku open --app phoenix-heroku-line-bot

```

この `--app APP`って毎回指定しないといけない？調べたらgit remote追加すればいい。

```
% git init
% git remote add heroku git@heroku.com:phoenix-heroku-line-bot.git
```

https://phoenix-heroku-line-bot.herokuapp.com/
が開いて、phoenixのページが出ればとりあえずデプロイできてる。

### fixieのアドオン追加

```
% heroku addons:create fixie:tricycle
```

https://dashboard.usefixie.com/#/account
herokuのページから追加されたfixieのアドオンのページを開く。
* Proxy URL
* Outbound IPs
を控えておく

### LINEの設定（callback, white IP list)

#### callback
https://developers.line.me/
右上のchanells > 右下のEdit

Callback URLを指定する。ポート番号も指定する必要があることに注意。
https://phoenix-heroku-line-bot.herokuapp.com:443/linebot/callback

これで、`/linebot/callback`というパスでアプリを作ればよい。

#### Server IP Whitelist
左のメニューから、Server IP whitelist
先ほどfixieのアドオン追加時に控えたIPアドレス2つを指定する。

### Phoenixアプリの実装

#### ライブラリの指定
HTTPクライアントとしてHTTPoison、JSONを扱うPoisonを使う。
* depsにhttpoisonとpoisonを追加。
* applicationにhttpoisonを追加

```mix.exs

  def application do
    [
      mod: { HerokuPhoenix, [] },
      applications: [
        :phoenix, :phoenix_html, :cowboy, :logger, :gettext,
        :phoenix_ecto, :postgrex, 
        :httpoison #add
      ]
    ]
  end

  defp deps do
    [
      {:phoenix, "~> 1.1.3"},
      {:phoenix_ecto, "~> 2.0"},
      {:postgrex, ">= 0.0.0"},
      {:phoenix_html, "~> 2.3"},
      {:phoenix_live_reload, "~> 1.0", only: :dev},
      {:gettext, "~> 0.9"},
      {:cowboy, "~> 1.0"},
      {:httpoison, "~> 0.8.0"}, #add
      {:poison, "~> 1.3"} #add
    ]
  end
```

```
% mix deps.get
```

#### callbackパスの設定
/linebot/callbackというURLでLINE APIサーバからのリクエストを受け付ける。

```router.ex

  scope "/linebot", PhoenixHerokuLineBot do
    pipe_through :api

    post "/callback", LinebotController, :callback
  end
```

#### オウム返しAPIの実装

```web/controllers/linebot_controller.ex
  def callback(conn, %{"result" => result}) do
    %{"content" => content} = List.first(result)

    endpoint_uri = "https://trialbot-api.line.me/v1/events"

    json_data = %{
      to: [content["from"]], # 送信先リスト
      toChannel: 1383378250, # 固定値
      eventType: "138311608800106203", # 固定値
      content: %{
        contentType: 1,
        toType: 1,
        text: content["text"] # 受信したメッセージをそのまま返す
      }
    } |> Poison.encode!

    headers = %{   
      "Content-Type" => "application/json; charset=UTF-8",
      "X-Line-ChannelID" => "1814639909", # 自分のLINE Channel IDに変更
      "X-Line-ChannelSecret" => "ef7f43a2fd8a3293dac37183bd0c37b0", # 自分のSecretに変更
      "X-Line-Trusted-User-With-ACL" => "9cac6ca1f4832ebu6d04c1772bd06680e" # 自分のMIDに変更
    }

    options = [
      {:proxy, "http://velodrome.usefixie.com:80"},
      {:proxy_auth, {"fixie", "IelXgZzaM1kyJdS"}}
    ]

    case HTTPoison.post(endpoint_uri, json_data, headers, options) do
      {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
        IO.puts body
      {:ok, %HTTPoison.Response{status_code: 404}} ->
        IO.puts "Not found :("
      {:error, %HTTPoison.Error{reason: reason}} ->
        IO.inspect reason
    end

    send_resp(conn, :no_content, "")
  end
```

受け取るメッセージと送信するメッセージの仕様はここから確認。
https://developers.line.me/bot-api/getting-started-with-bot-api-trial

まず、パターンマッチングでresultを取り出して、配列で返ってきてるので、List.first(result)で一つ目だけ取り出してます。resultが複数返ってくるのがどんな時なのかはわかりませんでした。

resultの中から、送信者のIDとメッセージを取り出してそのままメッセージ送信APIを叩くという処理です。

送信APIを叩くには、HTTPoisonを使って以下の仕様で呼び出せば良い。

ElixirのHTTPoisonライブラリの使い方
http://qiita.com/ColdFreak/items/4beb6cacfbb39baa955f

ここを参考にendpoint_uri、json_data、headersを指定してHTTPoison.postを呼び出す。

* プロトコルはHTTPS
* エンドポイントホスト
	* trialbot-api.line.me
* リクエストヘッダーに以下の3つを指定する
	* X-Line-ChannelID: Channel ID
	* X-Line-ChannelSecret: Channel secret
	* X-Line-Trusted-User-With-ACL: MID (of Channel)
* POST
* エンドポイントURL
	* /v1/events
* BODY
	* to ※ 配列なので注意
	* toChannel ※固定値
	* eventType ※固定値
	* content

APIのリファレンス
https://developers.line.me/bot-api/api-reference

送信元がServer IP whitelistに指定したIPアドレスになるように、proxyを指定する。
HTTPoison.postの第4引数に指定する。

HTTPoisonでプロキシを使用する
http://qiita.com/Kazunori_Sada/items/a8e42545a9b51bcf3bf9#_reference-1d0180aea7b858c9c335

FixieのProxy URLはこんな感じに書いてあるんですけど、ユーザとパスワードが入っていることにしばらく気づかずハマりました。
`http://fixie:IelXgZzaM1kyJdS@velodrome.usefixie.com:80`

### デプロイして動作確認

```
% heroku docker:release
% heroku logs --tail
```

LINEのページのURLから友達登録して、メッセージ送信してみると、/linebot/callbackが呼びだされ、入力したメッセージが返ってくるはず。


### ハマったところまとめ

自分がハマったところをまとめると、
* herokuでDockerを使う方法
　今回の内容とは関係ないけどやってみたくなってしまった。ローカルではdockerをどう使うべき？？
* 送信データのtoは配列。
　他のページを参考にして、そちらでは配列になってたけど見落としてた。
* FixieのProxy URLにユーザ・パスが入ってる。
　なんか、ランダムな文字列のURLが生成されてるんだなぁと思ってたらユーザ・パスだった。
　Proxyのエラーだと、エラーの内容がclosedとしか出てなくて、原因を特定する方法がわからなかった。Proxyの指定を外すとこのIPは許可されていない的なログが出たのでなんとか特定することができた。 

他のサイトで注意しろと書いてあるもの
* callbackのURLにはポート番号をつける。
* callbackやIP whitelistは反映にちょっとかかる。