SlackとGASで水分摂取量を管理してみた - 【STEP 3】OAuth編 - CARTA TECH BLOG

SlackとGASで水分摂取量を管理してみた - 【STEP 3】OAuth編

前回のあらすじ

【STEP 2】では6つの手順でSlackからGASへの記録リクエストを実現しました。

 1. GASの追加実装
 2. Slack App作成
 3. Bot User作成
 4. Events API設定
 5. ワークスペースにAppをインストール
 6. App表示情報の編集

前回の記事
参照:SlackとGASで水分摂取量を管理してみた【STEP 1】GAS編 | CCI TECH BLOG
参照:SlackとGASで水分摂取量を管理してみた – 【STEP 2】Events API編 | CCI TECH BLOG

今回はGASからSlackへ記録結果を返信する方法をご紹介します。

作成手順

【STEP 3】GASからSlackへ記録結果を返信

1. OAuth Token取得

Bot Userから記録結果を返信するために、OAuth Tokenを取得する必要があります。

https://api.slack.com/apps にアクセスし、 作成中のApp(今回はDrinkSupporter)を選択します。

左側のメニューから「OAuth & Permissions」を選択し、「Bot User OAuth Access Token」をコピーします。(手順2で使用)

2. GASの追加実装

【STEP 2】のコードに3点の変更を加えます。全体像は次のようになります。

function doPost(e) {
  
  // パラメータ
  var postData = JSON.parse(e.postData.getDataAsString());
  
  // Slack Events API認証用
  if (postData.type == "url_verification") {
    return ContentService.createTextOutput(postData.challenge);
  }

  // 処理要否判定
  var param = postData.event.text;
  if (postData.event.subtype == "bot_message" || param == null) {
    return;
  }

  // 記録用シート取得
  var recordSheet = SpreadsheetApp.openById("スプレッドシートID").getActiveSheet();

  // 日をまたいだ場合、記録と最終更新日を更新
  var today = Utilities.formatDate(new Date(), "JST", "yyyy/MM/dd");
  if (today != Utilities.formatDate(recordSheet.getRange(2, 4).getValue(), "JST", "yyyy/MM/dd")) {
    recordSheet.getRange(2, 2).setValue(0);
    recordSheet.getRange(2, 4).setValue(today);
  }

  // ★変更1(変数追加)
  // 返信先のchannel ID
  var channel = postData.event.channel;

  // ★変更3
  // 記録
  if (/^-?\d+$/.test(param)) {
    recordSheet.getRange(2, 2).setValue(recordSheet.getRange(2, 2).getValue() + Number(param));
    reply("記録しました。本日の記録:" + recordSheet.getRange(2, 2).getValue() + "ml", channel);

  // 目標値更新
  } else if(/^goal\s\d+$/.test(param)) {
    recordSheet.getRange(2, 1).setValue(Number(param.match(/\d+/)[0]));
    reply("目標値を更新しました:" + recordSheet.getRange(2, 1).getValue() + "ml", channel);

  // 記録確認
  } else if (/^check$/.test(param)) {
    reply("本日の達成状況:" + recordSheet.getRange(2, 3).getValue() + "% "
        + "(現在 " + recordSheet.getRange(2, 2).getValue() + "ml/目標 " + recordSheet.getRange(2, 1).getValue() + "ml)", channel);

  // Help表示
  } else {
    var msg = "【Help】\r\n";
    msg += "`[number]` 記録 (ml)\r\n";
    msg += "`goal [number]` 目標値設定 (ml)\r\n";
    msg += "`check` 記録確認";
    reply(msg, channel);
  }
}

// ★変更2(function追加)
// Slackへメッセージ送信
function reply(msg, channel) {

  var payload = 
  {
    "token" : "手順1でコピーしたOAuth Token",
    "channel" : channel,
    "text" : msg
  };
  
  var options =
  {
    "method" : "post",
    "contentType" : "application/x-www-form-urlencoded",
    "payload" : payload
  };

  UrlFetchApp.fetch("https://slack.com/api/chat.postMessage", options);
}

追加実装のポイント

・(変更1)返信先のchannel ID取得コードの追加
 Bot Userの返信先を特定するため、channel ID取得のコードを追加します。

  // 返信先のchannel ID
  var channel = postData.event.channel;

・(変更2)Slackへメッセージを返信するfunctionの追加
 手順1でコピーしたOAuth Tokenを使用して、doPostの下にreply functionを追加します。

