ベストクラウドさんの スラック+ガスログ保存のコードを解読する


https://best-cloud.jp/slack-message-log-auto-save-gas/
ベストクラウドさんの スラックの全チャンネルのメッセージを
ガスでスプレッドシートに全て保存する記事を読んで試した
しかし、肝心のコードでは

以下のテキストを最初から最後まで全てコピーしてください。


とあり、わずかなコメントだけだったので解釈してみる

setpropertyで スラックラグアピアントトケや フォールドラントIDをセットする


最初に  SerPropoerties()をしている
この関数は
function SetProperties() {
  PropertiesService.getScriptProperties().setProperty(
    'slack_api_token', 
    'xoxp-111-222-333-444'
  );
  PropertiesService.getScriptProperties().setProperty(
    'folder_id', '123ef'
  );
  PropertiesService.getScriptProperties().setProperty(
    'last_channel_no', -1
  );
}
ガスの内部プロパティ?に埋め込む setpropertyメソッドを使って
スラックロックアピワントークンに xoxp‐1234などのトークン
フォールドラントIDに グーグルドライブのフォルダ ID
リトルビッグプラネットに -1
これを初手で入れている
これはわかりやすい.

getpropertyで スラックロックアピワントークン や フォールドラントIDでデータを取得して定数に入れる


const FOLDER_ID = PropertiesService.getScriptProperties().getProperty(
  'folder_id'
);
if (!FOLDER_ID) {
  throw 'You should set "folder_id" property from [File] > [Project properties] > [Script properties]';
}
const API_TOKEN = PropertiesService.getScriptProperties().getProperty(
  'slack_api_token'
);
if (!API_TOKEN) {
  throw 'You should set "slack_api_token" property from [File] > [Project properties] > [Script properties]';
}
先ほど スラックロックアピワントークンや フォールドラントIDにいれたトークンとフォルダIDを
フォールドラントIDと アピワントークンと言う定数に入れ直す.
そしてなかった場合のエラー処理を書く.
先ほど ガスのプロパティに入れたものを出して定数に入れるだけなので
ここもわかりやすい.

グーグルドライブのフォルダ、シート、シートへのコントローラを作成する


let token = API_TOKEN
アピワントークンを今度は小文字の変数に入れるなぜここでコピーしているかは不明.
let folder = FindOrCreateFolder(
  DriveApp.getFolderById(FOLDER_ID), FOLDER_NAME
);
先ほどの フォールドラントIDと省略した フォールド名の文字列で
作者の自作メソッドの フォルダで、フォルダがない場合は作成する
let ss = FindOrCreateSpreadsheet(folder, SpreadSheetName);
中でも同じように、 フォルダとスプレッドシート名からスプレッドシートを探して
スプレッドシートがない場合は作成する
let ssCtrl = new SpreadsheetController(ss, folder);
そしてそのスプレッドシートのコントローラーを作成する

スラックへのアクセサを作成して


