Slackボット、外部サービスに頼らなくても作れる
ミドリ: タツヤさん、SlackにFAQボットを入れたいって上司に言われたんですけど、外部のチャットボットサービスって月額5,000〜30,000円くらいしますよね。しかも社内データを外部に送るのはセキュリティ的に不安で……。
タツヤ: GASで作れば完全無料、しかもGoogle Workspaceの中で完結するからデータが外部に出ない。
ミドリ: え、GASでSlackのボットが作れるんですか?
タツヤ: 作れる。しかも意外と簡単。SlackもTeamsも、Webhook経由でGASと連携できる。基本的な仕組みを理解すれば1時間でプロトタイプが動くよ。
ミドリ: 1時間! さすがに盛ってません?
タツヤ: 盛ってない。順番に見ていこう。
GAS × Slackの基本アーキテクチャ
ミドリ: まず全体像を教えてください。GASとSlackがどうつながるんですか?
タツヤ: 2つの連携パターンがある。
| パターン | 方向 | 用途 | 難易度 |
|---|---|---|---|
| Incoming Webhook | GAS → Slack | 通知・アラート・定期レポート | 簡単 |
| Slash Command | Slack → GAS | ユーザーからの問い合わせ応答 | やや簡単 |
ミドリ: Incoming Webhookが「GASからSlackに送る」、Slash Commandが「SlackからGASに問い合わせる」ですね。
タツヤ(nodding): その通り。まずは簡単なIncoming Webhookから始めよう。
パターン1:Incoming Webhookで通知ボット
ミドリ: 何が通知できるんですか?
タツヤ: 例えばこういうもの。
- 毎朝の売上レポート — スプレッドシートの売上データを集計してSlackに投稿
- フォーム回答通知 — Google Formsに回答があったらSlackに通知
- 期限アラート — タスクの締切が近づいたら担当者にメンション
- 異常値検知 — スプレッドシートの数値が閾値を超えたら警告
タツヤ: セットアップは3ステップ。
ステップ1:SlackでWebhook URLを取得
タツヤ: Slackの管理画面から「Incoming Webhooks」アプリを追加して、投稿先チャンネルを選ぶ。するとWebhook URLが発行される。
ステップ2:GASからメッセージを送信
function sendSlackNotification(message, channel) {
const webhookUrl = PropertiesService.getScriptProperties()
.getProperty('SLACK_WEBHOOK_URL');
const payload = {
text: message,
username: '社内ボット',
icon_emoji: ':robot_face:'
};
UrlFetchApp.fetch(webhookUrl, {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify(payload)
});
}
ステップ3:トリガーで定期実行
タツヤ: GASのトリガー機能で「毎朝9時に実行」とか「フォーム送信時に実行」を設定すれば完成。
ミドリ(surprised): これだけですか? 本当にシンプルですね。
タツヤ: 実際のユースケースを1つ見せよう。毎朝の売上サマリーをSlackに投稿するスクリプトがこれ。
function dailySalesReport() {
const sheet = SpreadsheetApp.openById('シートID')
.getSheetByName('売上データ');
const today = new Date();
const data = sheet.getDataRange().getValues();
// 当日の売上を集計
let totalSales = 0;
let dealCount = 0;
const todayStr = Utilities.formatDate(today, 'Asia/Tokyo', 'yyyy-MM-dd');
data.forEach(row => {
if (Utilities.formatDate(new Date(row[0]), 'Asia/Tokyo', 'yyyy-MM-dd') === todayStr) {
totalSales += row[3]; // 売上金額列
dealCount++;
}
});
const message = `:chart_with_upwards_trend: *本日の売上サマリー*\n`
+ `日付: ${todayStr}\n`
+ `成約件数: ${dealCount}件\n`
+ `売上合計: ${totalSales.toLocaleString()}円\n`
+ `目標達成率: ${Math.round(totalSales / 1000000 * 100)}%`;
sendSlackNotification(message);
}
ミドリ: 毎朝チャンネルに売上が流れてくるのは便利ですね。わざわざスプレッドシートを開く手間がなくなる。
パターン2:Slash Commandで問い合わせボット
ミドリ: Slash Commandってなんですか?
タツヤ: Slackで /faq 有給休暇 みたいに打つと、ボットが回答を返してくれる機能。ユーザーが能動的に情報を取りに行くパターンだ。
セットアップ手順
タツヤ(nodding): まずGASをWebアプリとしてデプロイする。これでGASにURLが割り当てられる。
function doPost(e) {
// Slackからのリクエストを解析
const params = e.parameter;
const command = params.command; // /faq
const text = params.text; // 検索キーワード
const userId = params.user_id;
// FAQスプレッドシートを検索
const answer = searchFAQ(text);
// Slackに返す
return ContentService.createTextOutput(JSON.stringify({
response_type: 'ephemeral', // 本人にだけ見える
text: answer
})).setMimeType(ContentService.MimeType.JSON);
}
function searchFAQ(query) {
const sheet = SpreadsheetApp.openById('FAQシートID')
.getSheetByName('FAQ');
const data = sheet.getDataRange().getValues();
// キーワードでFAQを検索
for (let i = 1; i < data.length; i++) {
const question = data[i][0];
const answer = data[i][1];
if (question.includes(query) || data[i][2].includes(query)) {
return `:white_check_mark: *${question}*\n${answer}`;
}
}
return `:thinking_face: 「${query}」に該当するFAQが見つかりませんでした。\n総務部にお問い合わせください。`;
}
ミドリ(confused): FAQの内容はスプレッドシートに入れておけばいいんですね。非エンジニアでもFAQの追加・修正ができる。
タツヤ: そこがポイント。ボットのメンテナンスにコードの知識が要らない。総務部がスプレッドシートを更新すれば、即座にボットの回答に反映される。
AI搭載ボット:Gemini APIとの連携
ミドリ: FAQの検索って、完全一致じゃないと引っかからないですよね? もうちょっと賢くできませんか?
タツヤ(nodding): いい質問。Gemini APIと組み合わせれば、自然言語で質問しても適切な回答を返すAIボットが作れる。
function aiAnswer(question, context) {
const apiKey = PropertiesService.getScriptProperties()
.getProperty('GEMINI_API_KEY');
const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`;
// FAQデータを全件取得してコンテキストに
const faqData = getFAQContext();
const payload = {
contents: [{
parts: [{
text: `あなたは社内FAQボットです。以下のFAQデータのみに基づいて回答してください。
FAQに該当する情報がない場合は「回答が見つかりません」と答えてください。
【FAQデータ】
${faqData}
【質問】
${question}`
}]
}]
};
const response = UrlFetchApp.fetch(url, {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify(payload)
});
const result = JSON.parse(response.getContentText());
return result.candidates[0].content.parts[0].text;
}
ミドリ(confused): 「有給何日残ってる?」みたいな自然な聞き方でも答えてくれるんですね。
タツヤ(thinking): ただし注意。Gemini APIはGASの無料枠内で使えるけど、リクエスト数に上限がある。社内利用なら十分な範囲だけど、大量のリクエストが来る場合は料金プランを確認すること。
Microsoft Teams連携
ミドリ: うちの会社、TeamsとSlack両方使ってるんですけど、Teamsにも対応できますか?
タツヤ: できる。TeamsもIncoming Webhookに対応してるから、基本的な仕組みは同じ。
function sendTeamsNotification(message) {
const webhookUrl = PropertiesService.getScriptProperties()
.getProperty('TEAMS_WEBHOOK_URL');
const payload = {
'@type': 'MessageCard',
'@context': 'http://schema.org/extensions',
'summary': '通知',
'themeColor': '0076D7',
'title': '社内ボットからの通知',
'sections': [{
'activityTitle': message.title,
'facts': message.facts,
'markdown': true
}]
};
UrlFetchApp.fetch(webhookUrl, {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify(payload)
});
}
ミドリ: SlackとTeamsの両方に同じ通知を送ることもできますか?
タツヤ: もちろん。1つのGASスクリプトから両方のWebhookに送るだけ。通知先の管理をスプレッドシートにしておけば、チームごとにSlackとTeamsを使い分けることもできる。
実践ユースケース5選
ミドリ: 他にどんなボットが作れますか? イメージが湧く例を教えてください。
タツヤ: 実際に需要が多い5つのユースケースを紹介しよう。
1. 勤怠管理ボット
タツヤ: /kintai in で出勤打刻、/kintai out で退勤打刻。データはスプレッドシートに記録される。月末に自動集計して勤怠サマリーをSlackに投稿。
ミドリ: 勤怠システムにログインする手間がなくなりますね。
2. 承認ワークフローボット
タツヤ: 経費申請や休暇申請をSlackから実行。上司にメンション付きで承認依頼が飛んで、ボタンで承認・却下。結果はスプレッドシートに記録。
3. 日報ボット
タツヤ: 毎日17時にSlackで「今日やったこと」「明日やること」「困っていること」を聞いてくる。回答はスプレッドシートに自動記録されて、週次サマリーが上司に送られる。
4. 会議室予約ボット
タツヤ: /room A会議室 14:00-15:00 で会議室を予約。Google Calendarに自動登録。空き状況の確認も /room check A会議室 でできる。
5. インシデント通知ボット
タツヤ: スプレッドシートにインシデント情報を記録すると、重要度に応じてSlackチャンネルに通知。対応状況の更新もSlackから可能。
| ボットの種類 | 開発時間 | 削減効果(月間) |
|---|---|---|
| FAQボット | 2時間 | 問い合わせ対応 10時間削減 |
| 勤怠管理ボット | 3時間 | 打刻作業 5時間削減 |
| 承認ワークフロー | 4時間 | 承認フロー 8時間削減 |
| 日報ボット | 2時間 | 日報管理 6時間削減 |
| 会議室予約ボット | 3時間 | 予約調整 4時間削減 |
ミドリ(smiling): どれも開発時間が2〜4時間で、月数時間の削減効果がある。投資対効果がすごい。
セキュリティの考慮事項
ミドリ: セキュリティ面で気をつけることはありますか?
タツヤ: 3つのポイントを押さえておこう。
1. Webhook URLの管理
タツヤ: Webhook URLはスクリプトプロパティに保存して、コードにハードコードしない。URLが漏れたら誰でもSlackに投稿できてしまう。
2. リクエストの検証
タツヤ: Slash Commandの場合、リクエストが本当にSlackから来たものか検証する。Slackの署名検証(Signing Secret)を使う。
function verifySlackRequest(e) {
const signingSecret = PropertiesService.getScriptProperties()
.getProperty('SLACK_SIGNING_SECRET');
const timestamp = e.parameter['timestamp'];
const signature = e.parameter['signature'];
// タイムスタンプが5分以内か確認(リプレイ攻撃防止)
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - parseInt(timestamp)) > 300) {
return false;
}
// 署名の検証(簡易版)
return true; // 実際にはHMAC-SHA256で検証
}
3. 権限の最小化
タツヤ: GASのスクリプトに付与する権限は最小限に。不要なスコープ(ドライブ全体へのアクセスなど)は外しておくこと。
ミドリ: セキュリティ対策もGASの中で完結できるのはいいですね。
まとめ:まず1つ作ってみよう
ミドリ: 今日の話をまとめるとどうなりますか?
タツヤ: GASを使えば、SlackやTeamsのボットが無料で、1〜4時間で作れる。外部サービスを契約する必要もないし、社内データが外に出ることもない。
ミドリ: コストゼロ、セキュリティ安心、開発も速い。三拍子揃ってますね。
タツヤ: 最初に作るなら、この優先順位がおすすめ。
- Incoming Webhookで通知ボット — 30分で動く。成功体験を積む
- Slash CommandでFAQボット — 2時間で動く。チームに「便利!」と思ってもらう
- Gemini連携でAIボット — 半日で動く。「GASでここまでできるのか」と驚かれる
ミドリ: まずは通知ボットから始めて、徐々にレベルアップしていけばいいんですね。
タツヤ(nodding): その通り。ボットは「作ること」より「使ってもらうこと」が大事。小さく始めて、チームのフィードバックを受けながら育てていこう。
次のアクション
社内ボットの構築を始めましょう。
- Slackの管理画面でIncoming Webhooksアプリを追加し、Webhook URLを取得する
- スプレッドシートのデータをSlackに投稿する通知ボットを30分で作ってみる
- GASによるチャットボット構築・AI連携のサポートは、Shimanto AI SolutionsのGAS自動化コンサルティングへ