GASを用いてGSSからSlackに速報を自動で投下したい

　DeNA Advent Calendar 2020 6日目を担当させていただきます。
 今回は目新しい技術ではありませんが、GAS(Google Apps Script)を用いて、GSS（Google Spread Sheets）から情報をSlackに自動投稿する方法をご紹介します。
　スクリプトを交えながら解説をしていきたいと思います。

　なお、事前にSlackAPPでbotアプリを作成し投稿するための権限付与、WebhookURLを取得しておく必要があります。

#要件


・GSS上のテキスト情報を投稿し、そのスレッドにグラフ画像を投稿
・毎日特定の時間(分単位で指定)に投稿
・投稿内容を平日/それ以外の日で出し分け
・データ反映に問題があれば投稿前に止める

#送る情報

まず、GSSのシートに下記の情報が出ている状況です(実際のデータとは異なります)


```
<!here> お疲れ様です。本日のKPI速報です。
```================================
12/06　KPI速報
-----------------------------------
|_______|__DAU(人)__|_売り上げ（円）_|
|昨日実績|  30,000 |  300,000 　　|
|昨日目標|  30,000 |  300,000　　 |
|目標比  | 100.0%  |  100.0%  　　| 
===================================```
```
これはSlackに送る書式で一つのセルに情報をまとめたものになります。

<!here> はSlack上で``` @here ```のメンション指定です。
基本的な情報の前後には ```がありますが、これはプレーンテキストで貼り付けるためにコードブロックを利用するためにつけています。
また、土日祝日用にメンション部分を消した同様の内容のものを別のセルに用意してあります。

さらに、直近の各数値の遷移を表したグラフを２つ用意しています。
これはシート上の適当な場所にグラフを作って置いておいてください。


#スクリプトの解説

##メインとなるスクリプト

投稿したい情報を取得し、投稿するための関数に情報を渡して投稿を実施しています。

これから文言投稿、画像投稿、平日/それ以外の日で出し分け関数について個別に解説していきます。

```
//SlackAPPで作成したbotのTOKEN

var  SLACK_TOKEN = "slack_token";
var  OATH_TOKEN = "oath_token";
//投稿したいチャンネルのID
var  CHANNNEL_ID = "channel_id";
var ss = SpreadsheetApp.getActiveSpreadsheet();

function test(){
　//情報のあるシートを指定
  var sheet = ss.getSheetByName("Blackbox");

　//数値が出ているか確認用のセル
  var check = sheet.getRange("B33");
  var check_value = check.getValue();
  
  if(check_value==0){
    return;
  }
  
　//休みと平日でだし分けている
  if(isHoliday(new Date())){
    var range = sheet.getRange("B29");
  }else{
    var range = sheet.getRange("B12");
  }

　
  var value = range.getValue();
　//ここでデータを送信
  postSlack(value);
  var res = getHistory();
  var json = JSON.parse(res);
　//スレッド投稿に必要なための情報を抽出
  var ts = json.messages[0].ts;
　//グラフ画像を送信
  report(ts);
}
```

##データ出力されているか確認

```
　//数値チェック用のセル
  var check = sheet.getRange("B33");
  var check_value = check.getValue();
  
  if(check_value==0){
    return;
  }