// Slackへメッセージ送信
function reply(msg, channel) {

  var payload = 
  {
    "token" : "手順1でコピーしたOAuth Token",
    "channel" : channel,
    "text" : msg
  };
  
  var options =
  {
    "method" : "post",
    "contentType" : "application/x-www-form-urlencoded",
    "payload" : payload
  };

  UrlFetchApp.fetch("https://slack.com/api/chat.postMessage", options);
}

・(変更3)Slackへのメッセージ返信内容の追加
 スプレッドシートの記録処理の下にメッセージ返信処理を「reply(メッセージ内容, channel)」の形式で追加します。
 また、併せて目標値更新等の処理も追加します。

  // 記録
  if (/^-?\d+$/.test(param)) {
    recordSheet.getRange(2, 2).setValue(recordSheet.getRange(2, 2).getValue() + Number(param));
    reply("記録しました。本日の記録:" + recordSheet.getRange(2, 2).getValue() + "ml", channel);

  // 目標値更新
  } else if(/^goal\s\d+$/.test(param)) {
    recordSheet.getRange(2, 1).setValue(Number(param.match(/\d+/)[0]));
    reply("目標値を更新しました:" + recordSheet.getRange(2, 1).getValue() + "ml", channel);

  // 記録確認
  } else if (/^check$/.test(param)) {
    reply("本日の達成状況:" + recordSheet.getRange(2, 3).getValue() + "% "
        + "(現在 " + recordSheet.getRange(2, 2).getValue() + "ml/目標 " + recordSheet.getRange(2, 1).getValue() + "ml)", channel);

  // Help表示
  } else {
    var msg = "【Help】\r\n";
    msg += "`[number]` 記録 (ml)\r\n";
    msg += "`goal [number]` 目標値設定 (ml)\r\n";
    msg += "`check` 記録確認";
    reply(msg, channel);
  }

ウェブアプリケーションの公開設定

外部からのリクエスト処理時に変更したコードが使用されるよう、公開設定を忘れずに行います。
その際「承認が必要です」のダイアログが再び表示されるので、承認を行います。

「許可を確認」をクリックします。

Googleアカウントを選択し、
「詳細」→「DrinkSupporterProject(安全ではないページ)に移動」→「許可」の流れで アクセス承認を行います。



【STEP 3】の作業はこれで完了です。

動作確認

Slackを開き、DrinkSupporterのダイレクトメッセージで確認を行うと、A~Dの動作が確認できます。

A. 水分摂取量を記録       …数値を入力した場合(例:200)
B. 目標値を更新         …「goal 数字」と入力した場合(例:goal 2500)
C. 本日の達成状況と目標値を返信 …「check」と入力した場合
D. ヘルプを返信         …上記以外の言葉を入力した場合(例:aaa)

(Helpが少し不格好なのはご愛嬌)

まとめ

【STEP 3】では2つの手順でGASからSlackへの記録結果の返信を実現することができました。

 1. OAuth Token取得
 2. GASの追加実装

全くの初心者(Slack Appが何かもGASの開き方も知らない状態)からのスタートでしたが、
最初に目標として挙げた4機能(A~D)を実現することができました。
Appの機能やGASの実装方法など工夫できる部分はたくさんあるようなので、今後更に育てていきたいと思います。

前回の記事
参照:SlackとGASで水分摂取量を管理してみた【STEP 1】GAS編 | CCI TECH BLOG
参照:SlackとGASで水分摂取量を管理してみた – 【STEP 2】Events API編 | CCI TECH BLOG

(おまけ)

今回作成したAppは、常にGoogleスプレッドシートの2行目に記録するため、複数人での利用はできません。
Slackから受信するpostDataの中身を見るとユーザーIDが含まれているので、それを用いることで上述の問題を解消できます。

・ユーザーIDの取得とGoogleスプレッドーシートの行数を特定

// ユーザーID取得
var postData = JSON.parse(e.postData.getDataAsString());
var userId = postData.event.user;

// 行番号の取得
var recordSheet = SpreadsheetApp.openById("スプレッドシートID").getActiveSheet();
var rowNum = recordSheet.createTextFinder(userId).matchEntireCell(true).findNext().getRow();

・新規ユーザーの場合は、appendRowをすることで最下行に行を追加

// appendRowのイメージ(各列の情報を配列形式で挿入する)
recordSheet.appendRow([userId, 0, 0, today]);