Gemcook Tech Blog
📖

Slack投稿をGeminiやNotebookLMに渡して好きに活用したい

に公開

はじめに

最近は日々、AI の便利な活用術が SNS や記事で紹介されていますね。私も何か面白い活用術を見つけられないかと考えていたところ、ふとひらめきました。

Slack の投稿を、そのまま Gemini や NotebookLM に渡せたら便利そうだなと。
実際にやってみた結果がこちらです。


Slack のスレッドを画像付きで読み込み要約する Gemini

なんと画像内の文字まで拾って、いい感じにサマリを作ってくれました。

「これ、もしかして…… かなり便利なのでは?」

ネタバラシをすると、やっていることはとても単純です。
GAS で Slack の投稿を Google ドキュメントに集めて、そのドキュメントを AI の参照ファイルとして渡しているだけ。

ということで今回は、Slack の投稿を自動で Google ドキュメントに集めて、それを Gemini や NotebookLM で好きに使えるようにする方法を紹介します。

手順の紹介

Step 1: Slack API の設定(トークン発行とイベント購読)

  1. Slack API 設定画面でアプリ作成
    • Slack API Apps にサインインして「Create New App」をクリック
    • 「From scratch」を選び、アプリ名と対象の Slack ワークスペースを指定

  1. 権限の設定
    • サイドメニューより「OAuth & Permissions」を選択
    • ScopesBot Token Scopes に以下を追加
      • channels:history(公開チャンネルへのアクセスに必要)
      • files:read(画像へのアクセスに必要)
      • groups:history(非公開チャンネルへのアクセスに必要)
      • users:read(投稿者の表示名を取得するのに必要)
      • reactions:read(スタンプを検知したい場合は必要)

  1. トークンの取得
    • 画面上部の「Install to {Workspace名}」をクリックし、Slack へのアクセスを許可
    • 表示された Bot User OAuth Token xoxb- をコピーして控えておく。

Step 2: GAS プロジェクトの準備とデプロイ

  1. Slack投稿の保存先を作成

    • Googleドキュメント で新規にドキュメントを作成する
    • URLからドキュメントIDをコピー。
      • https://docs.google.com/document/d/{この部分}/edit
  2. GAS プロジェクトを作成する

    • Google ドキュメントの上部メニューから「拡張機能」>「Apps Script」と選択
    • スクリプト編集画面になるので、下記スクリプトを貼り付ける
    • このスクリプトは、Slack で投稿に 📝(:memo:)スタンプが押されると、その投稿のスレッド全体(画像含む)を Google ドキュメントに書き込む仕組みです
      スクリプト(Gemini 3 Pro 生成)
      const SLACK_TOKEN = 'xoxb-{STEP1-3で取得したトークン}'; 
      const DOC_ID = '{STEP2-1で取得したドキュメントID}';
      
      function doPost(e) {
        const params = JSON.parse(e.postData.contents);
        if (params.type === 'url_verification') return ContentService.createTextOutput(params.challenge);
      
        const event = params.event;
      
        // memoスタンプを検知
        if (event.type === 'reaction_added' && event.reaction === 'memo') {
          const channel = event.item.channel;
          const ts = event.item.ts;
      
          // 二重実行防止(PropertiesServiceを利用)
          const props = PropertiesService.getScriptProperties();
          if (props.getProperty(ts)) return ContentService.createTextOutput("ok");
      
          const threadMessages = getThreadMessages(channel, ts);
          if (threadMessages) {
            writeThreadToDoc(threadMessages);
            props.setProperty(ts, "done"); // 完了を記録
          }
        }
        return ContentService.createTextOutput("ok");
      }
      
      function getThreadMessages(channel, ts) {
        const url = `https://slack.com/api/conversations.replies?channel=${channel}&ts=${ts}`;
        const res = UrlFetchApp.fetch(url, { "headers": { "Authorization": "Bearer " + SLACK_TOKEN } });
        const json = JSON.parse(res.getContentText());
        return json.ok ? json.messages : null;
      }
      
      function getUserName(userId) {
        try {
          const url = `https://slack.com/api/users.info?user=${userId}`;
          const res = UrlFetchApp.fetch(url, { "headers": { "Authorization": "Bearer " + SLACK_TOKEN } });
          const json = JSON.parse(res.getContentText());
          return json.ok ? (json.user.profile.display_name || json.user.real_name) : userId;
        } catch (e) { return userId; }
      }
      
      function writeThreadToDoc(messages) {
        const doc = DocumentApp.openById(DOC_ID);
        const body = doc.getBody();
        const firstMsgDate = Utilities.formatDate(new Date(messages[0].ts * 1000), "JST", "yyyy/MM/dd HH:mm");
      
        body.appendParagraph(`\n==================================`);
        body.appendParagraph(`📝 スレッド抽出記録 (${firstMsgDate})`).setHeading(DocumentApp.ParagraphHeading.HEADING3);
      
        messages.forEach((msg, index) => {
          const name = getUserName(msg.user);
          const time = Utilities.formatDate(new Date(msg.ts * 1000), "JST", "HH:mm");
      
          if (index === 0) {
            body.appendParagraph(`【親】${name} (${time})`).setBold(true);
            body.appendParagraph(msg.text || "(テキストなし)");
          } else {
            body.appendParagraph(` ┗ ${name} (${time}): ${msg.text || ""}`).setIndentStart(20);
          }
      
          if (msg.files) {
            msg.files.forEach(file => {
              if (file.mimetype && file.mimetype.includes('image')) {
                try {
                  const imgBlob = UrlFetchApp.fetch(file.url_private, {
                    "headers": { "Authorization": "Bearer " + SLACK_TOKEN }
                  }).getBlob();
                  const inlineImg = body.appendImage(imgBlob);
                  const width = 300;
                  const height = inlineImg.getHeight() * (width / inlineImg.getWidth());
                  inlineImg.setWidth(width).setHeight(height);
                  if (index > 0) inlineImg.getParent().asParagraph().setIndentStart(40);
                } catch (e) {
                  body.appendParagraph(`  (画像の取得に失敗しました)` ).setItalic(true);
                }
              }
            });
          }
        });
      }
      
    • SLACK_TOKENDOC_ID を書き換える

  1. GAS プロジェクトをデプロイする
    • 上部の「デプロイ」から「新しいデプロイ」→ 「ウェブアプリ」を選択
    • アクセスできるユーザーを 「全員」 に設定してデプロイを実行
      ※ ここで「全員」を選んでいないと Slack がアプリにアクセスできない
    • 発行された ウェブアプリURL をコピーして控えておく

