GASで気圧の変化をお知らせしてくれるLINEbotをつくる

#1.きっかけ
雨の日にパートナーが頭痛で辛そうだったので。
すでに「頭痛ーる」などのアプリが世にあるが、もっとお手軽に、勝手に気圧の変化を通知してくれる方法を提案してみたかった。
あとは純粋に、なにかLINEbotを作ってみたかった。

#2.概要
###作ったもの
GoogleAppsScriptでAPIから気象情報を取得し、それを加工して定期的にLINEへメッセージを送信するbot。

###使ったもの
####■GoogleAppsScript(通称GAS、メイン処理)
環境構築からデプロイまで簡単すぎるので、とにかくコードを書いて動くものを作りたいというときおすすめです。

####■LINE Messaging API（LINEへの投稿）
簡単にLINEbotを作れるAPI。
無料で使える機能としては以下があります。
　・ユーザーからの入力をトリガーとしてリプライを返すREPLY_MESSAGE
　・bot側から任意のタイミングでメッセージを送信できるPUSH_MESSAGE
今回は定期的（毎日）に気圧情報を送信したいので、PUSH_MESSAGEを実装します。

####■OpenWeatherMap API（気象情報の取得）
無料で気象情報を取得できるAPI。
利用にはユーザー登録が必要ですが、ネットに情報も多いので扱いやすいです。ただ、日本のサービスではないため、精度がちょっと不安。
その他のAPIでは「[気象情報API比較してみた](https://qiita.com/Barbara/items/93ae7969691164c7c2bc)」が参考になるかもしれません。ここには載っていませんが、日本製のものも一応あるみたいです。

#3.作成手順
###事前準備
詳しい手順はそれだけで一記事になりそうなので省略します。
最後に紹介する「6.お世話になった記事」を参考に進めてみてください。

　1.GoogleDriveからGoogleAppsScriptのアプリ追加
　2.[LINEのMessaging APIチャネルの新規作成](https://developers.line.biz/ja/)
　　　・「チャネル基本設定」の「アクセストークン（ロングターム）」を発行し、メモ
　　　・「チャネル基本設定」の「Your user ID」をメモ
　3.[OpenWeatherMapへの無料アカウント登録](https://openweathermap.org/)
　　　・マイページの「API keys」から自分のAPIキーをメモ
###GASの記述
「4.完成品」に記載のコードをGASの新規ファイルに記述してください。
###トリガーの設定
今回はbotからのメッセージ送信を定期的に実行したい為、GAS側で毎日21～22時の間に動くトリガーを設定します。

トリガーの設定方法
下記画像を参考に、メニューの「編集」＞「現在のプロジェクトのトリガー」を選択します。
<img width="719" alt="キャプチャ.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403060/52658d49-9814-0c2e-4fad-69845fd62b98.png">

右下の「トリガーを追加」を選択して新規のトリガーを作成しましょう。
<img width="751" alt="キャプチャ2.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403060/d9ac70ad-e0fa-db83-a40d-70c4b5f75a5e.png">

すると下記のようなポップアップが開くので、以下の項目を変更します。
・「実行する関数」：「pushMessage」
・「イベントのソースを選択」：「時間主導型」
・「時間ベースのトリガーのタイプを選択」：「日付ベースのタイマー」
・「時刻を選択」：「午後9時～10時」
<img width="552" alt="キャプチャ3.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403060/328a4358-254c-467a-4da1-a7c7d58aa048.png">
<img width="553" alt="キャプチャ4.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403060/31afd4e3-e014-15d6-c64b-dfa6807f566a.png">
これでトリガーの設定は完了となります。
###デプロイ
GASの編集画面から「公開」＞「ウェブ アプリケーションとして導入」を選択することで簡単にGoogleのサーバーにデプロイすることができます。
また、デプロイすることでURLが発行されるので、事前準備の2.で作成したLINE Messaging APIのチャネル設定画面へ移動し、「Webhook URL」にURLを設定しましょう。
この辺の詳しい方法は「6.お世話になった記事」を参考にしてください。
（頼りっぱなしで申し訳ないです。自分でも記事にするかもしれません）
#4.完成品
ちなみに警告メッセージを表示させるために、一部閾値を変更して実行しています。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/403060/f9c35905-d6a4-720c-d4d2-34fb0f3691ae.jpeg" width=50%>

<details>
  <summary>ソース全文はこちら</summary><div>

```javascript
function pushMessage() {
  const LINE_ACCESS_TOKEN = {YOUR_ACCESS_TOKEN};
  const LINE_USER_ID = {YOUR_USER_ID};
  const pushUrl = "https://api.line.me/v2/bot/message/push";
  
  var forecastResponse = getForecastInfo();  // 気象情報を取得
  var messageText = getMessageText(forecastResponse);  // メッセージをセット
  
  var options = {
    "headers": {
      "Content-Type": "application/json; charset=UTF-8",
      'Authorization': 'Bearer ' + LINE_ACCESS_TOKEN,
    },
    "method": "post",
    "payload": JSON.stringify({
      "to": LINE_USER_ID,
      "messages": [{
        "type": "text",
        "text": messageText,
      }]
    })
  };
  
  // push_messageの実行  
  UrlFetchApp.fetch(pushUrl, options);
}

// OpenWeatherMapのAPIから気象情報を取得するメソッド
function getForecastInfo(){
  const API_KEY = {YOUR_API_KEY};  // APIキーの指定
  const CITY_ID = "1850147";  // 都市IDの指定

  // APIのURL（5日/ 3時間毎の予測データ）
  var url = "http://api.openweathermap.org/data/2.5/forecast?id=" + CITY_ID + "&APPID=" + API_KEY + "&units=metric";

  // データ取得
  var response = UrlFetchApp.fetch(url);
  
  return JSON.parse(response);
}

function getMessageText(e){
  const newLine = "\n";    // 改行コード
  var response = "";       // 戻り値
  
  if (e.cod == "200"){
    var sysDate = new Date();
    var tomorrow = new Date(sysDate.getYear(), sysDate.getMonth(), sysDate.getDate() + 1)
    var formatTomorrow = Utilities.formatDate(tomorrow, "JST", "yyyy-MM-dd 00:00:00");
    
    response = "【" + formatTomorrow.slice(0, 10) + " の気圧情報】" + newLine;
    
    var currentPressure = 0.00; // 今回気圧
    var lastPressure = 0.00;    // 前回気圧
    var pressureDiff = 0.00;    // 気圧差
    
    // 気圧情報から警告をセットする
    e.list.forEach(function (item) {
      // 今回気圧をセット
      currentPressure = item.main.pressure;
      
      // 次の日のデータの場合のみ計算
      if(item.dt_txt.slice(0, 10) == formatTomorrow.slice(0, 10)){
        // 天気の絵文字をセット
        var iconcode = item.weather[0].icon;             // OpenWeatherMapのアイコンコード
        var emoji = getLINEemoji(iconcode.slice(0, 2));  // LINEの絵文字コード

        // 今回気圧－前回気圧の気圧差を加算
        pressureDiff += currentPressure - lastPressure;
        
        // 気温を小数点1位に丸める
        var temp = parseFloat(item.main.temp).toFixed(1);
        
        // 返信メッセージの作成
        response += "■" + item.dt_txt.slice(11, 16) + newLine
          + "　天気：" + emoji + "　気温：" + temp + newLine
          + "　気圧：" + item.main.pressure + newLine;
        
        // 気圧差が-3以下となった際は追加で警告メッセージをセット
        if(pressureDiff <= -3){
          response += "　※気圧低下に注意！\uDBC0\uDC9B" + newLine;
        }
      }
      
      // 前回気圧をセット
      lastPressure = currentPressure;
    });
  }
  else{
    response = "データ取得時にエラーが発生しました。" + newLine + e.cod + ":" + e.message;
  }
  
  return response;
}

// OpenWeatherMapのアイコンコードをもとに、LINE絵文字をセットする
function getLINEemoji(iconCode){
  // LINE絵文字一覧：https://developers.line.biz/media/messaging-api/emoji-list.pdf
  // OpenWeatherMapアイコン一覧：https://openweathermap.org/weather-conditions
  var response = "";
  
  if(iconCode == "01" || iconCode == "02"){
    response = "\uDBC0\uDCA9"; // 晴れ
  }
  else if(iconCode == "03" || iconCode == "04"){
    response = "\uDBC0\uDCAC"; // 曇り
  }
  else if(iconCode == "09" || iconCode == "10"){
    response = "\uDBC0\uDCAA"; // 雨
  }
  else if(iconCode == "11"){
    response = "\uDBC0\uDC3A"; // 雷
  }
  else if(iconCode == "13"){
    response = "\uDBC0\uDCAB"; // 雪
  }
  else if(iconCode == "50"){
    response = "\uDBC0\uDCA0"; // 霧
  }
  else{
    response = "不明"
  }
  
  return response;
}
```

</div></details>

#5.ソースの説明
　4つのメソッドで構成されています。
　1.`pushMessage()`（メイン処理）
　2.`getForecastInfo()`（気象情報の取得）
　3.`getMessageText(e)`（LINEへ送信するメッセージの作成、気象データの加工・計算）
　4.`getLINEemoji(iconCode)`（LINE絵文字の取得）
###1.`pushMessage()`（メイン処理）

```javascript
function pushMessage() {
  const LINE_ACCESS_TOKEN = {YOUR_ACCESS_TOKEN};
  const LINE_USER_ID = {YOUR_USER_ID};
  const pushUrl = "https://api.line.me/v2/bot/message/push";
  
  var forecastResponse = getForecastInfo();  // 気象情報を取得
  var messageText = getMessageText(forecastResponse);  // メッセージをセット
  
  var options = {
    "headers": {
      "Content-Type": "application/json; charset=UTF-8",
      'Authorization': 'Bearer ' + LINE_ACCESS_TOKEN,
    },
    "method": "post",
    "payload": JSON.stringify({
      "to": LINE_USER_ID,
      "messages": [{
        "type": "text",
        "text": messageText,
      }]
    })
  };
  
  // push_messageの実行  
  UrlFetchApp.fetch(pushUrl, options);
}
```
ここでは最終的にLINEへメッセージの送信（`UrlFetchApp.fetch(pushUrl, options)`）と、送信するにあたり必要な情報を各メソッドから取得しています。
第二引数で指定している`"payload"`内の`LINE_USER_ID`を持つアカウントに対して、` messageText`に格納されたメッセージが送信されることになります。
**※公式のリファレンスは[こちら](https://developers.line.biz/ja/reference/messaging-api/#send-push-message)**

ちなみにPUSH_MESSAGEを複数人に送信したい場合、[公式](https://developers.line.biz/ja/reference/messaging-api/#send-multicast-message)によるとフリープラン（無料）ではできないらしく…。
やるとしたらユーザーからメッセージを受信したタイミングでユーザー情報を取得し、DBとかに格納しとくとか？この辺は要調査です。

`LINE_ACCESS_TOKEN`には作成したLINE APIの「アクセストークン（ロングターム）」を、`LINE_USER_ID`にはLINE APIの「Your user ID」をセットしてください。
例）{YOUR_ACCESS_TOKEN} ⇒ "hogefuga"

###2.`getForecastInfo()`（気象情報の取得）
```javascript
// OpenWeatherMapのAPIから気象情報を取得するメソッド
function getForecastInfo(){
  const API_KEY = {YOUR_API_KEY};  // APIキーの指定
  const CITY_ID = "1850147";  // 都市IDの指定

  // APIのURL（5日/ 3時間毎の予測データ）
  var url = "http://api.openweathermap.org/data/2.5/forecast?id=" + CITY_ID + "&APPID=" + API_KEY + "&units=metric";

  // データ取得
  var response = UrlFetchApp.fetch(url);
  
  return JSON.parse(response);
}
```
OpenWeatherMapのAPIから気象情報を引っ張ってきます。APIも欲しい情報によって色々わかれていますが（[一覧はこちら](https://openweathermap.org/api)）、ここでは「5 day / 3 hour forecast」というAPIを使用します。5日後までの3時間毎の気象情報を提供してくれるものです。[公式ドキュメント](https://openweathermap.org/forecast5)の各「Examples of API calls:」を見ながら、呼出先のURLとパラメータを設定しています。

URLパラメータで指定しているのは以下になります。
　・都市ID：気象情報の取得対象となる地域を指定します（`"id=" + CITY_ID`）。今回は東京都の都市IDを指定しています。都市名や緯度経度でも指定可能です。
　・APIキー：OpenWeatherMapアカウントで取得したAPIキーを指定しましょう。（`"APPID=" + API_KEY`）
　・気温の単位：`units=metric"を指定することで、摂氏で気温を取得できます。指定しないとケルビン（摂氏温度に273.15度を足したもの）での取得になる為、後で摂氏への変換が必要になります。

###3.`getMessageText(e)`（LINEへ送信するメッセージの作成、気象データの加工・計算）
```javascript
function getMessageText(e){
  const newLine = "\n";    // 改行コード
  var response = "";       // 戻り値
  
  if (e.cod == "200"){
    var sysDate = new Date();
    var tomorrow = new Date(sysDate.getYear(), sysDate.getMonth(), sysDate.getDate() + 1)
    var formatTomorrow = Utilities.formatDate(tomorrow, "JST", "yyyy-MM-dd 00:00:00");
    
    response = "【" + formatTomorrow.slice(0, 10) + " の気圧情報】" + newLine;
    
    var currentPressure = 0.00; // 今回気圧
    var lastPressure = 0.00;    // 前回気圧
    var pressureDiff = 0.00;    // 気圧差
    
    // 気圧情報から警告をセットする
    e.list.forEach(function (item) {
      // 今回気圧をセット
      currentPressure = item.main.pressure;
      
      // 次の日のデータの場合のみ計算
      if(item.dt_txt.slice(0, 10) == formatTomorrow.slice(0, 10)){
        // 天気の絵文字をセット
        var iconcode = item.weather[0].icon;             // OpenWeatherMapのアイコンコード
        var emoji = getLINEemoji(iconcode.slice(0, 2));  // LINEの絵文字コード

        // 今回気圧－前回気圧の気圧差を加算
        pressureDiff += currentPressure - lastPressure;
        
        // 気温を小数点1位に丸める
        var temp = parseFloat(item.main.temp).toFixed(1);
        
        // 返信メッセージの作成
        response += "■" + item.dt_txt.slice(11, 16) + newLine
          + "　天気：" + emoji + "　気温：" + temp + newLine
          + "　気圧：" + item.main.pressure + newLine;
        
        // 気圧差が-3以下となった際は追加で警告メッセージをセット
        if(pressureDiff <= -3){
          response += "　※気圧低下に注意！\uDBC0\uDC9B" + newLine;
        }
      }
      
      // 前回気圧をセット
      lastPressure = currentPressure;
    });
  }
  else{
    response = "データ取得時にエラーが発生しました。" + newLine + e.cod + ":" + e.message;
  }
  
  return response;
}
```
引数で前回取得した気象情報を受け取り、LINEへ実際に送信するメッセージへ加工しています。
APIは3時間毎5日分＝40件のデータを返してくれるのですが、今回必要なのは翌日分のみなので、日付情報を持っている`dt_txt`が翌日日付の場合のみ[天気]、[気温]、[気圧]を取り出し、気圧差の推移が-3以下となった場合に注意喚起用のメッセージを追加しています。
###4.`getLINEemoji(iconCode)`（LINE絵文字の取得）
```javascript
// OpenWeatherMapのアイコンコードをもとに、LINE絵文字をセットする
function getLINEemoji(iconCode){
  // LINE絵文字一覧：https://developers.line.biz/media/messaging-api/emoji-list.pdf
  // OpenWeatherMapアイコン一覧：https://openweathermap.org/weather-conditions
  var response = "";
  
  if(iconCode == "01" || iconCode == "02"){
    response = "\uDBC0\uDCA9"; // 晴れ
  }
  else if(iconCode == "03" || iconCode == "04"){
    response = "\uDBC0\uDCAC"; // 曇り
  }
  else if(iconCode == "09" || iconCode == "10"){
    response = "\uDBC0\uDCAA"; // 雨
  }
  else if(iconCode == "11"){
    response = "\uDBC0\uDC3A"; // 雷
  }
  else if(iconCode == "13"){
    response = "\uDBC0\uDCAB"; // 雪
  }
  else if(iconCode == "50"){
    response = "\uDBC0\uDCA0"; // 霧
  }
  else{
    response = "不明"
  }
  
  return response;
}
```
APIから取得した気象情報の中には天気に応じたアイコン画像も含まれています。ここではそのお天気アイコンのコードをLINE絵文字へと変換しています。
LINE絵文字は「\uDBC0\uDC」＋「各アイコンコードの下2桁」で表示させることができます。下記リンクのLINE絵文字コードをそのまま記述しても絵文字に変換されないので、注意してください。

参考：
[OpenWeatherMapアイコン一覧](https://openweathermap.org/weather-conditions)
[LINE絵文字一覧](https://developers.line.biz/media/messaging-api/emoji-list.pdf)


#6.お世話になった記事
・[Google Apps ScriptでLINE BOTつくったら30分で動かせた件](https://qiita.com/hakshu/items/55c2584cf82718f47464)
こちらはGoogleスプレッドシートから開く場合の手順ですが、直接新規ファイルを開きたい時はGoogleDriveの「新規」＞「その他」から開けます。
**LINEbotの作成が初めての場合、こちらを一度作ってみると環境構築～デプロイまでの流れがつかめるので非常におすすめです。**

・[GASを使って天気予報botを作ってみよう！](https://qiita.com/shinbunbun_/items/23e2100abbe0fcd0274c)
Messaging APIのREPLY_MESSAGE機能を利用したLINEbotを作成されています。ユーザーからのアクションに対して何かメッセージを返したい場合はこちらの方が参考になると思います。

・[無料天気予報APIのOpenWeatherMapを使ってみる](https://qiita.com/nownabe/items/aeac1ce0977be963a740)
OpenWeatherMapからデータを取得する際のパラメータ指定方法など、詳しく紹介されています。今回の記事では都市IDを使用してデータを取得しています。

・[LINE BOTで「LINEの絵文字」を使う](http://www.pre-practice.net/2017/10/line-botline_21.html)
LINE絵文字の記述方法について、わかりやすく詳細に説明されています。

#7.つくってみた感想
　GASは環境構築～デプロイまでがすべてWEB上で完結できるので、非常に楽です。感動します。
　ソースの構成としては`getMessageText(e)`メソッドはもっと細かいモジュールに分割できた気がします。`UrlFetchApp.fetch(url, option)`のパラメータも、ある程度は共通化することですっきり書けたはずです。
　また技術的な箇所ではないですが、低気圧とする基準をどうするか非常に悩みました。1013hPaが一応平均というか基準であるらしいのですが、ぶっちゃけ周りより気圧が低ければ2000hPaだろうが3000hPaだろうがそれは低気圧らしく…。とりあえず今回は3hPa以上下がれば、体調に影響を及ぼすレベルの気圧の低下だと仮定しています。この辺「頭痛ーる」はどのような基準で判定しているのか、気になりますね。
　あとは実際に使ってもらって、改善点を洗い出す必要がありそうです。
　一本つくると知らない知識もたくさん出てくるので、今後は今回ここに書ききれなかったことを深堀りしてまた記事にしてみたいと思います。