blog

紫陽花

LINE botでレストラン検索してみた

LINE botが先日公開されました。

f:id:katlez:20160413145434p:plain

公式プレスリリースはこちら。
linecorp.com

LINE CONFERENCE TOKYO 2016で LINE bot オープンAPIを公開する告知があり、
2016.04.07日からトライアルとして1万人限定でAPIの提供が開始されました。

それに伴ってweb界隈では続々とエンジニアの方々が開発を始めています。
技術情報共有サイトQiitaにもLINE botのタグがついた記事がここ一週間程度で30記事ほど上がってきています。

ちょっぴり紹介

qiita.com


qiita.com


qiita.com


こんな記事も見つけた。
blogs.itmedia.co.jp


2016年はbotが一気にくるぞ!やばい!的な記事が結構バズってて、
大げさだな〜とか思いつつ。せっかくだからやってみるか!ってノリで開発してみた。

作ってる最中に似たようなものを作ってる人の記事がリアルタイムで上がってきてて、
先手を取られた〜。と思いつつも、せっかく作ったので記事にしてまとめておく。
なおネタが被ってしまったことに関しては全く気にしていない。
ただ、少し参考にさせてもらった。

構成

構成は、以下でやってみた。

Ruby 2.3.0
Heroku
LINE bot API
ぐるなびAPI

PHPでの開発記事を初めに目にしたので、せっかくだしPHPで開発してみるか〜と思ってたんですが。
Ruby界で有名な風呂グラマーのmasuidrive(増井)さんがRubyで実装した記事を後々見つけて、
こっちの方がすぐ出来そうやんって思ったので速さをとってRubyで実装しました。(慣れてるからね)

オウムbotを作ってみる

ウォーミングアップ

参考にした記事がこちら。
qiita.com

とりあえず動かしてみて感覚を掴みたかったので同じようにオウムbotを作りました。
初めはHerokuだとセキュリティの問題で使えない的な話がありました。
が、後にHerokuにプロキシを貼るサービス(Fixie)を入れれば使えることが判明したため、
下手にサーバを立てなくても簡単にデプロイできるようになりました。



Herokuにデプロイする記事がこちら。
qiita.com

Herokuデプロイ運用について記事にまとめてくれた方に感謝を申し上げたいです。

基本的に記事通りにやれば動くと思います。

レストランを探すbotを作ってみる。

f:id:katlez:20160413152750j:plain


本題に入ります。
構成は上の通りです。

まずLINE でDevelopers登録を行い、以下を取得します。
・Channel ID
・Channel Secret
・MID

これらは登録すればユーザページで確認することができます。

ぐるなびAPIを使います。
ぐるなび Web Service - トップページ

使うにはユーザ登録が必要です。
登録を済ませAPIを使うためのアクセスキーを取得します。

Gemは以下を使いました。

RubyGems

source 'https://rubygems.org'

ruby '2.3.0'

gem 'sinatra'
gem 'rest-client'
gem 'faraday'

Rubyで実装したソースコードを貼っておきます。
とりあえず動きますがエラー処理まで手が伸びていないためこれから実装、改修していきます。
コードも汚いため要リファクタリング。ツッコミ大歓迎です!


require 'sinatra/base'
require 'faraday'
require 'json'
require 'rest-client'

