0
0

More than 5 years have passed since last update.

Slack Botから駅すぱあとWebAPIを顔文字アイコンで対話的に利用してみる話。

Last updated at Posted at 2016-12-08

ヴァル研究所 Advent Calendar 2016、8日目は駅すぱあとWeb APIをSlack Botから使ってみる例を紹介してみようと思います。

Slack Botから駅すぱあとfor web URL生成機能を利用する

5日目の記事でRubyから駅すぱあとWebAPIを使ってみる例を紹介しました。Rubyにはslack-apiという、Slack APIを利用できるライブラリが存在しています。
このライブラリを使って、Slack Botから駅すぱあとWeb APIを使ってみます。

準備

まずはgemの準備です。以下のようにして必要なライブラリをインストールします。

$ cat <<_EOF > Gemfile
source 'https://rubygems.org'
gem 'slack-api'
_EOF
$ bundle install --path vender/bundle

ソースコードは以下のようになります。5日目の記事で解説したWebAPIの呼び出しを、Slackへの投稿に絡めて実行する形になっています。
Slackに投稿された内容(文字列)はdata["text"]で取得できます。今回は「roote:東京から高円寺」のような投稿から駅名(東京、高円寺)を抜き出して駅すぱあと for web URL生成のWrbAPIを呼び出しています。

#!/usr/bin/env ruby
# coding: utf-8

require 'net/http'
require 'uri'
require 'erb'
include ERB::Util
require 'json'

require 'slack'

Slack.configure {|config|
  config.token = ENV['SLACK_TOKEN']
}
client = Slack.realtime

key = ENV['EKISPERT_ACCESS_KEY']
stinfo_cache = {}
icons = [ 
  'one', 'two',   'three', 'four', 'five',
  'six', 'seven', 'eight', 'nine', 'keycap_ten'
]

# URLリクエストを投げる
def request(url_str)
  url = URI.parse(url_str)
  req = Net::HTTP::Get.new(url.request_uri)

  if ENV['http_proxy'] != nil
    ENV['http_proxy'] =~ /http:\/\/(.*?):(.*?)\//
    proxy = Net::HTTP::Proxy($1, $2)
    res = proxy.start(url.host, url.port) {|http|
      http.request(req)
    }
  else
    Net::HTTP.start(url.host, url.port) {|http|
      http.request(req)
    }
  end
end

start_time = Time.now.to_i

client.on :message do |data|
  post_time = data["ts"].sub(/\..*$/, "").to_i
  next if post_time < start_time

  msg = data["text"]

  if msg =~ /^roote:(.*?)から(.*?)$/
    from_stname = $1 
    to_stname   = $2

    begin
      # 出発駅の駅コードを得る
      url_str   = "http://api.ekispert.jp/v1/json/station/light?key=#{key}&name=#{url_encode(from_stname)}"
      from_code = JSON.parse(request(url_str).body)["ResultSet"]["Point"][0]["Station"]["code"]

      # 到着駅の駅コードを得る
      url_str = "http://api.ekispert.jp/v1/json/station/light?key=#{key}&name=#{url_encode(to_stname)}"
      to_code = JSON.parse(request(url_str).body)["ResultSet"]["Point"][0]["Station"]["code"]

      # 探索結果を駅すぱあと for webのURLとして取得する
      url_str = "http://api.ekispert.jp/v1/json/search/course/light?key=#{key}&from=#{from_code}&to=#{to_code}"
      result_url = JSON.parse(request(url_str).body)["ResultSet"]["ResourceURI"]

      result_str = '```' << "\n"
      result_str = result_str << result_url << "\n"
      result_str = result_str << '```'

      param = {
        token:   ENV['SLACK_TOKEN'],
        channel: data["channel"],
        text:    result_str,
        username: "経路探索(#{from_stname}#{to_stname})",
        icon_url: 'https://ekiworld.net/ekiworld/info/images/machinami.png'
      }
    rescue Exception => e
      param = {
        token:   ENV['SLACK_TOKEN'],
        channel: data["channel"],
        text:    "(>_<)\n" << "#{url_str}",
        username: "ERROR",
        icon_url: 'https://ekiworld.net/ekiworld/info/images/machinami.png'
      }
    end
    Slack.chat_postMessage(param)

  end

end
STDERR.puts("ok.")

client.start

スクリプトを実行してみます。

$ bundle exec ruby EkiRouteBot.rb 
ok.

この状態でSlackから「rooute:東京から高円寺」と投稿してみます。

img900.png

