1
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?

身の回りの困りごとを楽しく解決! by Works Human IntelligenceAdvent Calendar 2023

Day 20

Slackの投稿1週間分を要約・メンションで知らせてもらう

Last updated at Posted at 2023-12-20

Google Apps Script(GAS)を使用して、Slackの特定のチャンネルから1週間分の情報を取得し、それをGoogleスプレッドシートにまとめ、各ユーザーごとに1週間の発言内容の要約をGPT APIで行った後各ユーザーにリプライするスクリプトです。

【用意するもの】
・Slack APIのアクセストークン
・SlackのチャンネルID
・GPT APIのAPIキー
・google Spreadsheet

GASのメニュー「プロジェクトの設定」→「スクリプト プロパティ」にアクセストークンやAPIキーを保管しておきます。
・GPT_APIKEY
・SLACK_TOKEN

初期設定

var slackToken = PropertiesService.getScriptProperties().getProperty('SLACK_TOKEN');
var apiKey = PropertiesService.getScriptProperties().getProperty('GPT_APIKEY');
//Slackのチャンネル指定
var CHANNEL_ID = 'CHANNELID'

SlackのAPIにアクセスする

function callSlackAPI(path, params) {

  // メッセージ取得系のSlackAPIの制限が1分間で50回までなので、超えないようにスリープする
  Utilities.sleep(1100);

  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('&');
  console.log(url);
  var headers = {
    'Authorization': 'Bearer '+ slackToken
  };
  var options = {
    'headers': headers
  };
  var resp = UrlFetchApp.fetch(url, options);
  var data = JSON.parse(resp.getContentText());
  if (data.error) {
    throw "Error " + path + ": " + data.error;
  }
  return data;
}

メッセージの取得

function getMessages(ch_id, timestamp_from, timestamp_to, messages, next_cursor) {
  if (!messages) {
    messages = [];
  }

  var param = [];
  param['limit'] = 1000;
  param['channel'] = ch_id;
  param['oldest'] = timestamp_from;
  param['latest'] = timestamp_to;
  if (next_cursor) {
    param['cursor'] = next_cursor;
  }

  var resp = callSlackAPI('conversations.history', param);
  messages = messages.concat(resp.messages);
  if (resp.response_metadata && resp.response_metadata.next_cursor) {
    messages = getMessages(ch_id, timestamp_from, timestamp_to, messages, resp.response_metadata.next_cursor);
  }
  return messages;
}

リプライ・スレッドの取得

function getReplies(ch_id, thread_ts, timestamp_from, timestamp_to, messages, next_cursor) {
  if (!messages) {
    messages = [];
  }

  var param = [];
  param['limit'] = 1000;
  param['channel'] = ch_id;
  param['ts'] = thread_ts;
  param['oldest'] = timestamp_from;
  param['latest'] = timestamp_to;
  if (next_cursor) {
    param['cursor'] = next_cursor;
  }

  var resp = callSlackAPI('conversations.replies', param);
  messages = messages.concat(resp.messages);
  if (resp.response_metadata && resp.response_metadata.next_cursor) {
    messages = getReplies(ch_id, thread_ts, timestamp_from, timestamp_to, messages, resp.response_metadata.next_cursor);
  }
  return messages;
}

タイムスタンプの計算用

function getTimestamp(date) {
  var ms = new Date(date).getTime();
  var s = ms / 1000; // ms→sに変換
  return s;
}

メッセージをスプシに集約
タイムスタンプとユーザーが同じならば記録しない

function messageAnalysis(message) {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  var timestamp = new Date(parseFloat(message.ts) * 1000);
  var userId = message.user;
  var text = message.text;

  // タイムスタンプとユーザーIDで重複をフィルター
  var range = sheet.getDataRange();
  var values = range.getValues();
  var isDuplicate = values.some(function(row) {
    var existingTimestamp = row[0];
    var existingUserId = row[1];
    return existingTimestamp.getTime() === timestamp.getTime() && existingUserId === userId;
  });

  // 重複がない場合のみ新しい行を追加
  if (!isDuplicate) {
    sheet.appendRow([timestamp, userId, text]);
  }
}

