rubyでslackの天気botを作った
投稿間隔が空いてしまいましたが、slackのbotを作ったのでその事について描こうと思います。
汚いコードはこちら
環境
利用したサービス
お天気Webサービス仕様 - Weather Hacks - livedoor 天気情報weather.livedoor.com
やったこと
slackでチャンネルにボットを置いて、天気
と都道府県名
が同時に投稿されたとき、該当する都道府県の天気をAPIで取得して、整形した文字列をslackのbotが投稿する。
使ったサービスの紹介+α程度の記事なので、詳しくは作ったbotのコードをご覧ください。
内容
天気APIの取得と解析
今回使ったWeather Hacks
というAPIは、今日明日の天気をJSON形式で提供しているサービスです。認証も必要なく、URLはベースのpathと末尾の都市IDだけ、HTTPリクエストはgetのみと、わりと簡単に情報を得ることができます。
#群馬県前橋市のJSONを取得する例 require "uri" require 'net/http' require "json" MAEBASHI = "http://weather.livedoor.com/forecast/webservice/json/v1?city=100010" #URLをパースしてURIインスタンスを返す uri = URI.parse(MAEBASHI) #引数のURLへのgetリクエストのレスポンスを文字列で返す(引数はURIインスタンスのみ) res = Net::HTTP.get(uri) #文字列をJSON形式でパースして、ハッシュ(と配列)で返す weather = Json.parse(res) #仕様に書いてあるプロパティ名がハッシュのキーとなる #ハッシュの途中で今日か明日か選択する配列が出てくるので注意 #例 weather["forecasts"][0]["date"] #=>"2019-12-20" weather["forecasts"][0]["telop"] #=>"晴のち曇"
加えて都市IDと都道府県名でCSVを作成して、全ての都道府県に対応する処理を作ってみました。
... BASE = 'http://weather.livedoor.com/forecast/webservice/json/v1?city='.freeze ... # 都道府県に応じたAPIのURLを作って配列で返す def generate(text) hash.each_with_object([]) {|(k, v), ary| ary << (BASE + v) if text.include? k.chop } end # csvを読み込んで{'都道府県' => '都道府県ID'}ハッシュで返す def hash @hash ||= CSV.read(listfile).each_with_object({}) {|ary, h| h[ary[0]] = ary[1] } end # 絶対パス生成 def listfile @listfile ||= File.expand_path("api_list.csv", __dir__) end end ...
上のコードは文字列に都道府県名が含まれるなら、その都道府県IDとベースのURLを連結したURLを配列で返します。slackで投稿された文字列を引数にします。
slackAPI
slackのAPIは難しく、まだ理解できていない状態です。
RTM API
とEvents API
があり、今回使用したのはRTM API
です。
このRTM API
はWebSocket
を用いてリアルタイム通信を行いますが、WebSocket
や非同期処理で用いるeventmachine
は全く理解ができていないので、理解できる範囲で紹介しようと思います。
slack APIを使うには新しいアプリを作る必要があります。
- slack apiの
Create New App
からアプリを作成します。 - 作ったAppの
Bot User
でbotを作成します。 OAuth & Permissions
のInstall App to Workspace
でワークスペースにアプリをインストールします。- botが追加されたワークスペース内で、任意のチャンネルにbotを追加します
これでOAuth & Permissions
にAPIを使える様になるBot User OAuth Access Token
トークンが取得できます。
トークンはコードに平文で置かずに、環境変数等に入れて外部に漏れないようにしてください。平文に置いて、手違いでgithubにpushしたらすぐにトークンが無効となりました。
以下がWebSocketでリアルタイム通信を行う手順です。
#トークンを環境変数に入れておく(ここではSLACK_API_TOKEN) require 'http' require 'json' require 'eventmachine' require 'faye/websocket' RTM_API = "https://slack.com/api/rtm.start" #RTM APIのURLにトークンをつけてpostリクエストを送る #戻り値はHTTP::Responseインスタンスを返す response = HTTP.post(RTM_API, params: {token: ENV['SLACK_API_TOKEN']}) #HTTP::ResponseインスタンスのbodyのJSONをパースする rc = JSON.parse(response.body) #JSONからWebSocketで用いるwss通信のアドレスを得る。 url = rc['url'] #非同期処理を行うためにeventmachineの起動 EM.run do # Web Socketインスタンスの立ち上げ ws = Faye::WebSocket::Client.new(url) #onメソッドは引数に一致するイベントによって呼ばれる。 #ブロックパラメータはレスポンスのJSONを含むFaye::WebSocket::API::**インスタンスが代入される # 接続が確立した時の処理 ws.on :open do #処理 end # RTM APIから情報を受け取った時の処理 ws.on :message do |event| #レスポンスのJSONを得る data = JSON.parse(event.data) #チャンネルに投稿する ws.send({type: 'message',text: "メッセージ",channel: data['channel']}.to_json) #等の処理 end # 接続が切断した時の処理 ws.on :close do EM.stop #eventmachineの終了等の処理 end end
参考