SlackのFAQのやり取りに掛かっている時間を知りたい(後編)

前編は[こちら](https://qiita.com/AkiyoshiChan_/items/13b1528c51768f3e04c7)
## 事前準備

- SlackAPIのtoken取得
  - 以下を有効にしておくこと
    - channel.history
    - users:read
- Googleスプレッドシート
- やりきる気持ち



## 使用ライブラリ

- Momentライブラリ
  - SlackAPIから返却される時刻がUNIXなので、変換に使用する
  - プロジェクトキー：MHMchiX6c1bwSqGM1PZiW_PxhMjh3Sh48
- UnderscoreGSライブラリ
  - とにかく便利
  - プロジェクトキー：MiC3qjLYVUjCCUQpMqPPTWUF7jOZt2NQ8



## 処理のフローのおさらい

1. 対象チャンネルからコメントを取得
2. 特定グループへのメンションが行われているコメントを質問と判断し、抽出
3. 質問コメントからスレッドIDを取得
4. スレッドの全コメントを取得
5. スレッド内で質問者が最後に発言したコメントを取得
6. スプレッドシートに書き込み



各処理ごとに説明を記載する。

### 対象チャンネルからコメントを取得

```gas
  var token = "SlackAPIのトークン情報";
  
  // SlackAPI管理クラスを作成
  var slackMng = new SlackAPIManage(token);
  
  /** 1. 対象チャンネルからコメントを取得 **/
  
  // channel: 対象チャンネル, count: 取得コメント件数
  var channel = "対象チャンネル";
  var count = 2000;
  var getComStartTime = '2018/11/01 00:00:00' // 取得開始日
  var getComEndTime = '2018/11/30 23:59:59' // 取得終了日
  
  // 対象チャンネルからコメントを取得
  var comments = slackMng.getChannelHistory(channel, getComStartTime, getComEndTime, count);
  
  // User名を使用するためにUserListを取得（optional）
  var memberList = slackMng.getUserList(count);  
```

コメントを取得するAPI(channels.historyメソッド)の仕様やレスポンスフォーマットは[こちら](https://api.slack.com/methods/channels.history)も参照すること。

#### SlackAPI管理クラスについて

内容は以下の通り。

```gas
SlackAPIManage = function(accessToken){
  this.accessToken = 'token=' + accessToken; 
}

// GET Requestのみを想定している
// 取得したレスポンスをJSONフォーマットへ変換して返却する
SlackAPIManage.prototype.getRequest = function(url){
  
  var response = UrlFetchApp.fetch('https://slack.com/api/' + url);
  var results = JSON.parse(response.getContentText());
  return results;
}

// URL Queryを作成する関数
SlackAPIManage.prototype.createURL = function(method, paramDict){
  var url = method + '?' + this.accessToken;
  // 例えば{"order":"あか","drink":"ハイボール"}という辞書の場合、
  // "method名?token=トークン&order=あか&drink=ハイボール"と出力する用になっている
  underscoreGS._each(paramDict, function(value, key) {
    url = url + '&' + key + '=' + value;
  })  
  return url;
}

// チャンネル履歴取得
SlackAPIManage.prototype.getChannelHistory = function(channel, startTime, endTime, count){
  var method = 'channels.history';
  var paramDict = {
    'channel': channel,
    'oldest': Moment.moment(startTime).format('X'),
    'latest': Moment.moment(endTime).format('X'),
    'count':count
  };
  return this.getRequest(this.createURL(method, paramDict));
}

// ユーザーリスト取得
SlackAPIManage.prototype.getUserList = function(count){
  var method = 'users.list';
  var paramDict = {
    'count': count
  };
  return this.getRequest(this.createURL(method, paramDict));
}

// 特定スレッドのコメント全件取得
SlackAPIManage.prototype.getReplies = function(channel, threadID){
  var method = 'channels.replies';
  var paramDict = {
    'channel': channel,
    'thread_ts': threadID
  };
  return this.getRequest(this.createURL(method, paramDict));
}
```





### 特定グループへのメンションが行われているコメントを質問と判断し、抽出

```gas
/** 2. 特定グループへのメンションが行われているコメントを質問と判断し、抽出 **/
  var groupName = "@グループ名";
  // QAの情報を挿入するための空配列を作成
  // ※これは最後にまとめてスプレッドシートに書き込むため、一旦配列に格納している
  var QAList = [];
  underscoreGS._each(comments['messages'], function(value, key) {
    // groupNameが本文に入っていたら質問と判断する
    if (value['text'].indexOf(groupName) != -1){...}
    })
```



#### underscoreGS._eachの使い方メモ

配列の要素を順番に取得する関数。

```gas
function demoEach() {
  list = [
    {"order":"あか"},
    {"order":"しろ"},
    {"order":"純けい"}
  ];
  underscoreGS._each(list, function(value, key) {
    Logger.log(key + ':' + value['order'] + '5')
  });
}

/**　result
[18-11-14 11:04:37:733 JST] 0:あか5
[18-11-14 11:04:37:734 JST] 1:しろ5
[18-11-14 11:04:37:736 JST] 2:純けい5
**/
```

#### String.indexOf(str)の使い方メモ

文字列内から検索したい特定文字が**左から数えてどこに位置しているか**を検索する関数。ヒットしなかった（検索したい特定文字列が存在しなかった）ら、**-1**が返却される。

検索**される**文字列.indexOf(検索**する**文字列)という使い方。

```gas
function demoIndexOf() {
  var string = 'あか５、しろ５、砂肝５';
  Logger.log(string.indexOf('あか'));
  Logger.log(string.indexOf('しろ'));
  Logger.log(string.indexOf('純けい'));
}

/** result
[18-11-14 11:14:43:906 JST] 0.0
[18-11-14 11:14:43:907 JST] 4.0
[18-11-14 11:14:43:907 JST] -1.0
**/
```



### 質問コメントからスレッドIDを取得

### スレッドの全コメントを取得

### スレッド内で質問者が最後に発言したコメントを取得

この処理は質問と判断されて毎に行うため、まとめて記載する。

```gas
underscoreGS._each(comments['messages'], function(value, key) {
    // groupNameが本文に入っていたら質問と判断する
    if (value['text'].indexOf(groupName) != -1){
    
      /** 3. 質問コメントからスレッドIDを取得 **/
      var threadID = value['thread_ts'];
      
      /** 4. スレッドの全コメントを取得 **/
      var thread = slackMng.getReplies(channel, threadID); 
      
      /** 5. スレッド内で質問者が最後に発言したコメントを取得 **/
      var userID = value['user'];
      
      // スレッド内の質問者の発言を全て抽出 (このとき、取得されるリストは時系列に並んでいる想定)
      var userComments = underscoreGS._filter(thread['messages'], function(value, key) {
        return value['user'] == userID;
      });
      // 質問が無い場合は
      if (userComments.length == 0) {
        return;
      }
      // 最後のコメントを取得（時系列順に並んでいるため、最後のコメントを取得している）
      var lastComment = userComments[userComments.length - 1];
      
      // スプレッドシートに書き込むための情報を格納するためのオブジェクトを作成
      var QA = {};
      // 必要な情報を設定
      // userIDに一致するユーザーを取得する
      // このとき取得するuserは１要素のみの配列となる
      var user = underscoreGS._filter(memberList['members'], function(value, key) {
        return value['id'] == userID;
      });
      var startTime = Moment.moment.unix(value['thread_ts']);
      var endTime = Moment.moment.unix(lastComment['ts']);
      var diffTime = endTime.diff(startTime, 's');
      QA['質問投稿者'] = user[0]['name']; // 取得するuser情報は１要素のみの配列なので、0を指定する
      QA['質問投稿時刻'] = startTime.format('YYYY年MM月DD日　HH:mm:ss');
      QA['最終回答時刻'] = endTime.format('YYYY年MM月DD日　HH:mm:ss');
      QA['回答時間(s)'] = diffTime;
      
      // 上記の情報を全てQAListに追加する
      QAList.push(QA);
    }
    
  });
```



#### underscoreGS._filterの使い方メモ

配列から条件に合った要素を抽出する関数。

```gas
function demoFilter() {
  list = [
    {
      "order":"あか",
      "drink":"ハイボール"
    },
    {
      "order":"しろ",
      "drink":"梅酒"
    },
    {
      "order":"純けい",
      "drink":"ビール"
    }
  ];
  var NOTBeerOrder = underscoreGS._filter(list, function(value, key) {
  	// ドリンクがビールでないオーダーだけ抽出
    return value['drink'] != 'ビール';
  });
  Logger.log(NOTBeerOrder);
}

/** result
[18-11-14 11:53:37:891 JST] [{drink=ハイボール, order=あか}, {drink=梅酒, order=しろ}]
**/
```



### スプレッドシートに書き込み

今回は、列名のみ記載したスプレッドシートにQAListの内容が転記されることを目標とする。

QAListに格納されている情報は以下の通り。

```
[{
		"質問投稿者": "しろ",
		"質問投稿時刻": "2018年11月14日　11:13:05",
		"最終回答時刻": "2018年11月14日　11:24:52",
		"回答時間(s)": 707.0
	}, {
		"質問投稿者 ": "あか",
		"質問投稿時刻": "2018年11月13日　17:13:59",
		"最終回答時刻": "2018年11月13日　18:20:21",
		"回答時間(s)": 3982.0
	},
	{
		"質問投稿者": "純けい",
		"質問投稿時刻": "2018年11月13日　13:39:27",
		"最終回答時刻": "2018年11月13日　13:45:25",
		"回答時間(s)": 358.0
	}
]
```

上記の情報を以下のようにスプレッドシートに転記したい。

| 質問投稿者 | 質問投稿時刻             | 最終回答時刻             | 回答時間(s) |
| ---------- | ------------------------ | ------------------------ | ----------- |
| しろ       | 2018年11月14日　11:13:05 | 2018年11月14日　11:24:52 | 707.0       |
| あか       | 2018年11月13日　17:13:59 | 2018年11月13日　18:20:21 | 3982.0      |
| 純けい     | 2018年11月13日　13:39:27 | 2018年11月13日　13:45:25 | 358.0       |



ソースコードは以下となる。

```gas
// FAQ情報が記載されたスプレッドシートの情報
  var spreadFileID = "スプレッドシートID;
  var sheetName = "シート名";
  
  // SpreadSheet管理クラスを作成
  var spshMng = new SpreadSheetManage(spreadFileID, sheetName);
  
  /** 6. スプレッドシートに書き込み **/
  // QAList(配列)の要素を取得する
  //　keyには配列の要素の番号が格納される [0,1,2...]
  underscoreGS._each(QAList, function(value, key) {
    // 要素の各key,value(変数名はelKey, elValue)を取得する
    underscoreGS._each(value, function(elValue, elKey) {
      // elKeyを列名、elValueをセルに入力する値として書き込み関数に渡す
      // 行番号はkeyを使用するが、オフセットとして２を足すことに注意する
      spshMng.writeCell(spshMng.column2Alpha(elKey) + (key + 2), elValue);
    });
  });

```



#### スプレッドシート管理ライブラリについて

内容は以下の通り。

```gas

/***
@概要 スプレッドシートを管理するクラス
@詳細
***/
SpreadSheetManage = function(spreadFileID, sheetName){
  this.spreadFileID = spreadFileID;
  this.sheetName = sheetName;
  this.sheet = SpreadsheetApp.openById(spreadFileID).getSheetByName(sheetName);
  this.twoDimArray = this.getTwoDimArry();
}

//列名からアルファベットを生成する
SpreadSheetManage.prototype.column2Alpha = function(colName) {
  var colNum = this.twoDimArray[0].indexOf(colName);
  var result = this.sheet.getRange(1, colNum + 1);
  result = result.getA1Notation().replace(/\d/,'');
  return result;
}

/***
@概要 sheet情報から二次元配列を取得する
***/
SpreadSheetManage.prototype.getTwoDimArry = function(){
  //データが入力されている範囲を取得
  var range = this.sheet.getDataRange();  
  //Rangeの内容を2次元配列で取得
  return range.getValues();
}

SpreadSheetManage.prototype.writeCell = function(cell, value){
  this.sheet.getRange(cell).setValue(value);
}
```

あとは全部をくっつけるだけ！
お疲れ様でした。