[GoogleAppsScript] 天気予報をLINEに通知する

# はじめに
LINE Notifyを使用して天気予報を通知するBOTを作成しました。
<img width="257" alt="IMG_5276_1.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50563/4b135164-36ef-be66-d971-81265d3f816e.png">

GoogleAppsScript（GAS）にて1日1回通知プログラムを実行します。そのプログラムでは、[天気予報API](https://openweathermap.org/)からその日の天気予報を取得して、[LINE Notify](https://notify-bot.line.me/ja/)に通知します。

```plantuml
@startuml
GoogleAppsScript -> GoogleAppsScript: 1日1回起動
activate GoogleAppsScript
  GoogleAppsScript -> OpenWeatherMap: GET /forecast
  activate OpenWeatherMap
    GoogleAppsScript <-- OpenWeatherMap: 天気予報
  deactivate OpenWeatherMap
  GoogleAppsScript -> "LINE Notify": ...POST /notify
deactivate GoogleAppsScript
activate "LINE Notify"
  "LINE Notify" -> "LINE Notify": 対象のユーザーに通知
deactivate "LINE Notify"
@enduml
```

機能としては下記の通りです。

- スプレッドシートに記録
- 当日雨が降る場合は「※今日は傘を持っていきましょう!!」を表示する
- 前日より最高気温が3℃高い場合は「※今日は暑くなります!!」を表示する
- 前日より最低気温が3℃低い場合は「※今日は寒くなります!!」を表示する

# 天気予報API
- [OpenWeatherMap](https://openweathermap.org/)
- [API仕様](https://openweathermap.org/current)

OpenWeatherMapは、Webやモバイルアプリケーションの開発者に、現在の天候や予測履歴を含む各種気象データの無料APIを提供するオンラインサービスです。有料プランもあります。詳しくは下記の記事に詳細が記載されています。

- [無料天気予報APIのOpenWeatherMapを使ってみる](https://qiita.com/nownabe/items/aeac1ce0977be963a740)

# LINE Notify
- [詳しくはこちら](https://qiita.com/yun_bow/items/cc1f3c45c2d86e39c6f7)

# GoogleAppsScript
1. GoogleAppsScriptを開いて、以下のスクリプトを実装します。`LINE_NOTIFY_TOKEN`にLINE Notifyで発行したトークンを指定する。
2. `SSID_WEATHER`にスプレッドシートのIDと`SSN_WEATHER`にシート名を指定する。
3. `WEATHER_API_KEY `にOpenWeatherMapで発行したAPIキーを指定する。

```JavaScript:main.gs
/**
 * 天気予報BOT
 * 
 * OpenWeatherMap: https://openweathermap.org/current
 * - city id: http://bulk.openweathermap.org/sample/
 * - Weather Conditions: https://openweathermap.org/weather-conditions
 */
const SSID_WEATHER = '*****'; // WEATHERのスプレッドシートのID
const SSN_WEATHER = '*****'; // WEATHERのスプレッドシートのシート名
const LINE_NOTIFY_TOKEN = '*****'; // LINE NOTIFY用のアクセストークン
const WEATHER_API_KEY = '*****'; // OpenWeatherMapのAPIキー
const CITY_ID = 1850147; // 東京
const ABSOLUTE_ZERO = -273.15; // 絶対零度

/**
 * メイン処理
 */
function main() {
    try {
        let itemList = [];
        let spreadsheet = SpreadsheetApp.openById(SSID_WEATHER);
        let sheet = spreadsheet.getSheetByName(SSN_WEATHER);
        let lastRow = sheet.getLastRow();
        if (0 < lastRow) {
            itemList = sheet.getRange(1, 1, sheet.getLastRow(), 5).getValues();
            itemList = itemList.map((row) => {
                return {
                    timeStamp: row[0],
                    weatherId: row[1],
                    tempMax: row[2],
                    tempMin: row[3],
                    humidity: row[4],
                }
            });
        }

        let res = getWeatherInfo();
        let weatherList = [];
        let tempMax;
        let tempMin;

        let message = `\n今日の天気だよ!!\n\n`;

        for (let i in res.list) {
            let item = res.list[i];
            let dt = new Date(item.dt_txt);
            dt.setHours(dt.getHours() + 9);
            let timeStamp = Utilities.formatDate(dt, 'Asia/Tokyo', `yyyy/MM/dd HH:mm:ss`);
            tempMax = item.main.temp_max;
            tempMin = item.main.temp_min;

            if (parseInt(i) == 0) {
                message += `時刻: ${timeStamp}\n`;
                message += `天気: ${LanguageApp.translate(item.weather[0].description, 'en', 'ja')}\n`;
                message += `最高気温: ${Math.round(tempMax + ABSOLUTE_ZERO)}℃\n`;
                message += `最低気温: ${Math.round(tempMin + ABSOLUTE_ZERO)}℃\n`;
                message += `湿度: ${item.main.humidity}％\n\n`;
                sheet.appendRow([timeStamp, item.weather[0].id, tempMax, tempMin, item.main.humidity]);
            }
            if (parseInt(i) < 6) {
                weatherList.push(item.weather[0].id);
            }
        }

        let isRainGear = false;
        for (let i in weatherList) {
            let weather = weatherList[i];
            if (weather < 700) {
                isRainGear = true;
            }
        }
        if (isRainGear) {
            message += `※今日は傘を持っていきましょう!!\n`;
        }

        if (0 < itemList.length) {
            let oldItem = itemList[itemList.length - 1];
            if (3 < (tempMax - oldItem.tempMax)) {
                message += `※今日は暑くなります!!\n`;
            }
            if (3 < (oldItem.tempMin - tempMin)) {
                message += `※今日は寒くなります!!\n`;
            }
        }
        sendLineNotify(message);

    } catch (e) {
        console.error(e.stack);
    }
}

/**
 * 天気予報を取得する
 */
function getWeatherInfo() {
    let url = `http://api.openweathermap.org/data/2.5/forecast?id=${CITY_ID}&appid=${WEATHER_API_KEY}`;
    let options = {
        'method': 'get',
        'validateHttpsCertificates': false
    };
    let response = UrlFetchApp.fetch(url, options);
    return JSON.parse(response.getContentText('UTF-8'));
}

/**
 * LINEにメッセージを送信する
 * @param {String} message メッセージ 
 */
function sendLineNotify(message) {
    let url = 'https://notify-api.line.me/api/notify';
    let options = {
        'method': 'post',
        'headers': {
            'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
            'Authorization': `Bearer ${LINE_NOTIFY_TOKEN}`
        },
        'payload': `message=${message}`
    };
    let response = UrlFetchApp.fetch(url, options);
    return JSON.parse(response.getContentText('UTF-8'));
}
```
`SpreadsheetApp.openById`,`spreadsheet.getSheetByName`でスプレッドシートの情報を取得しています。`getValues()`でセルの値を全て取得しています。天気の内容は英文がAPIから返却されるため、`LanguageApp.translate()`で日本文に翻訳しています。

# 参考リンク
- [【GAS修行①】毎朝天気を教えてくれるLINE BOTをLINE Notifyで作成](https://qiita.com/t_o_d/items/a29b4ba6eacad77b3690)
- [天気予報を取得してLINEに通知を送る](https://qiita.com/taisatol/items/df399b47321bcb1e8d2a)

# さいごに
ソースコードをGitHubに公開しています。

- [ソースファイルはこちら](https://github.com/yunbow/GoogleAppsScript-LINE-Weather)

以上です。