とりあえずオウム返しをするだけの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を追加
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サーバからのリクエストを受け付ける。
scope "/linebot", PhoenixHerokuLineBot do
pipe_through :api
post "/callback", LinebotController, :callback
end
オウム返しAPIの実装
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は反映にちょっとかかる。