Briswell Tech Blog

ブリスウェルのテックブログです

GAS & Slackで地震発生時の安否確認Botを作ってみた

弊社のようにリモートワークが多い会社では、地震発生時に安否確認を「迅速かつ正確に」行う必要があります。
ただ、地震の発生はいつどこで起きるかわからないため、担当者の割り振りが難しいのが現状です。
今回はGASとSlackを組み合わせて、地震発生時にSlackに安否確認の投稿をする仕組みを実装(自動化)してみました。
 

投稿のイメージはこんな感じです。
   

地震の情報源は気象庁の以下電文を利用します。
気象庁防災情報XMLフォーマット形式電文の公開(PULL型)
今回は10分に一度、情報を取得し地震情報のチェックを行うため、「高頻度フィード(地震火山)」のxmlを使用します。

「高頻度フィード(地震火山)」には直近に発生した地震火山関連情報を一覧で取得できます。
今回は、この中から震度3以上に適用される「震度速報」を安否確認のトリガーとします。
震度速報の[entry]タグには、詳細情報が記載されたURLが[link]タグ内にセットされています。
 

[link]タグ内のURLにアクセスすると、このような詳細情報を確認することでができます。
「震度」や「震源」といった情報を取得できますね。
 

そのため、手順としては以下の流れとなりそうです。

  • 「高頻度フィード(地震火山)」を取得

  • 「震度速報」の[entry]タグ内リンクを順に確認していく

  • 詳細情報リンクにて、震度5以上の情報があれば、Slackにメッセージを投稿  
     

では、実際に実装していきます。 
 

GASでスクリプトの作成

Google Apps Script
上記リンクより、GASの新規プロジェクトを作成します。
プロジェクトの作成は「新しいプロジェクト」より行えます。

コードの作成

プロジェクトの作成ができたらスクリプトを書いていきます。
※[token]と[channel]は後ほど取得するので仮置きしておきます。

/**
* 気象庁の地震情報(高頻度フィード)を取得しSlackに安否確認を飛ばす
* http://xml.kishou.go.jp/xmlpull.html
*/
function getJisinkun() {
  var now = new Date();
  var Before_1_Minute = new Date();
  Before_1_Minute.setMinutes(now.getMinutes() - 11); //11分前の時刻を取得(10分ごとに実行するため)
  var preDate = Utilities.formatDate(Before_1_Minute,"JST","yyyy-MM-dd HH:mm");
  console.log(preDate);

  var feedUrl   = 'https://www.data.jma.go.jp/developer/xml/feed/eqvol_l.xml';
  var feedHtml  = UrlFetchApp.fetch(feedUrl).getContentText();
  var feedDoc   = XmlService.parse(feedHtml);
  var feedXml   = feedDoc.getRootElement();
  var feedentry = getElementsByTagName(feedXml, 'entry'); //xmlのentry要素を配列で取得
  var atom      = XmlService.getNamespace('http://www.w3.org/2005/Atom');

  //初期値設定
  var slackFlg  = false; //slack送信フラグ
  var quakedt   = ""; //地震発生時刻

  //気象庁のxmlデータ(feed)より、entry要素を繰り返し探索
  var quakeInfo = ""; //地震情報
  feedentry.forEach(function(value, i) {
    var titleText = value.getChild('title', atom).getText(); //title
    var linkText  = value.getChild('link', atom).getAttribute('href').getValue(); //link
    
    //titleが震度速報の場合(震度3以上が震度速報に該当)
    if('震度速報' == titleText){
      //気象庁のxmlデータ(data)の情報を取得
      var dataUrl   = linkText;
      var dataHtml  = UrlFetchApp.fetch(dataUrl).getContentText();
      var dataDoc   = XmlService.parse(dataHtml);
      var dataXml   = dataDoc.getRootElement();
      var titleText = value.getChild('title', atom).getText();  //title

      var dataItem            = getElementsByTagName(dataXml, 'Pref'); // xmlに含まれるPref要素を配列で取得する
      var dataMaxInt          = getElementsByTagName(dataXml, 'Kind')[0].getValue(); // 震度
      var dataArea            = getElementsByTagName(dataXml, 'Area')[0].getValue(); // 地域
      var dataReportDateTime  = getElementsByTagName(dataXml, 'TargetDateTime')[0].getValue(); // 地震発生時刻
      dataReportDateTime      = dataReportDateTime.replace('T', ' ');
      dataReportDateTime      = dataReportDateTime.replace('+09:00', '');

      //気象庁のxmlデータ(data)より、Pref要素を繰り返し探索
      dataItem.forEach(function(value, i) {
        //Pref要素の文字列を取得(震度と地域)
        var strPref = dataArea;
        // 都道府県指定(東京近辺を対象)
        if(dataArea.match(/福島県|東京都|神奈川県|埼玉県|千葉県|茨城県|静岡県|栃木県|群馬県|愛知県|長野県/)){
          //「地震発生時刻 > 実行時刻の5分前」かつ「震度速報相当」の場合
          if(dataReportDateTime > preDate && dataMaxInt.match(/[震度5-|震度5+|震度6-|震度6+|震度7]/)){
              slackFlg = true;  //送信フラグをtrue
              quakedt     = dataReportDateTime;  //地震発生時刻
              quakeArea   = dataArea;  //地震発生地域
              quakeMaxInt = dataMaxInt;  //震度
              Logger.log("地震発生時刻:" + dataReportDateTime);
              Logger.log("地震発生地域:" + dataArea);  //地震発生地域
              Logger.log(dataMaxInt);  //震度
          }
        }
      });
    }
  });
  
  //送信フラグがtrueの場合
  if(slackFlg == true){
    var url = "https://slack.com/api/chat.postMessage";
    var payload = {
    "token" : "xoxb-XXXXXXXXXXXXX-XXXXXXXXXXXXX-XXXXXXXXXXXXXXXXXXXXXXXX",
    "channel" : "XXXXXXXXXX",
    "text" : "<!channel>\n【安否確認】\n震度5以上検知しました。\n安否確認のため、各自リアクションを行ってください。\n\n【地震情報】\n地震発生時刻:" + quakedt +"\n地震発生地域:"+ quakeArea +"\n"+ quakeMaxInt,
    };
    var params = {
      "method" : "post",
      "payload" : payload
    };
    // Slackに投稿する
    UrlFetchApp.fetch(url, params); 
    Logger.log("送信完了");
  }else{
    Logger.log("送信なし");
  }
}