Slack Botが駅すぱあと for WEBへの探索結果URLをレスポンスとして返してくれました。

img901.png

URLをクリックすると、探索結果のページが開きます。

img902.png

Slack Botとアイコンで対話的処理してみる

無事にSlack Botから駅すぱあとWebAPIを利用できたのですが、文字列入力→探索結果表示なので、もう一歩すすんだSlack Botの活用を考えてみたいものです。
そこで応用例として、Slack Botの応答に対して、ユーザーが顔文字アイコンでリアクションすることで、Botに対話的な処理をさせてみます。

サンプルの概要

駅すぱあとWebサービスの機能の一つに、駅付加情報があります。これは指定した駅について、乗り入れ路線や最寄り路線、福祉施設情報や出口情報を返す機能です。
例えば、東京駅(駅コードは22828)について得られる駅付加情報の種類を取得してみます。

$ curl -s -XGET 'http://api.ekispert.jp//v1/json/station/info?key=<アクセスキー>&code=22828&type=welfare'|jq '.ResultSet.Information.WelfareFacilities[].Name'
"バリアフリー状況"
"エレベータ"
"エスカレータ"
"トイレ"
"スロープ"
"その他"

以下のような形で、それぞれの駅付加情報を取得できます。

$ curl -s -XGET 'http://api.ekispert.jp//v1/json/station/info?key=<アクセスキー>/v1/json/station/info?code=22828&type=welfare'|jq '.ResultSet.Information.WelfareFacilities[0].Comment'
" ※段差なしでの移動経路\n(○:有り △:要駅員設備 ×:無し)\n【JR東日本】【JR東海】:○\n【東京メトロ】:○"

そこで以下のように「stname:東京」のように入力した場合に、指定した駅の福祉施設情報の候補を一覧表示し、それに対してユーザーが取得したい情報の番号をアイコンで指定するとSlack Botが詳細情報を返してくる、という処理にしてみましょう。

img904.png

ソースコードの解説

ソースコード全体は以下のようになります。Slack Botと対話的に処理している部分について簡単に補足します。

#!/usr/bin/env ruby
# coding: utf-8

require 'net/http'
require 'uri'
require 'erb'
include ERB::Util
require 'json'

require 'slack'

Slack.configure {|config|
  config.token = ENV['SLACK_TOKEN']
}
client = Slack.realtime

key = ENV['EKISPERT_ACCESS_KEY']
stinfo_cache = {}
icons = [ 
  'one', 'two',   'three', 'four', 'five',
  'six', 'seven', 'eight', 'nine', 'keycap_ten'
]

# URLリクエストを投げる
def request(url_str)
  url = URI.parse(url_str)
  req = Net::HTTP::Get.new(url.request_uri)

  if ENV['http_proxy'] != nil
    ENV['http_proxy'] =~ /http:\/\/(.*?):(.*?)\//
    proxy = Net::HTTP::Proxy($1, $2)
    res = proxy.start(url.host, url.port) {|http|
      http.request(req)
    }
  else
    Net::HTTP.start(url.host, url.port) {|http|
      http.request(req)
    }
  end
end

start_time = Time.now.to_i
client.on :reaction_added do |data|
  item = stinfo_cache[data["item"]["ts"]]

  stname = item["stname"]
  stcode = item["stcode"]

  reaction = data["reaction"]

  # 駅情報APIから駅にある福祉施設一覧を取得する
  url = "http://api.ekispert.jp/v1/json/station/info?key=#{key}&code=#{stcode}&type=welfare"
  result = JSON.parse(request(url).body)["ResultSet"]["Information"]["WelfareFacilities"]

  idx  = icons.index(reaction)
  if idx != nil
    name    = result[icons.index(reaction)]["Name"]
    comment = result[icons.index(reaction)]["Comment"]

    param = {
      token:   ENV['SLACK_TOKEN'],
      channel: data["item"]["channel"],
      text:    stname << '駅の ' << name << ' 情報:' << "\n" << comment << "\n",
      username: "駅情報(#{stname}:#{stcode}):",
      icon_url: 'https://ekiworld.net/ekiworld/info/images/machinami.png'
    }
    Slack.chat_postMessage(param)
  end

end

