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

Slackの投稿を無料でAIで検索・分析できるようにGoogle Driveに自動保存してみた

3
Last updated at Posted at 2026-04-06

はじめに

みなさん、こんにちはー。Shizen ConnectのCEOの松村です。

Slackのメッセージを後から検索したり、AIで要約させたい場面は多いですが、Slack標準の検索機能だけでは限界を感じることがありますよね。

で、思い立って Google Apps Script (GAS) を利用して、Slackの過去ログ全件と、1時間ごとの新規投稿を自動でGoogle Driveにテキスト保存する仕組みを構築しました。

もちろん色々な実現方法があるのですが、結構コストが無視できなくて……。

Google Drive上のファイルをGeminiに読み込ませることで、高精度な検索・要約が可能な環境となり、個人的にはめちゃくちゃ満足です。しかも無料です!

1. Slack Appの準備

まずSlack API (Apps) でアプリを作成し、以下の設定を行います。

ユーザートークンのスコープに以下を追加

  • channels:history (メッセージ読み取り)
  • users:read (表示名取得用)

image.png

「Install App」ボタンをおしてワークスペースにインストールし、発行された User OAuth Token (xoxp-...) を控えておきましょう。

2. 全件取得用スクリプト

次にGASを準備していきましょう。

  1. Google Drive を開き、適当なフォルダ(ログ保存用)を作成します。
  2. 左上の「+新規」>「その他」>「Google Apps Script」を選択します。
  3. プロジェクトが開いたら、左上の「無題のプロジェクト」をクリックして「Slack2Text」などの名前を付けて保存します。
  4. 元からある function myFunction() {...} は消去して、以下のコードを貼り付けていきます。

まずは既存のメッセージをすべてエクスポートするための関数です。

  • SLACK_TOKENは先程のUser OAuth Tokenです
  • CHHANNEL_IDSはデータ収集するチャネルのIDのリストです。Slackアプリ上で、エクスポートしたいチャンネル名を右クリックし、「リンクをコピー」し、末尾の C0XXXXXXXのような文字列をカンマ区切りで書いていきます
  • FOLDER_IDはGoogle Drive上の保存用フォルダのURLの末尾(folders/◯◯◯ の◯の部分)です
const SLACK_TOKEN = 'YOUR_USER_TOKEN';
const CHANNEL_IDS = ['CXXXXXXXXXX', 'CXXXXXXXXXX']; // 対象チャンネルID
const FOLDER_ID = 'YOUR_DRIVE_FOLDER_ID'; // 保存先のGoogle DriveフォルダID

function exportAllPastMessages() {
  const folder = DriveApp.getFolderById(FOLDER_ID);
 
  CHANNEL_IDS.forEach(channelId => {
    let allContent = "";
    let nextCursor = "";
    let pageCount = 1;


    // メッセージがなくなるまでループ
    do {
      let url = `https://slack.com/api/conversations.history?channel=${channelId}&limit=1000`;
      if (nextCursor) {
        url += `&cursor=${nextCursor}`;
      }


      const options = {
        "method": "get",
        "headers": { "Authorization": "Bearer " + SLACK_TOKEN }
      };


      const response = UrlFetchApp.fetch(url, options);
      const data = JSON.parse(response.getContentText());


      if (data.ok) {
        const messages = data.messages; // 履歴取得時は新しい順(降順)で返ってきます
        messages.forEach(msg => {
          if (!msg.subtype) {
            const date = Utilities.formatDate(new Date(msg.ts * 1000), "JST", "yyyy-MM-dd HH:mm");
            const userName = getUserName(msg.user); // 前述の名前変換関数を併用
            allContent = `[${date}] ${userName}: ${msg.text}\n` + allContent; // 古い順に並ぶよう上に追加
          }
        });


        nextCursor = data.response_metadata ? data.response_metadata.next_cursor : "";
        Logger.log(`${channelId}: ${pageCount}ページ目取得完了...`);
        pageCount++;
       
        // API負荷軽減のため少し待機
        Utilities.sleep(1000);
      } else {
        Logger.log("エラー: " + data.error);
        break;
      }
    } while (nextCursor);


    // ファイルに保存
    const fileName = `FULL_HISTORY_${channelId}.txt`;
    folder.createFile(fileName, allContent);
    Logger.log(`${channelId} の全履歴エクスポートが完了しました。`);
  });
}

