Slack+Google Apps Scriptで天気予報を作ろう

Slackのワークスペースにて**「外に出る時間の天気が自動でSlackに投稿される機能が欲しい」**と要望があったので、Google Apps Scriptを使ってSlackに天気予報を流せるようにしました。

##### 参考にさせていただいた記事一覧 

- [Slackに天気や予定を毎朝通知する Google Apps Script](https://qiita.com/maatumoDev/items/f9497d5b37757db1b276)
- [Slash CommandsとGASでSlackのオリジナルコマンドをつくる](https://qiita.com/chikuwa111/items/7a1a349b82318a5861cc)
- [Slackのコマンドを作ろう!!](https://qiita.com/t-mimura/items/d6541ec596bdebea5a7b)

## 実装したもの
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/513327/372a1b71-5a21-dfec-f19f-f54d18b7e1ae.png)

- 時間になったら自動的に設定した時間の天気を投稿するもの
- Slashコマンド`/weather`で時間を指定して投稿するもの

この2種類の機能を一つのGASファイルで実装しました。
実装のために書いたコードは[github](https://github.com/Ogijun2018/weatherForecast)に上げているので自由にcloneして使ってください。

## 使ったもの
- **外部API**  
今回は1時間おきの気温・降水確率・降水量の情報が最低限知りたかった（雨が降りそうかどうか知りたかった）ので、その条件を満たすDark Sky APIを使用しました。  
天気予報のAPIは他にもいろいろとあります。  
 - [Dark Sky API](https://darksky.net/dev)
 - [天気予報API](https://tenkiapi.jp/)  
 - [Yahoo! 気象情報API](https://developer.yahoo.co.jp/webapi/map/openlocalplatform/v1/weather.html)  
 - [livedoor 天気予報](http://weather.livedoor.com/weather_hacks/)  

これらのAPIから取れる情報は各APIによって様々（何時間おき、週間天気、紫外線など）なので、欲しい情報が得られるAPIを探しましょう。

- **Google Apps Script**  
Google Apps ScriptはGoogle社が提供しているサーバサイドスクリプト環境です。  
サーバサイドの勉強を何もしていない私でも簡単に使うことができました。
詳しい使い方は下で説明します。

## 定時に天気予報を通知する
はじめに、定時をトリガーにして特定の時間の天気予報をSlackで自動的に通知する機能を作ります。

### カスタムインテグレーションの設定
まず最初に、Slackのワークスペース名から「その他管理項目」→「App管理」を開きます。
「その他管理項目」が表示されない場合は、そのワークスペースでAppなどをカスタマイズする権限を持っていない可能性があります。ワークスペースの管理者に聞いてみましょう。

App管理画面を開いたら、上の「Appディレクトリを検索」で「Incoming Webhook」を検索します。
Incoming Webhookは、「外部サービスからSlackにメッセージを送信する」ための機能です。この機能を使うと、外部APIからもらったメッセージを送ることができます。
**<font color="red">注意: カスタムインテグレーションは非推奨となり、今後廃止される予定です。この記事もアプリへの置き換えをしていずれ更新しようと思います。**

Incoming Webhookを追加すると、設定の編集画面に移動します。
![IMG_0114.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/513327/007968ad-0584-b1f4-31cb-2be3e33f136f.png)
ここでは、以下のことを設定・確認することができます。

- どのチャンネルに投稿するかの設定
- どんな名前、アイコンで投稿するかの設定
- Webhook URLの確認

大事なのはWebhook URLです。このURLをGoogle Apps Scriptで送信の際に入れることで、Slackに投稿されるという仕組みです。

### 外部APIの設定
次に、外部APIの設定をします。Dark Sky APIを例にとると、登録完了後このような画面が出てきます。
![IMG_0103.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/513327/4b42b139-a661-c11e-31e5-ac632e498618.png)
大事なのはSample API CallのURLです。このURLをGoogle Apps Scriptで受信の際に入れることで、APIから情報を受け取れるという仕組みです。どのAPIを使っても、このようなURLが登録後現れると思うので、コピーしておきましょう。

各APIにはどのような値が返ってくるのかなどの情報が記載されたドキュメントがあります。ドキュメントを見ながら自分が取り出したい情報を指定しましょう。

ちなみに、上記のSample API CallのURL末尾の数字は緯度と経度を示しています。天気予報を受け取りたい地点の緯度と経度を入れましょう。

### Google Apps Scriptの設定
新規プロジェクトの作り方は、[こちらの記事](https://qiita.com/t_imagawa/items/47fc130a419b9be0b447)の「事前準備」を参考にしましょう。（とても参考になりました）
プロジェクトが作成され、ここに書かれたコードが実行されます。


```JavaScript:weatherForcast.gs
 function myFunction() {
  var url = "https://api.darksky.net/forecast/[ID]/[緯度,経度]?lang=ja&units=si";
  var response = UrlFetchApp.fetch(url);

  var json = JSON.parse(response.getContentText());
  var text = "";
  var dailyData = json["daily"]["data"][0];
  var hourlyData;
  
  text += "・今日の天気: " + dailyData.summary + "\n";
  text += "・日没時刻: " + unixTime2jst(dailyData.sunsetTime, "hms") + "\n\n";
  
  for(var i = 4; i < 8; i ++){
    hourlyData = json["hourly"]["data"][i];
    text += "     　　     ＊" + unixTime2jst(hourlyData.time, "mdh") + ":00＊\n";
    text += "気温: " + hourlyData.temperature + "℃\n";
    text += "降水確率: " + hourlyData.precipProbability + "%\n";
    text += "降水量: " + hourlyData.precipIntensity + "mm/h\n";
    text += "風速: " + hourlyData.windSpeed + "m/s\n";
    text += "-----------------------------------------\n";
  }
  var data={
    "text": text,
    "icon_emoji": ":" + dailyData.icon + ":",
    "username": "今日のお空はどんな空？",
  };
  
  var options =
  {
  "method" : "POST",
  'contentType': 'application/json',
  'payload' : JSON.stringify(data),
  };
  // SlackのIncoming WebhookのURLを取得して入力
  UrlFetchApp.fetch("https://hooks.slack.com/services/[ID]", options);
}
```
私が実装したコードはこんな感じです。
`text`に送信したいメッセージを入れていきます。
今回は**今日の1時間ごとのデータ**が欲しかったので、APIから受け取ったデータのJSONファイルから`["daily"]["data"][0]`を指定しました。

Dark Sky APIは現在・分ごと・時間ごと・日にちごとetc...のデータが取れるので、変えたい場合は一つ目の`["daily"]`部分の配列を変えましょう。

また、三つ目の`[0]`は現在が0、1時間後が1、2時間後が2...となっています。for文で回すことでこのコードでは4時間後〜8時間後の天気を表示しています。

先ほど設定した際のIncoming WebhookのURLと外部APIのURLを `UrlFetchApp.fetch()`に渡すことで、受信と送信をしていることがわかるでしょう。

ここに記述されていない関数などが見たい方は、[こちら](https://github.com/Ogijun2018/weatherForecast)からご覧ください（不具合あれば連絡ください）。

コードが完成したら、実行ボタンの左隣にあると時計マークのアイコンをクリックしましょう。ここからトリガーを設定できます。

実行する関数を先ほどのコードの関数（今回だとmyFunction）に設定することで、指定した時間にmyFunctionが実行され、Slackに自動的に投稿されます。
<img width="386" alt="スクリーンショット 2019-10-13 18.26.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/513327/a123f2f0-fd04-d245-6b1a-2ed42d2e92f0.png">
このような感じで投稿されます。これで定時投稿は完成です！

## スラッシュコマンドで指定した時間の天気を表示する
次に、`/weather`というコマンドから指定した時間の天気を表示する機能を作りましょう。

今回は、`td(today)` `tm(tomorrow)` `dtm(day after tomorrow)`の後に時刻を入れることで、指定した日付・時刻の天気を表示するようにしました。また、数字単体の場合は数字分の時間後の天気を表示します。

例: 
`/weather td22` ...今日の22時の天気
`/weather tm12-14`...明日の12時〜14時の天気
`/weather dtm20`...明後日の20時の天気
`/weather 3`...今から3時間後の天気

### Google Apps Scriptの設定
先ほどのGoogle Apps Scriptのコードに、`doPost`という関数を追加します。
GASでは、`doPost()`でPOSTリクエストを受け取るようになっているため、スラッシュコマンドで使うコードは`doPost()`の中に書きます。
（参考: [Slash CommandsとGASでSlackのオリジナルコマンドをつくる](https://qiita.com/chikuwa111/items/7a1a349b82318a5861cc))

```JavaScript:weatherForecast.gs
function doPost(e){
  var parameter = e.parameter.text;
  var now = new Date();
  
  var url = "https://api.darksky.net/forecast/[ID]/[緯度,経度]?lang=ja&units=si"
  var response = UrlFetchApp.fetch(url);

  var json = JSON.parse(response.getContentText());
  var res;
  var text = "";
  
  if(parameter.match(/td/)){
    //今日の天気
    var Time = parameter.slice(2);
    //3文字以上かつ"-"があるときは範囲選択
    if(Time.length > 2 && Time.match(/-/)){
      var splitTime = Time.split("-");
      var subHour = splitTime[0] - now.getHours();
      var term = splitTime[1] - splitTime[0];
      if(splitTime[0] >= 24 || splitTime[1] >= 24){
        return postResult('日付をまたぐ場合はtdとtmを使って別々に実行してください');
      }else if(subHour < 0){
        return postResult('過去は出力できません');
      }
      for(var i=0; i < term + 1; i++){
        var targetArray = json["hourly"]["data"][subHour + i];     
        text += makeTextArray(targetArray);
      }
      return postResult(text);
    }else{
      //指定された時間1時間分の予報を出す部分
      var subHour = Time - now.getHours();
      if(Time >= 24){
        return postResult('24時を超える場合はtmを指定してください');
      }else if(subHour < 0){
        return postResult('過去は出力できません');
      }
      var targetArray = json["hourly"]["data"][subHour];
      return postResult(makeTextArray(targetArray));
    }
  }else if(parameter.match(/dtm/)){
    //明後日の天気を調べる
    var Time = parameter.slice(3);
    var todayLeftTime = 24 - now.getHours();
    if(Time.length > 2){
      var splitTime = Time.split("-");
      var subHour = 24 + parseInt(todayLeftTime) + parseInt(splitTime[0]);
      var term = splitTime[1] - splitTime[0];
      if(subHour > 48){
        return postResult('48時間を超える予報は出せません!');
      }
      for(var i=0; i < term + 1; i++){
        var targetArray = json["hourly"]["data"][subHour + i];
        text += makeTextArray(targetArray);
      }
      return postResult(text);
    }else{
      //指定された時間1時間分の予報を出す部分
      var subHour = 24 + parseInt(todayLeftTime) + parseInt(Time);
      if(subHour > 48){
        return postResult('48時間を超える予報は出せません!');
      }
      var targetArray = json["hourly"]["data"][subHour];
      return postResult(makeTextArray(targetArray));
    }
  }else if(parameter.match(/tm/)){
   ...
```
先ほどのSlackに送信するところのコードと、`text`を生成するコードは別の関数にまとめました。

単純な文字列比較でtd,tm,dtmを判別し、指定された範囲の予報をreturnするようにしています（想定外の文章を送るとすぐにエラーが出るのは許してください）。

引数`e`の中にスラッシュコマンドと一緒に指定した文字列（`"td20"`など）が入っています。`e.parameter.text`で取り出しましょう。

コードが完成したら、「公開」タブの「ウェブアプリケーションとして導入」をクリックします。
![IMG_0116.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/513327/bab73f2c-6a2a-a058-66fb-fa8e0a939949.png)
この画面が出てこない場合は、共有設定がされていないことが原因だと考えられます。一旦Google Driveにアクセスし、作成中のGASファイルを右クリック→「共有」をクリックすることで設定が変更できます。
<img width="532" alt="スクリーンショット 2019-10-13 19.04.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/513327/cdeafc76-0f7c-3c1f-a68d-437e546fb4c9.png">
「インターネット上の誰でも検索、閲覧可」の状態にすると先ほどの公開が可能になります。

「アプリケーションにアクセスできるユーザー」を「全員」に設定し、「導入」ボタンを押すと公開が完了します。最後に出てくる**「現在のウェブアプリケーションのURL」**をコピーしておきましょう。

### Slash Commandの設定
今回はSlack側がトリガーとなり、GASがメッセージを送信します。トリガーとなるスラッシュコマンドの設定をしましょう。
[slack api](https://api.slack.com/apps)
上リンクから`Create New App`を押し、アプリの名前と投稿先のワークスペースを選択します。
Setting画面の`Add features and functionality`から、`Slash Commands`を選択します。ここで、新しくコマンドを作成することができます。
<img width="595" alt="スクリーンショット 2019-10-13 18.52.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/513327/09c9d0f8-9621-1ebe-fde8-c3ef04f75d14.png">
`Request URL`には先ほどGAS側でコピーしておいた「現在のウェブアプリケーションのURL」を入れます。
最後に、Setting画面に戻り`Install your app to your workspace`から`Reinstall App`をクリックし、設定は完了です！実際にコマンドを打って動くことを確認しましょう。

## おわりに
比較的簡単にAPIからSlackに情報を投げることができたと思います。天気予報だけでなく、Googleのサービスだったり電車情報とか飲食店の情報とか、Slackで全体に共有したい情報を気軽に登録できます。
皆さんもGAS+APIで快適なSlackライフを送りましょう！