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週間に一回実行