オフィスから出る前に Slack で雨雲レーダーを確認しよう ⛈️ 

# 重要なおしらせ (2020/06/19)

Yahoo! JAPAN さんの API 提供が 2020/10/31 で終了となるようです。

https://map.yahoo.co.jp/promo/yolp/close_information.html

こちらの記事で紹介しているサンプルコードはそれ以降の動作を保証いたしません。もし代替案をサジェストしてくださる方がいれば、[こちらのリポジトリ](https://github.com/seratch/slack-weather-radar-map)に issue 登録、または pull request をお待ちしております。何らかの方法で対応できた場合はこの記事も更新します。

# そろそろ梅雨明けですが・・

そろそろ今年も、ほぼ全ての地域が梅雨明けしそうな頃合い[^1]ですが、いかがお過ごしでしょうか？

[^1]: [tenki.jp](https://tenki.jp/rainy-season/) で例年の梅雨明けの時期を確認しました。

先日、Twitter でこちらの投稿を見かけまして、確かにこれは便利な Slack 連携だと思いました。 

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">東京アメッシュをSlackに流す的なのがうらやましかった。<br>たまたま雨が降ってきたので見てたら「ん?これは?」となったので昼休みに実装してみた。できたー! <a href="https://t.co/eU1A26zSfS">pic.twitter.com/eU1A26zSfS</a></p>&mdash; しゃある🐰 (@sharlm) <a href="https://twitter.com/sharlm/status/1152082330531422209?ref_src=twsrc%5Etfw">July 19, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

「東京アメッシュをSlackに流す的なのがうらやましかった」と書かれているので、すでにそのような Slack アプリをつくって普段から利用されている方がいらっしゃるのでしょう。素晴らしい Slack API の活用例ですね！ :thumbsup: 

# 試しにつくってみた

アニメーション GIF になるバージョンをつくってみました。雨雲レーダーってこれから雨が降りそうかを正確に判断できるだけでなく、ずっと見ていられる感じもあって、眺めているのも楽しかったりしますよね :smiley:

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19163/0510aab3-90a2-1a57-cf57-5ab63b5386fb.gif" width=400 />

スラッシュコマンドからのリクエストの処理には Slack が最近公開した [Bolt ⚡️](https://slack.dev/bolt/ja-jp) という新しいフレームワークを利用して実装しています。Bolt については、以下の日本語の情報をご覧ください。

* [Bolt 入門ガイド](https://slack.dev/bolt/ja-jp/tutorial/getting-started)
* [Hello World, Bolt! ⚡️ Bolt フレームワークを使って Slack Bot を作ろう](https://qiita.com/girlie_mac/items/93538f9a69eb4015f951)

実装したアプリは以下の GitHub リポジトリで公開しています。

https://github.com/seratch/slack-weather-radar-map

`git.io/amesh` という短縮 URL もつくっておいたので、他の人に教えるときはぜひそちらで！

## 今回対応してみた三つの機能

まず、この Slack アプリがどのように動くものなのか、完成形を見てみましょう。この Slack アプリの基本的な挙動は、スラッシュコマンドで呼び出すと Slack アプリが [Yahoo! JAPAN の API](https://developer.yahoo.co.jp/webapi/map/openlocalplatform/v1/static.html#request-param) から日時をスライドしながら複数枚の画像を取得して、それを一つのアニメーション GIF として応答するというものです。

### デフォルトの表示 - `/amesh {prefecture}`

デフィルトでは、アルファベットの都道府県名（例: tokyo, osaka, aichi）を受け取ると、今の雨雲レーダーから少し先の予測までをアニメーション GIF で表示します。引数指定なしは東京都（tokyo）で動作します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19163/19aec052-be8e-5414-dfa6-63802b0bd867.gif" width=400/>

### 今の状況だけを表示 - `/amesh {prefecture} now`

アニメーション GIF ではなく、今時点での雨雲レーダーだけを表示したい場合は `now` をつけます。画像一枚だけの取得ですし、応答がはやくなります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19163/d689c0ac-fc7e-2011-0422-02dc6d090f96.gif" width=400 />

### 直近一日分を表示 - `/amesh {prefecture} today`

三つ目のオプションは、直近一日の雨雲の流れを見るための `today` オプションです。これはこれから雨が降るかどうか知りたいというよりは、大雨の日などにどの時間帯が一番ピークだったか、後から振り返るときなどに使うとよさそうですね。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19163/93eb0d5a-46ef-bb4e-9f68-a5390a302f87.gif" width=400 />

# コードを変えずにカスタマイズ

私が作ったこのアプリのコードを変えることなく、カスタマイズしたい場合、以下の二点を変更することができます。

## スラッシュコマンド名

デフォルトは `/amesh` ですが、`SLASH_COMMAND_NAME` という環境変数に別の名前を指定して Slack アプリの設定側でも同じ名前を指定すれば、変更することができます。

## マップの表示モード

デフォルトは `map` という設定ですが、[Yahoo! JAPAN の API](https://developer.yahoo.co.jp/webapi/map/openlocalplatform/v1/static.html#request-param) には以下のようなオプションがあります。変更する場合は `YAHOO_JAPAN_API_MAP_MODE` という環境変数に設定してください。

* `map` - 通常の地図
* `photo` - 航空写真
* `map-b1` - 地下街
* `hd` - ハイビジョン地図
* `hybrid` - ハイブリッド地図
* `blankmap` - 白地図
* `osm` - Open Street Map

例えば、以下は `hybrid` で設定した場合の表示例です。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19163/09a53314-c0b3-a466-fbac-2f4fbd48fb92.gif" height=300/>

# ローカルで動かしてみよう

事前に Slack アプリの設定が必要です。まだ作成していない場合は https://api.slack.com/apps にアクセスして「Create New App」ボタンから新しい Slack アプリを作ってください。

## Slack アプリの設定

Slack アプリをつくったら、アプリの設定画面で以下の設定や情報の取得を行います。

### スラッシュコマンドを追加する

左メニューの「Features > Slash Commands」から設定できます。

デフォルトの設定で OK なら `/amesh` という名前でコマンドを追加、Request URL はデプロイ予定の URL を入力します。まだ URL が決まっていない場合はダミーで入れておいてあとで変更することもできます。

### ボットユーザーを追加する

左メニューの「Features > Bot Users」から設定できます。

名前はなんでも OK です。

### 必要な権限(scope)を設定する

左メニューの「Features > OAuth & Permissions」から設定できます。

この手順でやっていれば、すでに `commands` と `bot` は付いていると思いますので、追加で `files:write:user` を指定します。

### アプリをワークスペースにインストールする

左メニューの「Settings > Install App」から実行できます。

インストールが終わったら、発行された「Bot User OAuth Access Token」を `SLACK_BOT_TOKEN` の環境変数の値として使います。また左メニューの「Settings > Basic Information > App Credentials」の「Signing Secret」を `SLACK_SIGNING_SECRET` の値として使用します。

## Yahoo! デベロッパーネットワークの設定

[Yahoo! デベロッパーネットワーク](https://e.developer.yahoo.co.jp/)にアクセスして、Yahoo! JAPAN の Web API を活用するアプリケーションの設定を行います。追加手順は[こちら](https://developer.yahoo.co.jp/start/)で確認することができます。

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19163/3ee4d366-4afc-f6b9-777d-a6494752ab2f.png">

「サーバーサイド（Yahoo! ID連携 v2）」のアプリケーションを作成して、発行された「**Client ID**」を `YAHOO_JAPAN_API_CLIENT_ID` として使用します。

## ローカルで動作確認する

Node.js を使った開発に慣れていれば、ローカルで動かしてみてください。Bolt はこの記事時点で Node 10.13.0 以上で動作するので、サポートされている Node.js をインストールしておいてください。そして必要な環境変数は `_env` というファイルにサンプルがあるのでそれらを指定してください。

```bash
nvm use 10.13.0 # Bolt runs on Node 10.13+
cp -p _env .env
vi .env
source .env
```

その上で必要な npm モジュールをインストールして、アプリを起動してみてください。

```bash
npm i
npm run local
```

上記でアプリが正常に起動しましたか？ この Node アプリは `http://localhost:3000/slack/events` で Slack からのリクエストを処理するよう設定されています。

さらに、もう一つ Terminal ウィンドウを開きます。[ngrok](https://ngrok.com/) という開発用プロキシを使って、この Node アプリが Slack プラットフォームからのリクエストを受け付けられるようにします。

```bash
ngrok http 3000
```

表示された URL を Slack アプリの設定画面の「Features > Slash Commands」に設定したスラッシュコマンドの「Request URL」に設定します。

`npm run local` はコードが変更されると自動的に反映されるように設定されているので、コードをいじっては `/amesh` コマンドで呼び出すというサイクルで試行錯誤ができます。色々と試してみてください。

例えば Yahoo! API の `z` パラメータを変更すれば、より広域な地図に変更して日本全体の雨雲の流れを表示してみたりもできます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19163/e4a0af9b-0068-6e60-42da-3107f1a86b1b.gif" height=300/>

もう少し実用的にするために、会社のオフィスの緯度・経度を基準点に指定して、パラメータを指定しなくても簡単にオフィスの近辺での情報を表示するように変更して運用するとよいかと思います。

また、スラッシュコマンドではなく bot メンションや、キーワードへの反応に変更するなどのカスタマイズも Bolt なら簡単にできるでしょう。メンションされたときの動作は `app.event('app_mention', ...)`、キーワードへの反応は `app.message('雨雲レーダー見せて', ...)` のように実装できると思います。

再掲となりますが、以下のドキュメントも参考にしてみてください。

* [Bolt 入門ガイド](https://slack.dev/bolt/ja-jp/tutorial/getting-started)
* [Hello World, Bolt! ⚡️ Bolt フレームワークを使って Slack Bot を作ろう](https://qiita.com/girlie_mac/items/93538f9a69eb4015f951)

# デプロイしよう

通常の Bolt アプリであれば、どのような環境にも非常に簡単にデプロイが可能です。Heroku だけでなく、AWS API Gateway + Lambda や Google Cloud Functions (Cloud Functions for Firebase も同様）など、様々な環境で Bolt は簡単に動作させることができます。[^2]

[^2]: [いくつか個人的にサンプルを書いたりもしている](https://github.com/seratch/slack-app-examples)ので、見てみてください。 

しかし、今回のサンプルに限っては [node-canvas](https://github.com/Automattic/node-canvas) に依存している事情から AWS Lambda で動かすのはなかなか手間がかかります（詳細は注釈参照）[^3]。この手のアプリは Docker image を作って別の仕組みでで動かすのがよさそうです。

また、Google Cloud Functions / Cloud Functions for Firebase については AWS Lambda のようにネイティブ npm モジュールについての制限は特になく、簡単に動作はさせられますが、今回のように外部 URL への HTTP リクエストを複数投げてアニメーション GIF をつくるような処理はあまり相性がよくないようで、どうしても応答までに長く時間がかかってしまうようでした。そのため、今回、以下に示す例は一枚のみの天気図を表示するサンプルとしました。

[^3]: 興味のある方は [node-canvas の AWS Lambda に関するドキュメント](https://github.com/Automattic/node-canvas/wiki/Installation%3A-AWS-Lambda)と[関連する GitHub issue](https://github.com/Automattic/node-canvas/issues/1448)を見てみてください。簡単に説明すると、今回の場合、 macOS などでパッケージングした zip パッケージは Lambda 上で動作しないため AWS の EC2 環境などで `sls deploy` する必要があります。が、それを行っても、なお `libuuid.so.1` が Lambda 環境だと存在しなくて動作しないため、[Lambda Layers を使って対応](https://github.com/jwerre/node-canvas-lambda)しなければならないようです。

ということで、今回のサンプルをそのまま動かすなら Heroku が最も簡単かと思います。

## Heroku にデプロイする

https://github.com/seratch/slack-weather-radar-map

この GitHub リポジトリは `Deploy to Heroku` ボタンが設置されています。Heroku のアカウントをお持ちなら、今すぐにでも簡単に Heroku にデプロイすることができます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19163/ffb8fb2e-acfc-2cbd-6d53-628751f23ce3.png" width=600 />

いかがでしたか？簡単に動作させることができたのではないかと思います。

## FaaS にデプロイする

Heroku に比べるとシンプルなサンプルとなりますが FaaS へのデプロイについても紹介しておきます。

### Cloud Functions for Firebase

Google Cloud に関しては Cloud Functions fore Firebase の[サンプル](https://github.com/seratch/slack-weather-radar-map/tree/master/cloud-functions-for-firebase)を紹介しています。若干の違いはありますが[^4] Google Cloud Functions でも同様にデプロイできるはずです。

[^4]: 関数を export するときに `functions.https.onRequest` を呼ぶかなどの軽微な違いです。

### AWS API Gateway + Lambda

[AWS API Gateway + Lambda のサンプル](https://github.com/seratch/slack-weather-radar-map/tree/master/serverless-aws)は [Serverless Framework](https://serverless.com/) を使ったサンプルです。 

AWS のサンプルは以下の二つの問題に対処するために少し工夫をしています。

* 同期的に処理をすると特にコールドスタート時に [3 秒以内に応答](https://api.slack.com/slash-commands#responding_basic_receipt)できずにタイムアウトしてしまう
* Lambda の handler が応答を返した後にまだ処理中の Promise での非同期処理が打ち切られてしまう

以下は Lambda functions の定義からの[抜粋](https://github.com/seratch/slack-weather-radar-map/blob/2cb8701f79cd6cfe2d4ab7684e9c60602f2e7af2/serverless-aws/serverless.yml#L29-L36
)です。API Gateway 経由で Internet facing な `frontend` と内部からのみ呼び出される `backend` の二つの Lambda を定義しています。

```yaml
functions:
  # frontend: Slack からのリクエストを受け付けて backend を起動し 3 秒以内に 200 OK を返す
  frontend:
    handler: handler.frontend
    events:
      - http:
          method: post
          path: /slack/events

  # backend: frontend 関数によって非同期実行され、時間がかかる処理を行う
  # エラーが発生したら Slack からもらった response_url に通知する
  backend:
    handler: handler.backend
```

`frontend` は Slack Platform からのリクエストを受け付けます。`frontend`は、無事 `backend` を非同期で起動できたら HTTP リクエストに対して 200 OK を返して終了します。

`backend` は処理を実行し、エラーが発生した場合には [`response_url` を使ってエラーを通知します](https://github.com/seratch/slack-weather-radar-map/blob/2cb8701f79cd6cfe2d4ab7684e9c60602f2e7af2/serverless-aws/app.ts#L111-L112)。 スラッシュコマンドでの `response_url` については[こちらのドキュメント](https://api.slack.com/slash-commands#responding_response_url)を参照してください。

# まとめ

このように [Yahoo! JAPAN さんが公開している API](https://developer.yahoo.co.jp/webapi/map/openlocalplatform/v1/static.html#request-param) を利用すれば、非常に簡単に雨雲レーダーを反映した地図画像を取得することができます。この記事ではそれを複数枚つなぎ合わせてアニメーション GIF をつくり、さらにそれを Slack にファイルとしてアップロードするサンプルをご紹介しました。

https://github.com/seratch/slack-weather-radar-map

そして、その実装は Bolt を活用しました。Bolt を使うと Slack アプリ固有の処理をするコードを最小化して、本質的な実装だけに集中することができるようになります。日本語に翻訳されていますので、[入門ガイド](https://slack.dev/bolt/ja-jp/tutorial/getting-started)を読んでみてください。

ぜひ皆さんの職場やコミュニティのワークスペースでこのスラッシュコマンドを有効にして活用してみてください :raised_hand: