0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-11-19

前編はこちら

事前準備

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

使用ライブラリ

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

処理のフローのおさらい

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

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

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

  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メソッド)の仕様やレスポンスフォーマットはこちらも参照すること。

SlackAPI管理クラスについて

内容は以下の通り。

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));
}

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

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

underscoreGS._eachの使い方メモ

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

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(検索する文字列)という使い方。

function demoIndexOf() {
  var string = 'あか5、しろ5、砂肝5';
  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を取得

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

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

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

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は1要素のみの配列となる
      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情報は1要素のみの配列なので、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の使い方メモ

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

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

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

// 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を使用するが、オフセットとして2を足すことに注意する
      spshMng.writeCell(spshMng.column2Alpha(elKey) + (key + 2), elValue);
    });
  });

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

内容は以下の通り。


/***
@概要 スプレッドシートを管理するクラス
@詳細
***/
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);
}

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

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?