```

　集計の過程で何かのエラーが発生し数値が正常に反映されなかった場合には速報を流さないようにしたいので、速報に使用する値のうち、任意の値をチェック用として別のセルに情報を持たせておきます。
　そして、その数値が０であれば投稿前に処理を中断するようにしています。



##データ送信をする

こちらはWebhookを利用して単純にSlackにポストするだけの関数です。
payloadのtextに上記のデータを指定しています。

```
function postSlack(text){
  var url = "Webhook_url";
  var options = {
    "method" : "POST",
    "payload" : '{"text":"' + text + '"}'
  };
  var response = UrlFetchApp.fetch(url, options);
  return response;
}
```


##直前の投稿を取得する

先に投稿した情報のスレッドにグラフ画像を投稿するために必要になります。
webhoockではなさそうだったのでAPIを叩くことで対応しています。

``channels.history``というAPIで過去投稿情報を取得します。

頻繁に投稿があるチャンネルではないこともあるので、特定のチャンネルに投稿された直近１件の情報を取得しています。

```
function getHistory(){
    var url        = 'https://slack.com/api/channels.history';
    var token      =  OATH_TOKEN;
    var channel    =  CHANNNEL_ID;
 
    var payload = {
        'token'      : token,
        'channel'    : channel,
        'count'      : 1
    };
 
    var params = {
        'method'  : 'get',
        'payload' : payload
    };
 
    var response = UrlFetchApp.fetch(url, params);
    return response;
}
```

##グラフ画像をスレッドに送信する

report関数でグラフ画像を取ってきています。
グラフが複数ある場合はchartsに配列のように入っているので何番めの画像が欲しいかと言う指定をします。
今回はグラフデータをBlobとして取得し、pngファイルに変換しています。

```
function report(ts) {

  var sheet = ss.getSheetByName("シート名");
  var charts = sheet.getCharts();
  var chartImage = []; 
  
  chartImage[0] = charts[2].getBlob().getAs('image/png').setName("graph.png");
  chartImage[1] = charts[3].getBlob().getAs('image/png').setName("graph1.png");
  
  chartImage.forEach(function(item){
    postImage(item,ts);
  });
}
```

こちらは取得したグラフ画像をアップロードする関数
files.uploadと言うAPIでアップロードを実施します。
スレッド投下するための肝はpayloadに入っている'thread_ts'です。

ここに指定する値は``getHistory``で取得した情報に``ts``として入っていますのでそちらを利用します。

```

function postImage(chart,ts){
    var url        = 'https://slack.com/api/files.upload';
    var token      =  SLACK_TOKEN;
    var channel    = CHANNNEL_ID;
 
    var payload = {
        'token'      : token,
        'channels'   : channel,
        'file'       : chart,
        'filename'   : 'KPI推移',
        'thread_ts'  : ts
    };
 
    var params = {
        'method'  : 'post',
        'payload' : payload
    };
 
    var response = UrlFetchApp.fetch(url, params);
}
```

##土日祝を判定する

 土日は``today.getDay();``で曜日が数値で取得できますので、土日に相当する数値でtrueを返します。
 祝日については、googleが公開している日本の祝日カレンダーがあるので、これを活用します。
当日にイベントが登録されていたら何かしらの祝日と言うことですので、trueを返します。

```
//土日祝判定
function isHoliday(today){

  //土日か判定
  var dayOfWeek = today.getDay();
  if(dayOfWeek <= 0 || 6 <= dayOfWeek){
    return true;
  }

  //祝日か判定
  var calendarId = "ja.japanese#holiday@group.v.calendar.google.com";
  var calendar = CalendarApp.getCalendarById(calendarId);
  var todayEvents = calendar.getEventsForDay(today);
  if(todayEvents.length > 0){
    return true;
  }
  return false;
}
```

ここまでが、Slackに情報を投稿するスクリプトです。


次はラスト、指定の時間に投稿するためのスクリプトになります。
このトリガー設定はUI上で設定するだけだと、分単位での時間指定ができないので、
１.分単位での指定スクリプトを作る
２.そのスクリプトをトリガーとして設定するためのトリガーを設定する

と言う過程を踏みます。

##分単位のトリガーを作成するスクリプト

``setHours``は投稿したい時間

``setMinutes``は投稿したい分を指定します

```
function setTrigger() {
  var day = new Date();
  day.setHours(12);
  day.setMinutes(01);
  ScriptApp.newTrigger("test").timeBased().at(day).create();
}
```

このスクリプトを実行するトリガーを時間主導型、日付ベースのトリガーとしてその日の朝くらいの時間にUIで登録しておきます。
すると、スクリプトで作成したトリガーが毎日自動で設定され日々の自動投稿につながります。


---


この記事を読んで「面白かった」「学びがあった」と思っていただけた方、よろしければ Twitter や facebook、はてなブックマークにてコメントをお願いします！
また [DeNA 公式 Twitter アカウント @DeNAxTech](https://twitter.com/DeNAxTech) では、 Blog記事だけでなく色々な勉強会での登壇資料も発信してます。ぜひフォローして下さい！
<a href="https://twitter.com/DeNAxTech?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-size="large" data-lang="ja" data-show-count="false">Follow @DeNAxTech</a><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>