// Slack へのアクセサ
var SlackAccessor = (function () {
  function SlackAccessor(apiToken) {
  this.APIToken = apiToken;
}
これは 156行目にあった作者の自作クラス
let slack = new SlackAccessor(API_TOKEN);
同じように スラックのアクセサを作る
// メンバーリスト取得
const memberList = slack.requestMemberList();
// チャンネル情報取得
const channelInfo = slack.requestChannelInfo();
そこからメンバーリストとチャンネルリストを取得する
この requestMemberList と requestChannelInfo も
後述の著者の自作メソッドなのでかなり難しい.
let first_exec_in_this_channel = true;

for (let ch of channelInfo) {
  console.log(ch.name)
  let timestamp = ssCtrl.getLastTimestamp(ch, 0);
  let messages = slack.requestMessages(ch, timestamp);
  ssCtrl.saveChannelHistory(ch, messages, memberList, token);
  if (timestamp == '1') {
  first_exec_in_this_channel = true;
  }
}
そして チャネリングを マップして コンソールにだす
スプレッドシートの最後のタイムスタンプ?を取得し
スラックからチャンネルのメッセージをチャンネルID ?とそのタイムスタンプで取得し
スプレッドシートのコントローラに渡す
タイムスタンプが 1であればチャンネルの最初で終わる?
// スレッドは重い処理なので各回に1回のみ行う
const ch_num = (
  parseInt(PropertiesService.getScriptProperties().getProperty(
    'last_channel_no')
  ) + 1
) % channelInfo.length;
console.log('ch_num');
console.log(ch_num);
const ch = channelInfo[ch_num]
console.log(ch);
最後のチャンネル名、-1を入れたものに +1をしてチャンネル数で割る
チャンネルの中のそのプロパティを出力???ここは意味がわからない
var p = SlackAccessor.prototype;
前述で作った スラックのアクセサからプロトタイプを取得して p に入れる
// API リクエスト
p.requestAPI = function (path, params) {
  if (params === void 0) { params = {}; }
  var url = "https://slack.com/api/" + path + "?";
  var qparams = [];
  for (var k in params) {
    qparams.push(encodeURIComponent(k) + "=" + encodeURIComponent(params[k]));
  }
  url += qparams.join('&');
  var headers = {
    'Authorization': 'Bearer ' + this.APIToken
  };
  console.log("==> GET " + url);

  var options = {
    'headers': headers, // 上で作成されたアクセストークンを含むヘッダ情報が入ります
  };
  var response = UrlFetchApp.fetch(url, options);
  var data = JSON.parse(response.getContentText());
  if (data.error) {
    console.log(data);
    console.log(params);
    throw "GET " + path + ": " + data.error;
  }
  return data;
};
  var qparams = [];
  for (var k in params) {
    qparams.push(encodeURIComponent(k) + "=" + encodeURIComponent(params[k]));
  }
クエリパラーメータの配列をエンコードして &= でくっつけるこのロジックはかなり便利そう.
そしてここで 東京工業大学と言う APIリクエストの
https://slack.com/api/ 
の エンドポイント URL でのラッパーを作成する.
p.requestAPI = function (path, params) {
  var url = "https://slack.com/api/" + path + "?";
  var qparams = [];
  for (var k in params) {
    qparams.push(encodeURIComponent(k) + "=" + encodeURIComponent(params[k]));
  }requestAPI
  url += qparams.join('&');
引数からクエリとパスを受け取り、それらをエンドポイントの基礎 URLに加算する
  var headers = {
    'Authorization': 'Bearer ' + this.APIToken
  };
  var options = {
    'headers': headers, 
  };
  var response = UrlFetchApp.fetch(url, options);

  var data = JSON.parse(response.getContentText());
  if (data.error) {
    console.log(data);
    console.log(params);
    throw "GET " + path + ": " + data.error;
  }
  return data;
}
そして HTTPヘッダーの認証情報で 「ベアラー」+ apichenトークンをつけて
先ほどの URLと 今のヘッダーをオプションとして渡し、レスポンスを受け取る
そのレスポンスを JSONにパースしてエラー処理する
// チャンネル情報取得
p.requestChannelInfo = function () {
  var response = this.requestAPI('conversations.list');
  response.channels.forEach(function (channel) {
    console.log("channel(id:" + channel.id + ") = " + channel.name);
  });
  return response.channels;
};

// 特定チャンネルのメッセージ取得
p.requestMessages = function (channel, oldest) {
  var _this = this;
  if (oldest === void 0) { oldest = '1'; }

  var messages = [];
  var options = {};
  options['oldest'] = oldest;
  options['count'] = HISTORY_COUNT_PER_PAGE;
  options['channel'] = channel.id;

  var loadChannelHistory = function (oldest) {
  if (oldest) {
    options['oldest'] = oldest;
  }
  var response = _this.requestAPI('conversations.history', options);
  messages = response.messages.concat(messages);
  return response;
};
この APIのラッパーをチャンネル情報の取得とメッセージの取得で使用する
// チャンネル情報取得
p.requestChannelInfo = function () {
  var response = this.requestAPI('conversations.list');
  response.channels.forEach(function (channel) {
    console.log("channel(id:" + channel.id + ") = " + channel.name);
  });
  return response.channels;
};

// 特定チャンネルのメッセージ取得
p.requestMessages = function (channel, oldest) {
  var _this = this;
  if (oldest === void 0) { oldest = '1'; }

  var messages = [];
  var options = {};
  options['oldest'] = oldest;
  options['count'] = HISTORY_COUNT_PER_PAGE;
  options['channel'] = channel.id;

  var loadChannelHistory = function (oldest) {
  if (oldest) {
    options['oldest'] = oldest;
  }
  var response = _this.requestAPI('conversations.history', options);
  messages = response.messages.concat(messages);
  return response;
};

まとめ


ガスの スラックAPIの利用ではしっかりと APIリクエストのラッパーを書いてその呼び出しにも一つ一つロジックを書いていかないと使えないことがわかった