client.on :message do |data|
  post_time = data["ts"].sub(/\..*$/, "").to_i
  next if post_time < start_time

  msg = data["text"]

  username = data["username"]
  if username =~ /駅情報\((.*?):(.*?)\)/
    stname = $1 # 駅名
    stcode = $2 # 駅コード
    ts = data["ts"] # timestamp

    stinfo_cache[ts.to_s] = {} if stinfo_cache[ts.to_s] == nil
    stinfo_cache[ts.to_s]["stname"] = stname
    stinfo_cache[ts.to_s]["stcode"] = stcode
  end

  if msg =~ /^stinfo:(.*?)$/
    stname = $1

    # 駅コードを得る
    url = "http://api.ekispert.jp/v1/json/station/light?key=#{key}&name=#{url_encode(stname)}"
    stcode = JSON.parse(request(url).body)["ResultSet"]["Point"][0]["Station"]["code"]

    # 駅情報APIから駅にある福祉施設一覧を取得する
    url = "http://api.ekispert.jp/v1/json/station/info?key=#{key}&code=#{stcode}&type=welfare"
    result = JSON.parse(request(url).body)["ResultSet"]["Information"]["WelfareFacilities"]

    msg = "#{stname}(#{stcode}):" << "\n"
    result.each_with_index do |item, idx|
      msg = msg << ':' << icons[idx] << ': ' << item["Name"] << "\n"
    end

    param = {
      token:   ENV['SLACK_TOKEN'],
      channel: data["channel"],
      text:    msg,
      username: "駅情報(#{stname}:#{stcode}):",
      icon_url: 'https://ekiworld.net/ekiworld/info/images/machinami.png'
    }
    Slack.chat_postMessage(param)

  end
end
STDERR.puts("ok.")

client.start

ユーザーが「stinfo:東京」と入力した際に取得した駅付加情報をハッシュの形で保持しておきます。ハッシュのキーはレスポンスとして返されたデータのタイムスタンプを使用します。アイコンによるリアクションの際、このタイムスタンプによってリアクションがあった投稿を紐付ける形になります。
具体的には以下の部分です。Slack Botのレスポンスデータのdata["username"]に駅名と駅コードを含めるようにしているので、それをハッシュのデータとして保存しておきます。

client.on :message do |data|
  post_time = data["ts"].sub(/\..*$/, "").to_i
  next if post_time < start_time

  msg = data["text"]

  username = data["username"]
  if username =~ /駅情報\((.*?):(.*?)\)/
    stname = $1 # 駅名
    stcode = $2 # 駅コード
    ts = data["ts"] # timestamp

    stinfo_cache[ts.to_s] = {} if stinfo_cache[ts.to_s] == nil
    stinfo_cache[ts.to_s]["stname"] = stname
    stinfo_cache[ts.to_s]["stcode"] = stcode
  end
  ...

ユーザーがアイコンでリアクションすると、client.on :reaction_added do |data| { ... }ブロックが実行されます。ここでリアクション先の投稿のタイムスタンプとハッシュに保存しておいた駅コードから再度、駅付加情報を取得し、ユーザが指定した番号の駅付加情報詳細を取得します。

client.on :reaction_added do |data|
  item = stinfo_cache[data["item"]["ts"]]

  stname = item["stname"]
  stcode = item["stcode"]

  reaction = data["reaction"]

  # 駅情報APIから駅にある福祉施設一覧を取得する
  url = "http://api.ekispert.jp/v1/json/station/info?key=#{key}&code=#{stcode}&type=welfare"
  result = JSON.parse(request(url).body)["ResultSet"]["Information"]["WelfareFacilities"]

  idx  = icons.index(reaction)
  if idx != nil
    name    = result[icons.index(reaction)]["Name"]
    comment = result[icons.index(reaction)]["Comment"]

    param = {
      token:   ENV['SLACK_TOKEN'],
      channel: data["item"]["channel"],
      text:    stname << '駅の ' << name << ' 情報:' << "\n" << comment << "\n",
      username: "駅情報(#{stname}:#{stcode}):",
      icon_url: 'https://ekiworld.net/ekiworld/info/images/machinami.png'
    }
    Slack.chat_postMessage(param)
  end

end

動かしてみる

さっそく動作を試してみます。「stinfo:東京」と入力して駅付加情報(福祉施設情報)の候補一覧が返されている状態で、顔文字アイコンでリアクションしてみます。

img905.png

想定通りSlack Botが反応し、東京駅のバリアフリー状況を取得することができました。エレベータなどの他の情報についてもアイコンでリアクションすることで同様に取得できます。

img907.png

まとめ

駅すぱあとWebAPIとSlack Botを組み合わせて使用する例を紹介してみました。単純にBotにレスポンスを返させるだけでなく、顔文字アイコンなどでBotに対話的な処理を行わせるようにすると、複数ある駅名候補からユーザーがどれか一つ選択するといった応用ができそうです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0