1日分のログを取得

function run() {
  var now = new Date();
  var oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);

  var timestamp_to = getTimestamp(now);
  var timestamp_from = getTimestamp(oneDayAgo);

  var messages = getMessages(CHANNEL_ID, timestamp_from, timestamp_to);

  messages.forEach(function(message) {
    // スレッドのあるメッセージの場合
    if (message.thread_ts) {
      if (message.subtype == 'thread_broadcast') {
        return;
      }
      var thread_messages = getReplies(CHANNEL_ID, message.thread_ts, timestamp_from, timestamp_to);
      thread_messages.forEach(function(thread_message) {
        messageAnalysis(thread_message);
      });
    } else {
      messageAnalysis(message);
    }
  });
}

GTP APIを使用してログの要約をする

function getSummaryFromGPT(messages) {
  var apiKey = PropertiesService.getScriptProperties().getProperty('GPT_APIKEY');
  var apiEndpoint = 'https://api.openai.com/v1/chat/completions';

  // プロンプトを作成
  var formattedMessages = messages.map(function(message) {
    return {
      role: "user",
      content: message
    };
  });

  formattedMessages.push({
    role: "system",
    content: "以下のメッセージを要約してユーザーの動向を教えてください。約束事が含まれていればリマインドをお願いします。"
  });


  var payload = {
    model: "gpt-3.5-turbo-1106",
    messages: formattedMessages,
    max_tokens: 2048,
    temperature: 0.9
  };

  var options = {
    method: 'post',
    contentType: 'application/json',
    headers: {
      'Authorization': 'Bearer ' + apiKey
    },
    payload: JSON.stringify(payload),
    muteHttpExceptions: true
  };

  try {
    var response = UrlFetchApp.fetch(apiEndpoint, options);
    var jsonResponse = JSON.parse(response.getContentText());

    if (jsonResponse.choices && jsonResponse.choices.length > 0) {
      return jsonResponse.choices[0].message.content.trim();
    } else {
      Logger.log('No valid response or empty choices array.');
      return '';
    }
  } catch (error) {
    Logger.log('Error fetching summary: ' + error);
    return '';
  }
}

Slackにメッセージを送る

function sendSlackMessage(channelId, userId, message, token) {
  var apiUrl = 'https://slack.com/api/chat.postMessage';
  var payload = {
    channel: channelId,
    text: `<@${userId}>: ${message}`,
    as_user: true
  };

  var options = {
    method: 'post',
    contentType: 'application/json',
    headers: {
      'Authorization': 'Bearer ' + token
    },
    payload: JSON.stringify(payload)
  };

  UrlFetchApp.fetch(apiUrl, options);
}

1週間分のログを集約

function summarizeWeeklyLogs() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  var oneWeekAgo = new Date();
  oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);

  var range = sheet.getDataRange();
  var data = range.getValues();
  var userMessages = {};

  data.forEach(function(row) {
    var timestamp = row[0];
    var userId = row[1];
    var text = row[2];

    if (timestamp >= oneWeekAgo) {
      if (!userMessages[userId]) {
        userMessages[userId] = [];
      }
      userMessages[userId].push(text);
    }
  });
  for (var userId in userMessages) {
    var messages = userMessages[userId];
    var summary = getSummaryFromGPT(userMessages[userId]);

    // Slackチャンネルに要約をリプライ
    sendSlackMessage(CHANNEL_ID, userId, summary, slackToken);
  }
}

ログは一定期間経つと消えたほうが良いかも

実行
GASのトリガー設定でrun()を1日に一回実行
summarizeWeeklyLogs()を1週間に一回実行

1
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
1
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?