GAS + OpenWeatherMap One Call API + Slackで天気予報通知bot作ってみた

こんにちは。今回はなんとなく思い立ってGAS + OpenWeatherMap One Call API + Slackで天気予報通知botを作ってみたので、その概要を記述していこうと思います。

普段はジュニアレベルのデータアナリストをしていて、趣味は写真、最近プログラミングで遊び始めたっていう人間なので、コードのレベルは低いかもですが、ご容赦ください。

# 天気予報通知botを作ろうと思ったきっかけ
僕は札幌の自宅でフルリモートで働いていることもあり、天気予報を確認する、という習慣が全くありません。
たまに用事があって外出してみたら、午後からめちゃくちゃ雨が降って、傘もないし、どうしよう、、、となるシーンが時たまあります。これを無くしたいなーって思ったのがきっかけです。

天気アプリ見に行けばいいじゃん？その通りです。でも通知してくれないと見れない怠け者なのです。

天気アプリで毎日同じ時間に通知してくれる仕組みあるじゃん？その通りです。でもまぁ便利な世の中で「他に変わりがあるじゃん？」って言い始めちゃうと僕のような初学者は何も作れなくなるので多めに見てください。

# なぜGAS + OpenWeatherMap One Call API + Slackという構成を選択したか
まず、今回作成しようと思ったbotの機能としては、毎日朝6時に特定のSlackチャンネルに3時間ごとの天気・気温・降水確率等の情報を投稿してくれる、というものです。

GASを選択した理由は以下3つです
1. 本業の中で業務効率化のため利用したことがあったこと
2. GUIで簡単にトリガー設定ができること
3. 無料

タダが一番。次いでGUIで簡単にトリガー設定できるのも今回の目的からすると魅力的でした。

次にOpenWeatherMap One Call APIを選択した理由は以下3つです。
1. 天気予報情報取得APIとして利用が多そうなサービスだから
2. 48時間先までの時間ごとの天気情報を取得でき、今回作りたいものに合っているから
3. 1日1000回のAPIコールまでなら無料

調べてみるとわかるのですが、意外と無料で1時間ごとの天気情報を取得できるAPIを提供しているところは少ないみたいです。正確性・信用度でいうと気象庁の公開しているWeather Data APIを利用したいところですが、基本商用利用を想定しており、月額32,000円~ということで選択肢から外しました。
OpenWeatherMapのOne Call APIも1日1000回という上限はありますがそれまではタダで利用できます。僕は毎日1回天気予報送ってくれたらOKなので十分です。

最後にSlackですが、これは僕が一番確実にみるであろうチャットツール、という理由だけで選びました。LINEやDiscodeなどでも同様のことはできると思います（詳しくないですが）。

# 開発の準備
GASを利用するのに必要なGogleアカウントの作成やSlackのサインアップの過程はそこまで複雑じゃない^すでにある方も多いと思うので飛ばします。

