プログラミング学習メモ

RubyとRuby on Rails等

rubyでslackの天気botを作った

投稿間隔が空いてしまいましたが、slackのbotを作ったのでその事について描こうと思います。

汚いコードはこちら

github.com

環境

  • macOS Catalina 10.15.2

  • ruby 2.6.4p104 (2019-08-28 revision 67798) [x86_64-darwin18]

利用したサービス

お天気Webサービス仕様 - Weather Hacks - livedoor 天気情報weather.livedoor.com

api.slack.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 APIEvents APIがあり、今回使用したのはRTM APIです。

このRTM APIWebSocketを用いてリアルタイム通信を行いますが、WebSocketや非同期処理で用いるeventmachineは全く理解ができていないので、理解できる範囲で紹介しようと思います。


slack APIを使うには新しいアプリを作る必要があります。

  1. slack apiCreate New Appからアプリを作成します。
  2. 作ったAppのBot Userbotを作成します。
  3. OAuth & PermissionsInstall App to Workspaceワークスペースにアプリをインストールします。
  4. botが追加されたワークスペース内で、任意のチャンネルにbotを追加します

これでOAuth & PermissionsAPIを使える様になる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

参考

【第一回】超簡単!RubyでSlack Botを作る方法 - Studio Andy