Step 3: アプリと GAS プロジェクトとの紐付け

  1. Slack API管理画面 に戻る

  2. 監視するbotイベントの有効化

    • サイドメニューから「Event Subscriptions」を選択
    • Enable Eventsを On にする
    • Subscribe to bot events に以下3つを追加
      • message.channels : 公開チャンネルのイベントを有効化
      • message.groups : 非公開チャンネルのイベントを有効化
      • reaction_added : スタンプ(リアクション)の検知に必要
  3. GAS プロジェクトとの紐付け

    • Request URL 欄に、Step 2でコピーしたウェブアプリURLを貼り付ける
    • Verified と表示されたら、下部の Save Changes をクリック
      (筆者はここ、Step 2 の 3 で「全員」を選択できていなくてエラーになりました)

  1. Slack チャンネルへアプリを招待
    • 反映させたい Slack チャンネル(公開・非公開問わず)へ移動
    • チャット欄に/invite を入力、「このチャンネルにアプリを追加する」を選択
    • 作成したアプリを追加する


Slack 投稿反映テスト

無事、成功しました!
📝(:memo)を押したスレッドのみ、画像も含めてドキュメントに保存されています。

Step 4: いよいよ Gemini に接続

  1. Google ドキュメントを読み込むGemを作成
    • ブラウザ版 Gemini のサイドメニューから「Gem」を選択
    • 「+ Gem を作成」ボタンを押下
    • 任意の名前説明カスタム指示を入力する
    • 知識に Slack 投稿を反映させる Google ドキュメントを追加する

  1. Gem 実行
    • Slack 投稿を自動反映させた Google ドキュメントの内容を読み取ってくれました

補足: NotebookLM でも使える

同じ Google ドキュメントを NotebookLM のソースとして追加すれば、そちらでも活用できます。

  1. NotebookLM を開き、「新しいノートブック」を作成
  2. ソースとして「Google ドキュメント」を選択し、Slack 投稿を反映させているドキュメントを追加
  3. あとは NotebookLM 上で質問や要約を行うだけ

最後に

思ったよりも簡単にできました。
GAS スクリプトは生成 AI にかなり精度高く書いてもらえるので、紐付けの手順さえ押さえれば自由度はかなり高いです。(ちなみに今回の手順自体も複数の生成 AI に出してもらったのですが、簡単な指示だとちょこちょこ漏れがありました。複数画面にまたがる手順系はまだ人間の目が必要ですね。)

Slack × Google ドキュメント活用のアイデアとしては、こんなことも考えました。

  • 会話の流れで出たアイデアをスタンプ一つで手軽にドキュメント保存、後から AI で整理
  • 社内交流用のチャンネルを週次でまとめて、簡単な活動報告を自動生成
  • 過去の投稿履歴を丸ごと渡して、メンバーを AI にざっくりパーソナライズしてもらう(実験)

他にも色々できそうなので、皆さんのアイデアもぜひ聞いてみたいです。

なお、大量の投稿を一気に読み込みたい場合は、Slack API のレート制限や GAS の実行時間制限(6分)、Google ドキュメントのサイズ上限など、いくつか注意すべきポイントがあります。本格的に大量読み込みを行う際は別途調査が必要になりそうです。

とはいえ、スレッド単位や少人数チャンネルの週間まとめくらいなら気軽に使えそうですので、ぜひ試してみてください。

Gemcook Tech Blog
Gemcook Tech Blog

Discussion