
Slack Events API、OpenAI API (ChatGPT)、GAS (Google Apps Script)を使用してSlackにGPTBotを導入して使えるようになります。業務でSlackを使用されているのであれば入れておくと便利かも。
Contents 非表示
Botの作成
Bot自体の作成はこちらを参考に進めてください、今回必要な権限は以下の通り。
- OAuth & Permissions
- app_mentions:read
- channels:history
- chat:write
- Event Subscriptions
- message.channels
こちらの手順を完了させて、チャンネル追加ができればBot自体の作成はOKです。

OpenAI API Keyの取得
OpenAIのアカウントが必要なので作成してない場合は先に作成を完了させてください。
https://platform.openai.com/api-keys
こちらにアクセスして、APIKeyを取得してください。
こちらでAPI Keyを取得したら忘れないように一旦どこかにメモをしておきましょう、以降のGASで使用します。
Google Apps Scriptの実装
こちらでSlackとChatGPTの橋渡しを担わせます。
こちらにアクセスして、新規プロジェクトの作成を行い、以下スクリプトを貼り付けてください。
const slackBotId = PropertiesService.getScriptProperties().getProperty('slackBotId');
const slackBotToken = PropertiesService.getScriptProperties().getProperty('slackBotToken');
const openAIApiKey = PropertiesService.getScriptProperties().getProperty('openAIApiKey');
function doPost(e, _IsTest = false) {
const params = (_IsTest) ? JSON.parse(e) : JSON.parse(e.postData.getDataAsString());
// Slackリクエスト検証
if (params.type === 'url_verification') {
return ContentService.createTextOutput(params.challenge);
}
const slackChannel = params.event.channel;
const paramsText = params.event.text;
// 無限ループ回避
if ('subtype' in params.event) {
return ContentService.createTextOutput('');
}
// @slackBotIdに反応してGPTからのレスをスレッドに返す
if (paramsText.includes(`<@${slackBotId}>`) && params.event.user !== slackBotId) {
// 3秒タイムアウトリトライ対策 (再送処理リクエストは200を返すだけ)
let cache = CacheService.getScriptCache();
if (cache.get(params.event.client_msg_id) == 'done') {
return ContentService.createTextOutput();
} else {
cache.put(params.event.client_msg_id, 'done', 600);
}
let history;
if (params.event.thread_ts) {
history = getThreadHistory(slackChannel, params.event.thread_ts);
} else {
history = [{ role: 'user', content: paramsText }];
}
const message = requestToChatGPT(history);
const new_message = replace_mention_with_username(message);
sendMessagesToSlack(slackChannel, new_message, params.event.thread_ts || params.event.ts);
}
return ContentService.createTextOutput('');
}
function getThreadHistory(channel, thread_ts, max_history_len = 20) {
const params = {
'headers': {
'Authorization': 'Bearer ' + slackBotToken
}
};
const url = 'https://slack.com/api/conversations.replies?ts=' + thread_ts + '&channel=' + channel;
const response = UrlFetchApp.fetch(url, params);
const json = JSON.parse(response.getContentText('UTF-8'));
if (json.ok) {
const messages = json.messages;
const results = [];
for (let i = 0; i < messages.length; i++) {
const message = messages[i];
const role = message.user == slackBotId ? 'assistant' : 'user';
results.push({ role: role, content: message.text });
if (results.length >= max_history_len) {
break;
}
}
return results;
} else {
throw new Error('Failed to get thread: ' + json.error);
}
}
function replace_mention_with_username(text) {
const mentions = text.match(/<@\w+>/g);
let replaced_text = text;
if (mentions !== null) {
for (let j = 0; j < mentions.length; j++) {
const mention = mentions[j];
const userId = mention.match(/<@(\w+)>/)[1];
const username = getUserName(userId);
if (username !== null) {
const pattern = new RegExp(mention, "g");
replaced_text = replaced_text.replace(pattern, `at:${username}`);
}
}
}
return replaced_text;
}
function getUserName(userId) {
const url = "https://slack.com/api/users.info?user=" + userId;
const params = {
'headers': {
'Authorization': 'Bearer ' + slackBotToken
}
};
const response = JSON.parse(UrlFetchApp.fetch(url, params).getContentText('UTF-8'));
Logger.log(response);
if (response.ok) {
return response.user.name;
} else {
return null;
}
}
function sendMessagesToSlack(channel, message, thread_ts) {
const url = "https://slack.com/api/chat.postMessage";
var payload = {
"token": slackBotToken,
"channel": channel,
"thread_ts": thread_ts,
"text": message
};
params = {
"method": 'post',
'payload': payload
}
const response = UrlFetchApp.fetch(url, params);
return response.ok;
}
function requestToChatGPT(history) {
const apiUrl = 'https://api.openai.com/v1/chat/completions';
const headers = {
'Authorization': 'Bearer ' + openAIApiKey,
'Content-type': 'application/json; charset=UTF-8'
};
const params = {
'headers': headers,
'method': 'POST',
'muteHttpExceptions': true,
'payload': JSON.stringify({
'model': 'gpt-4o-mini',
'messages': history,
'temperature': 0.7,
})
};
const response = JSON.parse(UrlFetchApp.fetch(apiUrl, params).getContentText('UTF-8'));
return response.choices[0].message.content;
}
slackBotId
, slackBotToken
, openAiApiKey
はスクリプト プロパティに保存します。

- openAIApiKey
- OpenAI Console画面で取得したKeyをそのままここに貼り付けましょう
- slackBotId
- Botの識別ID、以下添付画像のCopy Member IDでコピーされたものをそのまま貼り付けでOK
- slackBotToken
- Slack Events APIコンソールから確認
OAuth & Permissions
にあるxoxb
から始まるものがTOKENです。
- Slack Events APIコンソールから確認

全て入れ終わったらGASをデプロイして、Slack上で動作確認をしましょう、レスポンスが正常に返ってこれば成功です。