[GAS×Google Chat]雨が降りそうな時に通知するbotを作った

#★使えなくなりました★
一般データ保護規則(GDPR)の関係でYahoo!のサービスが欧州から繋がらなくなりました。
GASから気象情報APIを叩くと「欧州からは使えねーよ」と403エラーが出るように…。
エラー出ない時もあるので、多分GASの接続元が欧州じゃない時もあるのかな？
接続元のリージョンを指定出来れば良いのですが…。そんなのあるのかな。

#作ったきっかけ
リアルタイムに「今から雨降るよ！！」って通知してくれるbotが欲しくて作りました。
前日や当日に一日の天気を通知するbotはちょこちょこあるんですが、リアルタイムに監視するbotがなかったので。

#まぁスマホアプリとかで簡単に実現出来ますけど
社内でGoogle Chatを活用して紙やメールを減らしていきましょうって流れがあり、
Google Chatを定期的に見る仕組みを用意したかったので…。
他にも社食のメニューをお知らせするbotとかもあります。

#ざっくりとした仕組み
気象情報APIはYahoo!のを使いました。

Yahoo! Open Local Platform(YOLP) 気象情報API
https://developer.yahoo.co.jp/webapi/map/openlocalplatform/v1/weather.html

気象情報APIから予報を取得
　　　　　↓
スプレッドーシートに書き出す
　　　　　↓
前回と今回の取得時の観測値に変化があったら
「雨がやみました/降り始めました」と通知
　　　　　↓
現在の観測値と15分後の天気を比べて差があったら
「雨がやみそうです/降り始めそうです」と通知
　　　　　↓
メッセージと一緒に雨雲レーダーの画像チャットにポスト
　　　　　↓
15分毎にループする

#気象情報APIの使い方
APIを使うためにはアプリケーションIDを取得する必要があります。
https://e.developer.yahoo.co.jp/register
アプリケーションの種類はサーバーサイドで良いっぽい。

天気を知りたい場所の緯度、経度、取得したアプリケーションIDを以下のように記述して使います。
`https://map.yahooapis.jp/weather/V1/place?coordinates=<経度>,<緯度>&appid=＜アプリケーションID＞`

#スプレッドシート
こんな感じにしてます。
<img width="450" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/514873/6b1b67da-626f-e98f-51b7-cd30f729f7dc.png">
実行時刻と観測時刻を見比べて貰ったら分かると思いますが、結構ラグがあります。

#Google Chatの通知はこんな感じ
<img width="450" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/514873/0503bd90-1aa3-7b1a-72cf-a37dc233e78f.png">