class App < Sinatra::Base
  post '/linebot/callback' do
    # ぐるなびに渡すキーワードの取得
    params = JSON.parse(request.body.read)
    # APIのオブジェクト生成
    conn = Faraday::Connection.new(url: 'http://api.gnavi.co.jp/RestSearchAPI/20150630/') do |builder|
      builder.use Faraday::Request::UrlEncoded
      builder.use Faraday::Response::Logger
      builder.use Faraday::Adapter::NetHttp
    end

    if params['result'][0]['content']['location'].nil?
      send_data = keyword_seach(params, conn)
      p 'keyword側'
      p send_data
      send(params, send_data)
    else
      send_data = location_seach(params, conn)
      p 'location側'
      p send_data
      send(params, send_data)
    end
  end

  def keyword_seach(params, conn)
    search_place = params['result'][0]['content']['text']
    search_place_array = search_place.split("\n")

    if search_place_array.length == 2
      keyword_array = search_place_array[1].split("")
      gnavi_keyword = keyword_array.join()
    end

    # GETでAPIを叩く
    response = conn.get do |req|
      req.params[:keyid] = ENV['GURUNAVI_API_KEY']
      req.params[:format] = 'json'
      req.params[:address] = search_place_array[0]
      req.params[:hit_per_page] = 1
      req.params[:freeword] = gnavi_keyword
      req.params[:freeword_condition] = 2
      req.headers['Content-Type'] = 'application/json; charset=UTF-8'
    end

    json = JSON.parse(response.body)
    result = {}
    result['name'] = json['rest']['name'] if json['rest'].include?('name')
    result['shop_image1'] = json['rest']['image_url']['shop_image1'] if json['rest'].include?('image_url')
    result['address'] = json['rest']['address'] if json['rest'].include?('address')
    result['latitude'] = json['rest']['latitude'] if json['rest'].include?('latitude')
    result['longitude'] = json['rest']['longitude'] if json['rest'].include?('longitude')
    result['opentime'] = json['rest']['opentime'] if json['rest'].include?('opentime')
    return result
  end

  def location_seach(params, conn)
    # 緯度取得
    latitude = params['result'][0]['content']['location']['latitude']
    # 経度取得
    longitude = params['result'][0]['content']['location']['longitude']

    # GETでAPIを叩く
    response = conn.get do |req|
      req.params[:keyid] = ENV['GURUNAVI_API_KEY']
      req.params[:format] = 'json'
      req.params[:hit_per_page] = 1
      req.params[:latitude] = latitude.to_f
      req.params[:longitude] = longitude.to_f
      req.params[:range] = 1
      req.headers['Content-Type'] = 'application/json; charset=UTF-8'
    end
    json = JSON.parse(response.body)
    result = {}
    result['name'] = json['rest']['name'] if json['rest'].include?('name')
    result['shop_image1'] = json['rest']['image_url']['shop_image1'] if json['rest'].include?('image_url')
    result['address'] = json['rest']['address'] if json['rest'].include?('address')
    result['latitude'] = json['rest']['latitude'] if json['rest'].include?('latitude')
    result['longitude'] = json['rest']['longitude'] if json['rest'].include?('longitude')
    result['opentime'] = json['rest']['opentime'] if json['rest'].include?('opentime')
    return result
  end

  # send LINE BOT
  def send(params, result)
    message = {
          # テキスト
          messageNotified: 0,
          messages: [
          {
            contentType: 1,
            text: "お店を見つけました!\n[お店]" + "#{result['name']}" + "\n営業時間" + "#{result['opentime']}"
          },
          # 画像
          {
            contentType: 2,
            originalContentUrl: result['shop_image1'],
            previewImageUrl: result['shop_image1']
          },
          # 位置情報
          {
            contentType: 7,
            text: result['name'],
            location: {
              title: result['address'],
              latitude: result['latitude'].to_f,
              longitude: result['longitude'].to_f
            }
          }
        ]}

    to_array = []
    to_array.push(params['result'][0]['content']['from'])
    request_content = {
      to: to_array,
      toChannel: 1383378250, # Fixed  value
      eventType: '140177271400161403', # Fixed value
      content: message
    }
    endpoint_uri = 'https://trialbot-api.line.me/v1/events'
    content_json = request_content.to_json

    RestClient.proxy = ENV['FIXIE_URL'] if ENV['FIXIE_URL']
    RestClient.post(endpoint_uri, content_json, {
      'Content-Type' => 'application/json; charset=UTF-8',
      'X-Line-ChannelID' => ENV["LINE_CHANNEL_ID"],
      'X-Line-ChannelSecret' => ENV["LINE_CHANNEL_SECRET"],
      'X-Line-Trusted-User-With-ACL' => ENV["LINE_CHANNEL_MID"]
    })
  end
end

今回RubyAPIを叩く際に'net/http'ではなく、'faraday'というGemを使ってみました(新しい挑戦)
faraday | RubyGems.org | your community gem host

使い方は以下を参考

Ruby の faraday をつかって Parse の REST API 叩いてみる - セロリ食べたい
Path API を叩く PHP / Python / Ruby のサンプル - Qiita

使ってみた感想は、めっちゃ使いやすかったです。そして何よりわかりやすかった。

やってみて

公開後まだ6日しか経っていないのですが早速開発記事をwebにあげてくださっている方々のおかげで、簡単に実装を行えました。新しい挑戦になりました。
まだトライアルでアカウント登録上限数に達してないと思うので、この記事を見て興味持った人はすぐにやってみましょう!

今回は簡易的に作ってみましたがLINEbotの登場でかなり可能性が広がった気がします。
アイディア次第で簡単にサービスが作れる!

今後、美容院の予約がbotになったり、レストラン予約がbotになってくるのかな〜
botは便利だけど人との人とのやりとりが極端に薄くなる世界は嫌だな〜と思ったりしました。

余談

本当は"ぐるなびAPI"じゃなくて"食べログAPI"を使ってやろうと考えてました。が、食べログAPIの提供してなかった。。。w
食べログならランキング付けとかされてるのでランキング上位3つの美味しいお店の情報だけを厳選してとれたりしたらいいなと考えてました。くやしい・・・