#はじめに
前回の「Python3の勉強がてらSlackbotを使ってみた」から、
機能を改善したり追加したりしました。
#仕様
-
ぐるなびAPIを利用して、slackで検索ワードを入力してヒットしたURLを返します。
「ご飯 品川 焼き鳥」と打つと、品川の焼き鳥屋っぽい店のURLを返します。
場所のキーワードで検索する際に住所で検索していたため、実際のイメージと違う場所の結果が返ってくるので、エリアマスタを使った検索に変更。 -
ぐるなびAPIを利用した、店名検索です。
「お店 品川 笑笑」と打つと、品川の笑笑のURLを返します。
単純に店名検索です。ただ、間に空白が入るケースには対応できていませんw -
YahooのジオコーダAPIとスタティックマップAPIを利用して、雨雲レーダーの画像を返します。
「雨 品川」と打つと、品川付近の雨雲付き地図画像を返します。地図画像を返す方法はSlackのAPIfiles.upload
を使います。
#環境などなど
- Slack
- Heroku
- Python3.6.0
- lins05/slackbot
- python-pillow/Pillow
画像ファイルを扱うためにPillowを使いました。
#構成
slackbot/
├ plugins/
│ └ slackbot_restapi.py
│ └ restapi.py
│ └ gnaviapi.py
│
└ run.py
└ slackbot_settings.py
└ Procfile(Heroku用ファイル)
└ runtime.txt(Heroku用ファイル)
特に変わっていません。
gnaviapi.pyとslackbot_restapi.pyを変更しています。
ただし、クラス化とかなんとか整理は仕切れていませんw
もう少しキレイに書けると思っています。
#実装
今回は変更分のみです。
前回と違って、仕様ごとの解説です。
##お店検索
"""
Plugin Program
"""
from io import BytesIO
import requests
from requests.exceptions import RequestException
from PIL import Image
from slackbot.bot import listen_to
from plugins.restapi import RestApi
from plugins.gnaviapi import GnaviApi
import slackbot_settings
@listen_to('ご飯')
@listen_to('お店')
def search_restraunt(message):
"""
受信メッセージを元にぐるなびを検索してURLを返す。
場所:エリアMマスタコード(areacode_m) or 住所(address)
キーワード:フリーワード(freeword) or 店舗名(name)
"""
url = 'https://api.gnavi.co.jp/RestSearchAPI/20150630/'
key = 'YOUR_GNAVI_API_TOKEN'
gnavi = GnaviApi(url, key)
search_word = message.body['text'].split()
if len(search_word) >= 3:
try:
params = gnavi.create_params(search_word)
gnavi.garea_middle_fech()
search_area = gnavi.garea_middle_search(search_word[1])
if len(search_area) == 0:
search_area = {'address': search_word[1]}
params.update(search_area)
gnavi.api_request(params)
for rest_url in gnavi.url_list():
message.send(rest_url)
except RequestException:
message.send('ぐるなびに繋がんなかったから、後でまた探してくれ・・・( ´Д`)y━・~~')
return
except Exception as other:
message.send(''.join(other.args))
return
else:
message.send('↓こんな感じで検索してほしい・・・( ̄Д ̄)ノ')
message.send('ご飯 場所 キーワード(文字はスペース区切り)')
message.send('例)ご飯 品川 焼き鳥')
params = gnavi.create_params(search_word)
で、「ご飯」か「お店」を判定して、APIに投げるパラメータをフリーワードか店舗名に切り替えています。
garea_middle_fech()
でぐるなびのエリアMマスタを検索してエリアコードを取得します。
garea_middle_search(search_word[1])
では、Slackで入力された地名に合致する最初のエリアコードを返します。
エリアコードが取得できない場合は、これまで通り住所に対して検索することにします。
あとは前回と一緒です。
"""
ぐるなびAPI
"""
# -*- coding: utf-8 -*-
from requests.exceptions import RequestException
from plugins.restapi import RestApi
class GnaviApi(RestApi):
"""
ぐるなびAPI用クラス
"""
def __init__(self, url, key):
super().__init__(url)
self.key = key
self.garea_s = None
def create_params(self, search_word):
"""
Slackで入力されたキーワードにより、APIのパラメータを変える。
"""
params = {
'format': 'json'
}
if search_word[0] == 'ご飯':
params['freeword'] = search_word[2]
elif search_word[0] == 'お店':
params['name'] = search_word[2]
return params
def url_list(self):
"""
ResponseからレストランURLのリストを作って返す。
"""
json_data = self.response_data.json()
if 'error' in json_data:
raise Exception('そのキーワードじゃ見つかんなかった・・・(´・ω・`)')
if json_data['total_hit_count'] == '1':
return [(json_data['rest'])['url']]
else:
return [rest_data['url'] for rest_data in json_data['rest']]
def garea_middle_fech(self):
"""
ぐるなびAPIからエリアMマスタを取得する。
"""
garea = RestApi('https://api.gnavi.co.jp/master/GAreaMiddleSearchAPI/20150630/')
params = {
'keyid': self.key,
'format': 'json',
'lang': 'ja'
}
try:
garea.api_request(params)
self.garea_s = garea.response_data.json()
if 'error' in self.garea_s:
raise Exception('その場所知らない・・・(´・ω・`)')
except RequestException:
raise RequestException()
def garea_middle_search(self, area_name):
"""
エリアMマスタ内から、area_nameに一致する値を取得する。
(完全一致だと厳しいので、部分一致。)
"""
result_dict = {}
for area_s in self.garea_s['garea_middle']:
if area_s['areaname_m'].find(area_name) >= 0:
result_dict = {'areacode_m': area_s['areacode_m']}
break
return result_dict
↑エリアマスタを探すメソッドをぐるなびAPIクラスに追加しました。
##雨雲検索
"""
Plugin Program
"""
from io import BytesIO
import requests
from requests.exceptions import RequestException
from PIL import Image
from slackbot.bot import listen_to
from plugins.restapi import RestApi
from plugins.gnaviapi import GnaviApi
import slackbot_settings
def search_restraunt(message):
"""
省略!!!
"""
@listen_to('雨')
def search_weather(message):
"""
受信メッセージを元にジオコーダAPIから緯度経度を取得する。
緯度経度を中心に元にスタティックマップAPIから雨雲レーダーの画像を返す。
場所:住所(query)
"""
url_geocoder = 'https://map.yahooapis.jp/geocode/V1/geoCoder'
url_staticmap = 'https://map.yahooapis.jp/map/V1/static'
key_yahoo = 'YOUR_YAHOO_API_TOKEN'
url_slackapi = 'https://slack.com/api/files.upload'
geocoder_api = RestApi(url_geocoder)
staticmap_api = RestApi(url_staticmap)
search_word = message.body['text'].split()
try:
geocoder_api_params = {
'appid': key_yahoo,
'query': search_word[1],
'output': 'json'
}
geocoder_api.api_request(geocoder_api_params)
geocoder_json = geocoder_api.response_data.json()
if 'Error' in geocoder_json:
raise Exception('その場所知らない・・・(´・ω・`)')
coordinates = (((geocoder_json['Feature'])[0])['Geometry'])['Coordinates']
staticmap_api_params = {
'appid': key_yahoo,
'lon': (coordinates.split(','))[0],
'lat': (coordinates.split(','))[1],
'overlay': 'type:rainfall',
'output': 'jpg',
'z': '13'
}
staticmap_api.api_request(staticmap_api_params)
slackapi_params = {
'token': slackbot_settings.API_TOKEN,
'channels': 'C5CJE5YBA'
}
image_obj = Image.open(BytesIO(staticmap_api.response_data.content), 'r')
image_obj.save('/tmp/weather.jpg')
with open('/tmp/weather.jpg', 'rb') as weatherfile:
requests.post(url_slackapi, data=slackapi_params, files={
'file': ('weather.jpg', weatherfile, 'image/jpeg')})
except Exception as other:
message.send(''.join(other.args))
return
ぐるなびAPIと違い、エリアマスタは存在しないようなので住所をベースに緯度経度を取得します。
緯度経度を入手したら、あとは簡単です。画像データの**「入手」**までは簡単でした。
ここまでは・・・
こっから、ドハマりしました。
どうしても画像データをSlackにアップロードできない、と悩みました。
最初はimage_obj = Image.open(BytesIO(staticmap_api.response_data.content), 'r')
を送れば行くだろうと思っていたのですが、全くダメ。
色々試した結果、一度実ファイルを保存してからkopen()
で読み込んで、そのデータを送ることで成功しました。
Herokuでは/tmp
配下であればファイルの保存ができるようなので、image_obj.save('/tmp/weather.jpg')
と保存してから読み込み直しました。
image_obj
はJpgImageFile
オブジェクトだったので、file
オブジェクトと同じだろう、思い込んだのが敗因でしょうか。PngImageFile
にしたり、image_obj = BytesIO(staticmap_api.response_data.content)
としてから、getvalue()
やgetbuffer()
を使ってみたりして3日くらい悩みましたw
#終わりに
requests.post()
の仕様を調べていますが、なぜJpgImageFile
が送れないのかは原因がわかっていません。引き続き調べますが、どなたか知っている方がいれば情報ください。
#追記(20170701)
コメントをいただきまして、
image_obj = Image.open(BytesIO(staticmap_api.response_data.content), 'r')
image_obj.save('/tmp/weather.jpg')
with open('/tmp/weather.jpg', 'rb') as weatherfile:
requests.post(url_slackapi, data=slackapi_params, files={
'file': ('weather.jpg', weatherfile, 'image/jpeg')})
この箇所を、
output = BytesIO()
image_obj = Image.open(BytesIO(staticmap_api.response_data.content), 'r')
image_obj.save(output, 'jpeg')
requests.post(slackbot_settings.API_URL, data=slackapi_params, files={
'file': ('weather.jpg', output.getvalue(), 'image/jpeg')
})
と修正して動かしたところ、動きました!
なぜ、今ままで動かなかったのか。
何はともあれ、ありがとうございました!