**☆残念なお知らせ☆**
[【重要】YOLP Web APIにおける一部API・SDK 提供終了のお知らせ](https://developer.yahoo.co.jp/changelog/yolp_web_apiapisdk.html)
雨雲レーダーのAPIは2020/10/31で提供終了になるそうです。代替サービスあるんか…？
終了しちゃったらImageなしで通知するしかないですね。

#コード
```javascript:getJson.gs
//YOLPのapiを叩くのに必要なパラメータ
let appId = "<取得したAPIのAPP ID>";
let lon = "139.7649361"; //経度
let lat = "35.6812362"; //緯度
let interval = 5; //予報の間隔(分)
let apiUrl = "https://map.yahooapis.jp/weather/V1/place?coordinates=" + lon + "," + lat + "&output=json&appid=" + appId + "&interval=" + interval;

//Google ChatのWebhook
let webhookUrl = 'https://chat.googleapis.com/v1/spaces/~';

//スプシとシートの設定
let spreadSheet = SpreadsheetApp.getActiveSpreadsheet();
let sheet = SpreadsheetApp.getActiveSheet();

function getJson() {
  //お天気api叩く
  let response = UrlFetchApp.fetch(apiUrl);
  //let response = spreadSheet.getSheetByName('シート3').getRange('A1').getValue();
  let date = '';
  let rainfall = '';
  let previouszObservation = sheet.getRange(3,3).getValue(); //前回天気を確認した時の雨量
  let currentObservation = '';
  let after15minForecast = '';
  let minDiff = '';
  let now = new Date();
  let str = '';

  //現時刻を書き込み
  sheet.getRange(1,2).setValue(Utilities.formatDate( now, 'Asia/Tokyo', 'HH:mm'));

  Logger.log(response.getContentText());
  //JOSNオブジェクトに変換
  let json=JSON.parse(response.getContentText());
  //観測結果と5分毎の予報を取得(13回)
  for(let i=0;i<=12;i++){
    //JSONから日時を取得
    date = json['Feature'][0]['Property']['WeatherList']['Weather'][i]['Date'];
    //'yyyy/MM/dd hh:mm'に変換
    date = date.slice(0,4)+'/'+date.slice(4,6)+'/'+date.slice(6,8)+' '+date.slice(8,10)+':'+date.slice(-2);
    //雨量を取得
    rainfall = json['Feature'][0]['Property']['WeatherList']['Weather'][i]['Rainfall'];
    //type = json['Feature'][0]['Property']['WeatherList']['Weather'][i]['Type'];
    //日時と雨量を書き出す
    sheet.getRange(i+3,2).setValue(date);
    sheet.getRange(i+3,3).setValue(rainfall);
  }
  //現在の観測雨量
  currentObservation = sheet.getRange(3,3).getValue();
  //観測時刻から25分後の予測雨量
  //apiを叩くと約10分前の観測と予報が得られるので、api叩いた時刻の15分後の予報が知りたい時はJSONから25分後のデータを取る
  after15minForecast = sheet.getRange(8,3).getValue();
  //観測から25分後の時刻と、スクリプト実行時刻との時間差を計算
  minDiff = String(Math.round((sheet.getRange(8,2).getValue() - now)/60000)); //単純に引き算するとミリ秒で出て来るので60000で割ると分になる
  
  //雨の観測結果の変化チェック
  if(previouszObservation!=currentObservation){ //前回の雨量と現在の雨量が一致しない時
    if(currentObservation==0){ //現在の雨量が0なら
      str += '雨がやみました。';
    }else if(previouszObservation==0) { //前回の雨量が0なら
      str += '雨が降り始めました。';
    }
  }
  
  //約15分後の予報チェック
  //現在の雨量と15分後の雨量の差異で判断
  if(currentObservation==0){
    if(after15minForecast>0){
      if(str!=''){
        str += '<br>';
      }
      str += '約' + minDiff + '分後に雨が降り始めるようです。'
    }
  }else {
    if(after15minForecast==0){
      if(str!=''){
        str += '<br>';
      }
      str += '約' + minDiff + '分後に雨がやむようです。'
    }
  }
  Logger.log(str);
  Logger.log('前回の観測値：'+ previouszObservation);
  Logger.log('今回の観測値：'+ currentObservation);
  Logger.log('15分後の予報：'+ after15minForecast);
  //通知内容があればGoogle Chatにpost
  if(str!=''){
    let payload  = {
      "cards": [
        {
          "sections": [
            {
              "widgets": [
                {
                  "textParagraph": {
                    "text": str
                  }
                },
                {
                  "image": { "imageUrl": "https://map.yahooapis.jp/map/V1/static?appid="+ appId + "&lat=" + lat + "&lon=" + lon + "&z=11&width=300&height=200&pointer=on&overlay=type:rainfall|date:"+Utilities.formatDate( now, 'Asia/Tokyo', 'yyyyMMddHHmm') }
                },
                {
                  "buttons": [
                    {
                      "textButton": {
                        "text": "雨雲レーダーを見る",
                        "onClick": {
                          "openLink": {
                            "url": "https://weather.yahoo.co.jp/weather/zoomradar/?lat=" + lat + "&lon="+ lon + "&z=12"
                          }
                        }
                      }
                    }
                  ]
                }
              ]
            }
          ]
        }
      ]
    };
  
    let options = {
      "method" : "POST",
      "contentType" : 'application/json; charset=utf-8',
      "payload" : JSON.stringify(payload)
    };
    //Google Chatにポスト
    let dayOfWeek = now.getDay();
    let hour = now.getHours();
    //土日じゃない&7時～21時の時チャットに通知
    if(dayOfWeek != 0 && dayOfWeek != 6){
      if(hour>6 && hour<22){
        UrlFetchApp.fetch(webhookUrl, options);
      }
    }
  }
}
```
職場にいない時に通知されるのも邪魔なので、土日と夜間/早朝は通知しないようにしています。
#作ってみて
天気予報の精度に因るところもあるんですけど、結構「降りそう」って通知があっても降らなかったりとか外れる事も多いです。笑
あと、Google Chatのカード形式メッセージを初めて使ってみました。
見た目がシュッとするので良い感じですね。(もうちょっと自由度欲しいけど)