python:LINEの位置情報で近くのカレー屋さんを通知する
今回は、LINE Messenger APIを使って、位置情報を入力することで近くのカレー屋さんを連絡してくれるプログラムを作ってみました。
LINEチャンネル『最寄りカレー』
参考にしたサイト
おおまかな流れ
①LINEで位置情報を受け取る
②位置情報をもとにぐるなびAPIを使って、近くのカレー屋を抽出
③カレー屋の情報を通知
完成コード
app.py
import sys
import json
import os
from flask import Flask, request, abort, send_file
from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import (
CarouselColumn, CarouselTemplate, FollowEvent,
LocationMessage, MessageEvent, TemplateSendMessage,
TextMessage, TextSendMessage, UnfollowEvent, URITemplateAction
)
import requests
import urllib.parse
app = Flask(__name__)
#環境変数の設定
channel_secret = os.environ['channel_secret']
channel_access_token = os.environ['channel_access_token']
gurunavi_api = os.environ['gurunavi_api']
#LINE APIの設定
line_bot_api = LineBotApi(channel_access_token)
handler = WebhookHandler(channel_secret)
#その他
no_hit_message = "近くにカレー屋さんはないようです"
DAMMY_URL = "https://canbus.com/blog/wp-content/uploads/2018/02/2015-tenpo.jpg"
#テスト用
@app.route("/")
def hello_world():
return "hello world!"
#関数の呼び出し処理
@app.route("/callback", methods=['POST'])
def callback():
signature = request.headers['X-Line-Signature']
body = request.get_data(as_text=True)
app.logger.info("Request body: " + body)
try:
handler.handle(body, signature)
except InvalidSignatureError:
abort(400)
return 'OK'
#ぐるなび検索
def search_rest(lat, lon):
url = "https://api.gnavi.co.jp/RestSearchAPI/v3/"
params = {}
params['latitude'] = lat
params['longitude'] = lon
params['keyid'] = gurunavi_api
params['range'] = 3
params['freeword'] = "カレー"
response = requests.get(url, params)
results = response.json()
if "error" in results:
if "message" in results:
raise Exception("{}".format(results["message"]))
else:
raise Exception(DEF_ERR_MESSAGE)
total_hit_count = results.get("total_hit_count", 0)
if total_hit_count < 1:
raise Exception(no_hit_message)
return results
#メイン処理
@handler.add(MessageEvent, message=LocationMessage)
def handle_location_message(event):
list = []
user_lat = event.message.latitude
user_longit = event.message.longitude
rest_result = search_rest(user_lat, user_longit)
for rest in rest_result.get("rest"):
image_url = rest.get("image_url", {})
image1 = image_url.get("shop_image1", "thumbnail_template.jpg")
if image1 == "":
image1 = DAMMY_URL
name = rest.get("name", "")
url = rest.get("url", "")
pr = rest.get("pr", "")
pr_short = "以下、内容\n" + pr.get("pr_short", "")
if len(pr_short) >= 60:
pr_short = pr_short[:56] + "…"
result_dict = {
"thumbnail_image_url": image1,
"title": name,
"text": pr_short,
"actions": {
"label": "ぐるなびで見る",
"uri": url
}
}
list.append(result_dict)
print(list)
columns = [
CarouselColumn(
thumbnail_image_url=column["thumbnail_image_url"],
title=column["title"],
text=column["text"],
actions=[
URITemplateAction(
label=column["actions"]["label"],
uri=column["actions"]["uri"],
)
]
)
for column in list
]
messages = TemplateSendMessage(
alt_text="お近くのカレー屋さんについて連絡しました。",
template=CarouselTemplate(columns=columns),
)
line_bot_api.reply_message(event.reply_token, messages=messages)
if __name__ == "__main__":
port = int(os.getenv("PORT", 5000))
app.run(host="0.0.0.0", port=port)
API関連初期準備
LINE Messenger APIとぐるなびAPIの登録が事前に必要です。
前処理と実行処理
import sys
import json
import os
from flask import Flask, request, abort, send_file
from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import (
CarouselColumn, CarouselTemplate, FollowEvent,
LocationMessage, MessageEvent, TemplateSendMessage,
TextMessage, TextSendMessage, UnfollowEvent, URITemplateAction
)
import requests
import urllib.parse
app = Flask(__name__)
#環境変数の設定
channel_secret = os.environ['channel_secret']
channel_access_token = os.environ['channel_access_token']
gurunavi_api = os.environ['gurunavi_api']
#LINE APIの設定
line_bot_api = LineBotApi(channel_access_token)
handler = WebhookHandler(channel_secret)
#その他
no_hit_message = "近くにカレー屋さんはないようです"
DAMMY_URL = "https://canbus.com/blog/wp-content/uploads/2018/02/2015-tenpo.jpg"
#テスト用
@app.route("/")
def hello_world():
return "hello world!"
#関数の呼び出し処理
@app.route("/callback", methods=['POST'])
def callback():
signature = request.headers['X-Line-Signature']
body = request.get_data(as_text=True)
app.logger.info("Request body: " + body)
try:
handler.handle(body, signature)
except InvalidSignatureError:
abort(400)
return 'OK'
上記の部分は、いつも通りですね。
no_hit_messageは、ぐるなびで検索の結果が出ない場合に表示されるメッセージ。
DAMMY_URLは、店舗のイメージ画像がない場合に設定される画像を適当に入れています。
ぐるなび検索
def search_rest(lat, lon):
url = "https://api.gnavi.co.jp/RestSearchAPI/v3/"
params = {}
params['latitude'] = lat
params['longitude'] = lon
params['keyid'] = gurunavi_api
params['range'] = 3
params['freeword'] = "カレー"
response = requests.get(url, params)
results = response.json()
if "error" in results:
if "message" in results:
raise Exception("{}".format(results["message"]))
else:
raise Exception(DEF_ERR_MESSAGE)
total_hit_count = results.get("total_hit_count", 0)
if total_hit_count < 1:
raise Exception(no_hit_message)
return results
上記では、ぐるなびAPIを使って、位置情報の半径2,000m以内のキーワード『カレー』を含む店舗のjsonデータを取得しています。
各種パラメータについては、下記のURLに記載されています。
メイン処理
@handler.add(MessageEvent, message=LocationMessage)
def handle_location_message(event):
list = []
user_lat = event.message.latitude
user_longit = event.message.longitude
rest_result = search_rest(user_lat, user_longit)
for rest in rest_result.get("rest"):
image_url = rest.get("image_url", {})
image1 = image_url.get("shop_image1", "thumbnail_template.jpg")
if image1 == "":
image1 = DAMMY_URL
name = rest.get("name", "")
url = rest.get("url", "")
pr = rest.get("pr", "")
pr_short = "以下、内容\n" + pr.get("pr_short", "")
if len(pr_short) >= 60:
pr_short = pr_short[:56] + "…"
result_dict = {
"thumbnail_image_url": image1,
"title": name,
"text": pr_short,
"actions": {
"label": "ぐるなびで見る",
"uri": url
}
}
list.append(result_dict)
print(list)
上記では、以下のような流れでコードを作っています。
①空のリストを作成
②LINEからの位置情報で緯度と経度を受け取る
user_lat = event.message.latitude
user_longit = event.message.longitude
③②の情報から、ぐるなび検索関数を実行
④リクエストパラメータのrestに絞って、for文で各情報を抽出
.get関数は、.get(辞書のキー, キーがない場合の値)
pr_shortでは、店舗の内容を入れ込んでいますが、空の場合と60文字以上の場合は、エラーが出るので、if関数で対応
⑤辞書型にして、リストに格納(のちに使用するLINEカルーセルのため)
LINEカルーセル
columns = [
CarouselColumn(
thumbnail_image_url=column["thumbnail_image_url"],
title=column["title"],
text=column["text"],
actions=[
URITemplateAction(
label=column["actions"]["label"],
uri=column["actions"]["uri"],
)
]
)
for column in list
]
messages = TemplateSendMessage(
alt_text="お近くのカレー屋さんについて連絡しました。",
template=CarouselTemplate(columns=columns),
)
LINEカルーセルは、横にスライドできる画像付きのボックスみたいなものです。
LINEカルーセルのリファレンスに従って、各項目を先ほど作った辞書を使って、入力していく。
終わりに
フリーワードをカレーとしていますが、実際のところ、カレーが店舗情報にさえあれば、カレー屋以外の店舗も出てきてしまうので、そこは改善の余地があるかもしれません。