## GAS
まずはGASのプロジェクトを作成します。
chromeからsheet.newと入力することでspreadsheetを新規に作成します。
![](https://storage.googleapis.com/zenn-user-upload/77677730627b-20240616.png)

spreadsheetが開いたら拡張機能からApp Scriptを選択します
![](https://storage.googleapis.com/zenn-user-upload/118dcf287fc6-20240616.png)

無事、GASの新規プロジェクトが作成できました。適当な名前をつけておきます。
![](https://storage.googleapis.com/zenn-user-upload/be82dc547ff0-20240616.png)

## OpenWeatherMap One Call API 
次にOpenWeatherMapのOne Call APIを利用するための準備をしていきます。[Weather API - OpenWeatherMap](https://openweathermap.org/api)を開き、One Call API 3.0のSubscribeをクリックします。

![](https://storage.googleapis.com/zenn-user-upload/3f0ff05c0479-20240616.png)

1日1000回までは無料ですが、その上限を超えると課金が発生するため、支払い手段・請求先の登録が必要です。案内に沿って入力していきます。
![](https://storage.googleapis.com/zenn-user-upload/4d074a7f3892-20240616.png)

無事サブスクリプションができるとメールが届きます。
![](https://storage.googleapis.com/zenn-user-upload/19d9fc22ff54-20240616.png)

サブスクリプションをした際に、OpenWeatherMapのアカウントも同時に作成されます。初回ログインに利用するメールアドレスとパスワード情報がメールに記載されているので、それを用いてOpenWeatherMapにログインします。
![](https://storage.googleapis.com/zenn-user-upload/491fda32d00b-20240616.png)

なぜかわかりませんが、サブスクリプション完了のメールはGmailの受信ボックスにちゃんと入っていましたが、ログイン情報通知のメールは迷惑メールに分類されてしまっていました。もしメールを見つけられない場合は迷惑メールを確認してみることをお勧めします。

サインインすればMy API keysから自分のAPI keyを取得できるようになっています。
![](https://storage.googleapis.com/zenn-user-upload/2c5370cd8034-20240616.png)
![](https://storage.googleapis.com/zenn-user-upload/6abc5e66d406-20240616.png)
このAPI Keyを後でGASからAPI Callする際に利用することになります。

## Slack
スラックの特定のチャンネルに通知を飛ばすためにWebhookのURLを取得する必要があります。
この辺りは他の記事がたくさんあると思いますので、解説は譲ろうと思います。サクッと検索しただけでも以下のような記事が見つかったので参考にしてください。

[GASでスプレッドシート上のテキストをSlackに送信する - 株式会社ライトコード](https://rightcode.co.jp/blogs/40358)

# GASでの実装
いきなりですが最終的なコードは以下になりました。

```js
function hundler() {
  const weatherData = fetchWeatherData();

  const weatherReportBlock = createWeatheReportBlock(weatherData);

  postToSlack(weatherReportBlock);

}

function fetchWeatherData() { // API Callをして天気予報情報を取得
  const apiKey = 'yourApiKey'; // OpenWeatherMap APIkey
  const lat = 43.062; // 札幌の緯度
  const lon = 141.354; // 札幌の経度
  const lang = 'ja' // 言語指定

  const apiUrl = `https://api.openweathermap.org/data/3.0/onecall?lat=${lat}&lon=${lon}&lang=${lang}&appid=${apiKey}`;

  const response = UrlFetchApp.fetch(apiUrl);
  const data = JSON.parse(response.getContentText());

  return data;
}

function createWeatheReportBlock(weatherData) { // 取得した天気情報から欲しいデータを抽出・整形
  const reportBlocks = [
    {
      "type": "header",
      "text": {
        "type": "plain_text",
        "text":"本日の3時間毎天気"
      }
    }
  ]

  for (let i = 0; i < 23; i += 3) { // 3時間ごとのデータを取得する
    const hourData = weatherData.hourly[i];
    const time = new Date(hourData.dt * 1000);
    const hour = time.getHours();
    const weather = hourData.weather[0].description;
    const temp = Math.round(hourData.temp - 273.15); //ケルビンを℃に変換
    const pop = Math.round(hourData.pop * 100);
    const clouds = hourData.clouds;

    reportBlocks.push({
      "type": "section",
      "text":{
        "type":"mrkdwn",
        "text":`*${hour}:00*\n天気：${weather}, 気温：${temp}℃, 降水確率：${pop}%, 曇り度：${clouds}%`
      }
    });
  }
  return reportBlocks;
}

function postToSlack(blocks) { // Slackにポスト
  const webhookUrl = 'yourWebhookUrl'; //Slack Webhook URL

  const payload = {
    blocks:blocks,
  };

  const options = {
    method: 'post',
    contentType: 'application/json',
    payload: JSON.stringify(payload),
  };

  UrlFetchApp.fetch(webhookUrl, options);
}
```

GASではhundlerを指定して実行することになります。
hundlerの中では3つのfunctionを呼び出してます。

## fetchWeatherData()
```js
function fetchWeatherData() { // API Callをして天気予報情報を取得
  const apiKey = 'yourApiKey'; // OpenWeatherMap APIkey
  const lat = 43.062; // 札幌の緯度
  const lon = 141.354; // 札幌の経度
  const lang = 'ja' // 言語指定

  const apiUrl = `https://api.openweathermap.org/data/3.0/onecall?lat=${lat}&lon=${lon}&lang=${lang}&appid=${apiKey}`;

  const response = UrlFetchApp.fetch(apiUrl);
  const data = JSON.parse(response.getContentText());

  return data;
}
```
コメントでも記載してますが、OpenWetherMap One Call APIをコールして、取得したデータをdataに入れて返す、という関数になってます。
API Callにどんなパラメーターをどう入れたらいいのか、はOpenWeatherMapのAPI Documentを見ることでわかります。各パラメーターの意味はもちろん、Exampleも複数提示されてあって、非常にわかりやすいです。

[One Call API 3.0 - OpenWeatherMap](https://openweathermap.org/api/one-call-3#current)

## createWeatheReportBlock()
```js
function createWeatheReportBlock(weatherData) { // 取得した天気情報から欲しいデータを抽出・整形
  const reportBlocks = [
    {
      "type": "header",
      "text": {
        "type": "plain_text",
        "text":"本日の3時間毎天気"
      }
    }
  ]

  for (let i = 0; i < 23; i += 3) { // 3時間ごとのデータを取得する
    const hourData = weatherData.hourly[i];
    const time = new Date(hourData.dt * 1000);
    const hour = time.getHours();
    const weather = hourData.weather[0].description;
    const temp = Math.round(hourData.temp - 273.15); //ケルビンを℃に変換
    const pop = Math.round(hourData.pop * 100);
    const clouds = hourData.clouds;

    reportBlocks.push({
      "type": "section",
      "text":{
        "type":"mrkdwn",
        "text":`*${hour}:00*\n天気：${weather}, 気温：${temp}℃, 降水確率：${pop}%, 曇り度：${clouds}%`
      }
    });
  }
  return reportBlocks;
}
```

APIのレスポンスをもとにSlackに通知する内容を作成するfunctionです。レスポンスの形や各フィールドの内容については[One Call API 3.0 - OpenWeatherMap](https://openweathermap.org/api/one-call-3#parameter)に定義があります。こちらもE詳細に書かれてあってわかりやすいです。

Slack通知の内容は[Block Kit | Slack](https://api.slack.com/block-kit)に合わせて作成しています。Slack上でのいろんな表現を作ることができるので面白いです。僕もにわかなのでとりあえずsectionの形を採用して記述しました。

## postToSlack()
```js
function postToSlack(blocks) { // Slackにポスト
  const webhookUrl = 'yourWebhookUrl'; //Slack Webhook URL

  const payload = {
    blocks:blocks,
  };

  const options = {
    method: 'post',
    contentType: 'application/json',
    payload: JSON.stringify(payload),
  };

  UrlFetchApp.fetch(webhookUrl, options);
}
```
createWeatheReportBlock()で作成したblockをSlackの特定のチャンネルに投稿するfunctionです。投稿の形式はcreateWeatheReportBlock()で整形済みなのでただただ送ってるだけですね。

## 全体処理の作成過程
処理全体を作成するにあたって、以下の順で実装していきました。
1. まずはOpenWetherMap One Call APIをGASからコールし、console.logに出力できるようにする
2. 上記処理をfetchWeatherData()としてfunctionにまとめ、hundler()に追加する
3. fetchWeatherData()で得られたデータから必要なデータを取得しconsole.logに出力できるようにする
4. 必要なデータをSlackに投稿するための形に並べ直す（[Block Kit | Slack](https://api.slack.com/block-kit)に則り）
5. 上記の処理をcreateWeatheReportBlock()としてまとめ、hubdler()に追加する
6. createWeatheReportBlock()で作成したBlockをwebhook URLを用いて特定のチャンネルに投稿できるようにする
7. 上記の処理をpostToSlack()としてまとめ、hundler()に追加する

処理内容を段階的に区切って、順々に作っていった感じです。いっきにやるよりつまづきを防止できてよかったかなと思います。

## トリガーの設定
GASではScriptの実行トリガーを設定できます。カレンダーをもとに決定したり、紐づいているスプレッドシートに対するイベントを検知したり、といろんな方法がありますが、今回は毎朝6時ごろに実行して欲しいので、以下のように設定しました。
![](https://storage.googleapis.com/zenn-user-upload/610493f62ff3-20240616.png)

## デプロイ
最後に右上のデプロイから新しいデプロイを選択し、デプロイをします。多分これをしないと定期実行してくれないんだと思います。
![](https://storage.googleapis.com/zenn-user-upload/4a04a3e11e74-20240616.png)

## 実行結果
実行した結果、以下のような通知が指定したチャンネルに通知されました。
![](https://storage.googleapis.com/zenn-user-upload/e1990546436a-20240616.png)
この時は手動で朝8時ごろに実行したため、8時から3時間ごとの天気が通知されています。
見た目は色々改善の余地がありそうですが欲しい情報はちゃんと得られていそうです。

# まとめと展望
今回はGAS + OpenWeatherMap One Call API + Slackで天気予報通知botを作ってみました。これで出先で土砂降りにあって傘が無くて帰れ無くなることもなくなりそうです。多分。

何度か天気情報を受け取ってみてわかったのですが、予報の精度はそこまで良くなさそうでした。数日分の予報を出してみた感じ、天候をマイナスに評価しがちな傾向がありそうです。日本の気象庁が公開しているデータをもとにAPIを作成しているサイトを見つけたので、予報情報の取得先はこちらに切り替えてみても良いかなと思いました。
[天気予報 API（livedoor 天気互換）](https://weather.tsukumijima.net/)

またSlackの通知文も平坦で面白みに欠けるので、天気情報に合わせて絵文字で天気を表現するなど、工夫をしたいなと思いました。

追加で、個人的にAWSの勉強をしたいなと思うので、これをAWS上で実装してみることにもチャレンジしてみたいです。Lambdaを使えば同じようにできるかなと思ってます。

今回は以上です。またアップグレードする時は記事にできればと思います。