これを動かすとSlackの投稿が全件テキストファイルで Google Driveに保存されます!

3. 定期取得(差分更新)用スクリプト

次にGASのトリガーで「1時間おき」に実行することを想定したコードです。その日の日付ファイルに最新の書き込みを追記します。

/ ユーザー名のキャッシュ何度もAPIを叩かないように一時保存
let userCache = {};


function exportSlackToTextFiles() {
  const folder = DriveApp.getFolderById(FOLDER_ID);
  const now = new Date();
  const today = Utilities.formatDate(now, "JST", "yyyy-MM-dd");
 
  // 現在から1時間前(3600秒前)のタイムスタンプを計算
  const oneHourAgo = Math.floor((now.getTime() - (60 * 60 * 1000)) / 1000);


  CHANNEL_IDS.forEach(channelId => {
    // oldestパラメータを追加して、過去1時間分のみ取得
    const url = `https://slack.com/api/conversations.history?channel=${channelId}&oldest=${oneHourAgo}&limit=100`;   const options = {
      "method": "get",
      "headers": { "Authorization": "Bearer " + SLACK_TOKEN }
    };
   
    try {
      const response = UrlFetchApp.fetch(url, options);
      const data = JSON.parse(response.getContentText());
     
      if (data.ok) {
        let logContent = "";
        const messages = data.messages.reverse();


        messages.forEach(msg => {
          if (!msg.subtype) {
            const date = Utilities.formatDate(new Date(msg.ts * 1000), "JST", "HH:mm");
            const userName = getUserName(msg.user); // 名前変換関数を呼び出し
            logContent += `[${date}] ${userName}: ${msg.text}\n`;
          }
        });


        // フォルダ内にファイルを作成(既存なら追記、新規なら作成)
        const fileName = `${channelId}_${today}.txt`;
        const files = folder.getFilesByName(fileName);
       
        if (files.hasNext()) {
          const file = files.next();
          const oldContent = file.getBlob().getDataAsString();
          file.setContent(oldContent + logContent);
        } else {
          folder.createFile(fileName, logContent);
        }
       
        Logger.log(`${channelId} のテキスト保存完了`);
      }
    } catch (e) {
      Logger.log(`エラー: ${e.message}`);
    }
  });
}


// ユーザーIDを名前に変換する関数
function getUserName(userId) {
  if (userCache[userId]) return userCache[userId];


  const url = `https://slack.com/api/users.info?user=${userId}`;
  const options = {
    "method": "get",
    "headers": { "Authorization": "Bearer " + SLACK_TOKEN }
  };


  try {
    const response = UrlFetchApp.fetch(url, options);
    const data = JSON.parse(response.getContentText());
    if (data.ok) {
      const name = data.user.real_name || data.user.name;
      userCache[userId] = name; // キャッシュに保存
      return name;
    }
  } catch (e) {
    return userId; // エラー時はIDをそのまま返す
  }
  return userId;
}

GASを保存したら1時間ごとに実行するように設定しましょう。

image.png

4. Geminiで利用

ここまでできたらあとは、ファイルを保管したGoogle Drive上のGeminiや、GeminiのプロンプトでDriveのURLを指定してあげればSlackの情報を活用した検索・文書生成ができます。

私は以下のようなのを試して便利さに感動しました。

  1. 「最近の各部門の状況を報告して」
  2. 「〇〇のインシデントの報告書を作成して」

ちなみにテキストになってるので他のAIでも使えますね。AIたのしすぎますね。では!

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