Slackボット、外部サービスに頼らなくても作れる

ミドリ: タツヤさん、SlackにFAQボットを入れたいって上司に言われたんですけど、外部のチャットボットサービスって月額5,000〜30,000円くらいしますよね。しかも社内データを外部に送るのはセキュリティ的に不安で……。

タツヤ: GASで作れば完全無料、しかもGoogle Workspaceの中で完結するからデータが外部に出ない。

ミドリ: え、GASでSlackのボットが作れるんですか?

タツヤ: 作れる。しかも意外と簡単。SlackもTeamsも、Webhook経由でGASと連携できる。基本的な仕組みを理解すれば1時間でプロトタイプが動くよ。

ミドリ: 1時間! さすがに盛ってません?

タツヤ: 盛ってない。順番に見ていこう。

GAS × Slackの基本アーキテクチャ

ミドリ: まず全体像を教えてください。GASとSlackがどうつながるんですか?

タツヤ: 2つの連携パターンがある。

パターン方向用途難易度
Incoming WebhookGAS → Slack通知・アラート・定期レポート簡単
Slash CommandSlack → GASユーザーからの問い合わせ応答やや簡単

ミドリ: Incoming Webhookが「GASからSlackに送る」、Slash Commandが「SlackからGASに問い合わせる」ですね。

タツヤ(nodding): その通り。まずは簡単なIncoming Webhookから始めよう。

パターン1:Incoming Webhookで通知ボット

ミドリ: 何が通知できるんですか?

タツヤ: 例えばこういうもの。

タツヤ: セットアップは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時間で作れる。外部サービスを契約する必要もないし、社内データが外に出ることもない。

ミドリ: コストゼロ、セキュリティ安心、開発も速い。三拍子揃ってますね。

タツヤ: 最初に作るなら、この優先順位がおすすめ。

  1. Incoming Webhookで通知ボット — 30分で動く。成功体験を積む
  2. Slash CommandでFAQボット — 2時間で動く。チームに「便利!」と思ってもらう
  3. Gemini連携でAIボット — 半日で動く。「GASでここまでできるのか」と驚かれる

ミドリ: まずは通知ボットから始めて、徐々にレベルアップしていけばいいんですね。

タツヤ(nodding): その通り。ボットは「作ること」より「使ってもらうこと」が大事。小さく始めて、チームのフィードバックを受けながら育てていこう。


次のアクション

社内ボットの構築を始めましょう。