Cloud RunとSlackBotで作るおすすめランチスポットボット

# Cloud RunとSlackBotで作るおすすめランチスポットボット

この記事は ウェブクルー Advent Calendar 2019 22日目の記事です。
昨日は@ytakadama0922さんの「令和になってもまだDC作業で疲弊してるの？」「してるよ」ってお話でした。

## はじめに

みなさんは、お昼ごはんのお店をどうやって選んでいますか？私は、だいたい、同じお店に食べに行く事が多く、最近まんねり化しています。というわけで、この課題をCloud RunとSlackbotを使って今回はいい感じに解決してみたいと思います。

Cloud Runはフルマネージドなサーバーレスプラットフォームで、Dockerコンテナをデプロイするだけで、簡単にHTTPリクエスト経由で呼び出し可能なアプリケーションが作成できます。

一方で、Slackbotには色々機能がありますが、今回は一番簡単な、ワークスペースでの特定の発言に対して、レスポンスを返してくれる機能を使います。Slackのワークスペースのカスタマイズ( https://ワークスペース名.slack.com/customize/slackbot )から設定できる機能です。

以下のように、「おすすめのランチスポットを教えて」と、Slackで発言したら、Slackbotにお店をおすすめしてもらいます。

![おすすめのランチスポットを教えて.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211867/229f24d2-2c03-3b78-35e2-003d461e34c5.png)


## ランチスポットをおすすめするアプリケーションを作成する

今回は、以下のようにSlackbotがURLだけを表示し、そのURLのコンテンツをSlackが展開してくれるような流れにしたいと思います。

![レスポンスの完成イメージ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211867/00dd6f44-0888-04da-c4e0-8c4602251c7e.png)

Slackは、metaタグなどの情報をもとにしてURL先のコンテンツの情報を展開しています。
なので、次のような形で、bodyには情報を記載せずに、metaタグにのみ、APIから取得した情報を展開します。

```
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<meta property="og:title" content="おすすめランチスポットボット" />
<meta property="og:description" content="こちらはいかがでしょう?  「 binwan 2nd 」 ぐるなび: https://r.gnavi.co.jp/evd0w9c00000/?ak=nQtXXu6chstQH09xyrFOXRh%2BhdgGeM3iaywmwCA5r1w%3D ◆【東急田園都市線 三軒茶屋駅 南口A 徒歩4分】
◆充実のワインと手作り料理が楽しめる、三軒茶屋の炉端イタリアン♪
◆大沼牛などの厳選食材をご堪能ください！" />
<meta property="og:url" content="https://r.gnavi.co.jp/evd0w9c00000/?ak=nQtXXu6chstQH09xyrFOXRh%2BhdgGeM3iaywmwCA5r1w%3D" />
<meta property="og:image" content="" />
</head>
<body></body>
</html>
```

今回は、ランチスポットの検索のためにぐるなびAPIのレストラン検索APIを使用して、次のようなリクエストを飛ばし、検索結果をmetaタグに展開します。弊社は三軒茶屋にあるので、エリアコードは"AREAM2166"、ランチ検索のため、オプションに`lunch=1`を入れています。

```
https://https://api.gnavi.co.jp/RestSearchAPI/v3/?keyid=<APIキー>3&areacode_m=AREAM2166&lunch=1&hit_per_page=100
```

エンドポイントが1つの簡単なflaskアプリケーションを書きます。

```python:app.py
import os
import requests
import random

from flask import Flask

app = Flask(__name__)

SEARCH_RESTAURANT_ENDPOINT = "https://api.gnavi.co.jp/RestSearchAPI/v3/"

API_KEY = os.getenv("GURUNAVI_API_KEY")

SANCHA_AREA_CODE = "AREAM2166"

QUERY_OPTIONS = "&lunch=1&hit_per_page=100"

searchQuery = "{}?keyid={}&areacode_m={}{}".format(
    SEARCH_RESTAURANT_ENDPOINT, API_KEY, SANCHA_AREA_CODE, QUERY_OPTIONS)

template = """
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<meta property="og:title" content="おすすめランチスポットボット" />
<meta property="og:description" content="こちらはいかがでしょう?  「 {} 」 ぐるなび: {} {}" />
<meta property="og:url" content="{}" />
<meta property="og:image" content="{}" />
</head>
<body></body>
</html>
"""

def send_search_request():
    return requests.get(searchQuery).json()

def select_restaurant(json):
    total = json["total_hit_count"]
    return random.randrange(total - 1)

def generate_content(json, index):
    restaurant = json["rest"][index]
    name = restaurant["name"]
    url = restaurant["url"]
    image = restaurant["image_url"]["shop_image1"]
    pr = restaurant["pr"]["pr_short"]
    return template.format(name, url, pr, url, image)

@app.route('/')
def find_spot():
    json = send_search_request()
    index = select_restaurant(json)
    return generate_content(json, index)

if __name__ == "__main__":
    app.run(host='0.0.0.0',port=8080)
```

ぐるなびAPIに検索のリクエストを飛ばし、その内容をもとに、metaタグ中心のhtmlのレスポンスとして返します。

## Cloud RunでAPIをエンドポイントを生やす

今回は、簡単なアプリケーションだったので、特に何の工夫もなく、Python用のDockerイメージを作成し、デプロイしました。Dockerfileは以下です。簡単なものです。

```
FROM python:3.7
ENV APP_HOME /app
WORKDIR $APP_HOME
COPY . .
RUN pip install Flask gunicorn requests
CMD exec gunicorn --bind :8080 --workers 1 --threads 8 app:app
```

dockerのビルドとcloud registoryへのpush

```
$ gcloud builds submit --tag gcr.io/yuwki0131-app-runs/sancha-lunch
```

デプロイ

```
$ gcloud run deploy --image gcr.io/プロジェクト名/sancha-lunch
```

これだけで、簡単にエンドポイントを生やす事ができてしまいました。

## Slackbotにエンドポイントを設定する

SlackのワークスペースのカスタマイズのSlackbotの項目から、以下のように設定します。

![ワークスペースのカスタマイズ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211867/62623eea-257a-6088-785e-58ff4bbb0ad0.png)

## ランチスポットをおすすめされたものの。。。

というわけで、冒頭の画像のように、Slack内で雑に「おすすめのランチスポットを教えて」と発言すると、Slackbotがなにかお店を選んで表示してくれるようになったのですが。。。

このmetaタグの情報、Slack全体でグローバルに30分間は、キャッシュされてしまうそうです。。。

> Responses to these requests are cached globally across the service for around 30 minutes. You should not be seeing more than one request for the same URL from us more frequently than that. More about how link unfurling works to display summary content.

[Slack Robots | Slack](https://api.slack.com/robots) より

なので、今回の方式だと、30分おきにしか使えないですね。。。

## おわりに

Slackのリンクの情報が30分キャッシュされる仕様により、botとしてはなかなか残念な出来になってしまいました。。。しかし、ぐるなびのAPIを何度か叩いてみると、意外と自分の知らないお店がレスポンスとして返ってきていたので、ランチスポットのまんねり化を防ぐのに使えるかもしれません。

明日は@kouaresさんの記事になります。よろしくお願いします。

## 参考文献
* [クイックスタート: ビルドとデプロイ  |  Cloud Run  |  Google Cloud](https://cloud.google.com/run/docs/quickstarts/build-and-deploy?hl=ja)
* [ぐるなび Web Service - トップページ](https://api.gnavi.co.jp/api/)
* [Google CloudのCloud Runを試してみた - Qiita](https://qiita.com/TFG/items/b24fbd4a226ccbbfe002)
* [Everything you ever wanted to know about unfurling but were afraid to ask /or/ How to make your site previews look amazing in Slack](https://medium.com/slack-developer-blog/everything-you-ever-wanted-to-know-about-unfurling-but-were-afraid-to-ask-or-how-to-make-your-e64b4bb9254)