/**
* @param {string} element 検索要素
* @param {string} tagName タグ
* @return {string} data 要素
*/
function getElementsByTagName(element, tagName) {
  var data = [], descendants = element.getDescendants();
  for(var i in descendants) {
    var elem = descendants[i].asElement();
    if ( elem != null && elem.getName() == tagName) data.push(elem);
  }
  return data;
}

スクリプトの記載は以上です。

このように丸々コピペでOKです。

スクリプトについては下記サイトを参考にさせていただきました。
参考文献: 気象庁のサイトから地震情報を取得してメール通知する

 

ライブラリ(SlackAPI)の追加

Slackへの投稿を行うために、SlackAPIライブラリを追加します。
ライブラリの追加はサイドメニューのライブラリ[ + ]より追加できます。
追加に際して、スクリプトIDが必要になるのですが、SlackAPIの場合は下記となります。

SlackAPIスクリプトID:
  1on93YOYfSmV92R5q59NpKmsyWIQD8qnoLYk-gkQBI92C58SPyA2x1-bq

ライブラリを追加したことで、GAS上でSlackAPIの利用が可能となります。
 


続いて、Slackアプリの作成とtoken情報の取得を行います。


 

Slackアプリ(bot)作成

SlackAPI
上記リンクより、Slackアプリを作成します。
※既に作成済みの場合はBOT_USER_OAUTH_TOKENの取得までスキップしてください。

アプリの作成は「Create New App」ボタンより行えます。
 
アプリの作成には[From scratch]と[From an app manifest]のどちらかを選べます。
今回は[From scratch]を選択します
   

Botの権限設定

サイドメニューの[OAuth & Permissions]よりOAuth & Permissions画面に遷移します。

OAuth & Permissionsのページ中腹にある[Scopes]より[Bot Token Scopes]を設定します。
 
[Add an OAuth Scope]より以下の権限を追加
今回はメッセージの書き込みを行うので、[chat:write]と[chat:write.public]を追加します。
 

ワークスペースへのインストール

OAuth & Permissionsのページ上部にある[Install to Workspace]をクリックし、ワークスペースにインストールを行います。
 
アクセス権限のリクエストを許可します。
 

BOT_USER_OAUTH_TOKENの取得

ワークスペースへのインストールが完了すると、OAuth & Permissionsのページに[Bot User OAuth Token]が表示されます。
 
取得した[Bot User OAuth Token]を、GASの[token]にセットします。
 


次に、投稿したいSlackチャンネルのIDを取得します


 

SlackのチャンネルIDを取得

Slackのワークスペースにて、投稿したいチャンネルのURLをコピーします。
投稿したいチャンネルを[右クリック]→[コピー]→[リンクをコピー]
https://[slackのワークスペース名].slack.com/archives/XXXXXXXXXXX
上記のようなリンクをコピーできるので、チャンネルID(URL末尾のXXXXXXXXXXX)を取得します。

 
取得した[SlackチャンネルID]を、GASの[channel]にセットします。
 


仮置きの値をセットできたので、スクリプトのデプロイ作業を行います。


 

作成したスクリプトをデプロイ

GASのデプロイボタンをクリックし、作成したScriptのデプロイを行います。
[新しいデプロイ][デプロイを管理][デプロイをテスト]の3種選択可能ですが、[新しいデプロイ]を選択します。

[種類の選択]ではウェブアプリを選択し、[設定]の説明文に概要を記載します。
[アクセスできるユーザー]は[全員]としておきます。
入力完了後、[デプロイ]ボタンよりデプロイ作業が開始されます。

アクセス許可を求められるので[アクセスを承認]より承認します。

デプロイが完了するとURLが発行されるのですが、特に利用しないので[完了]ボタンより戻ります。
 


最後にGASのトリガーを設定し、スクリプトを定期実行させます。


 

GASのトリガー設定

GASサイドメニューの[時計アイコン]をクリックし、トリガー画面に遷移します。

右下の「トリガーを追加」から新規トリガーの作成を行います。

[時間ベースのトリガーのタイプを選択]にて、[分ベースのタイマー]を選択します。
[時間の間隔を選択(分)]にて、[10分おき]を選択します。
上記以外はデフォルト値のままでOKです。
選択できたら[保存]ボタンを押下し登録します。

登録できました。
 


これで設定完了です!お疲れ様でした🙌


 

実行結果を確認してみる

震度5以上の地震は直近で発生していないため、対象を震度3まで引き下げて検証してみます。
実行はGASのヘッダーメニューの[実行]ボタンより行えます。 実行後はこのようにログが出力されます。 ※直近で震度3の発生がなかったため[Before_1_Minute]の値を30000にまで引き上げています。

Slackに投稿されました!

これで地震発生時の取り急ぎでの安否確認はできますね👏
実運用では投稿メッセージにgoogleフォームのリンクをつけて細かい安否確認を行った方が良いかと思います😇。