Posted at

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

More than 3 years have passed since last update.

とりあえずオウム返しをするだけの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は反映にちょっとかかる。