Zenn
Closed7

Slack投稿の絵文字リアクションをトリガーにNotionページを自動作成する

おとのおとの

概要

Slackの投稿に特定の絵文字リアクションが付与されたら、その投稿とスレッド内容をNotionのページとして自動保存する仕組みを構築する。

セットアップ

Slack

  1. Slack APIでアプリ作成
  2. Bot Token ScopesとUser Token Scopesに必要な権限を追加
    • channels:history
    • groups:history
    • mpim:history
    • im:history
    • reactions:read
  3. User OAuth Tokenを取得

Notion

  1. Notion Developersでインテグレーション作成
  2. Internal Integration Token取得
  3. データベースIDを取得

https://note.com/amatyrain/n/nb9ebe31dfab7

GAS

スクリプトプロパティに以下を設定

  • SLACK_TOKEN
  • NOTION_TOKEN
  • NOTION_DATABASE_ID
おとのおとの

GASでのスクリプト実装

GASに以下コードを実装

const targetEmoji = "notion"; // 追跡する絵文字に置き換える

// eslint-disable-next-line
function doPost(e) {
  // Slackからのリクエストを解析
  const params = JSON.parse(e.postData.contents);

  // SlackからのURL検証リクエストを処理
  if (params.type === "url_verification") {
    return ContentService.createTextOutput(params.challenge);
  }

  // 絵文字リアクションが追加された場合
  if (params.event && params.event.type === "reaction_added") {
    const reaction = params.event.reaction;

    if (reaction === targetEmoji) {
      // prettier-ignore
      // eslint-disable-next-line
      const slackToken = PropertiesService.getScriptProperties().getProperty("SLACK_TOKEN");

      // Slack APIを使って該当のメッセージとスレッドを取得
      const channelId = params.event.item.channel;
      const messageTs = params.event.item.ts;
      const messageResponse = UrlFetchApp.fetch(
        `https://slack.com/api/conversations.history?channel=${channelId}&latest=${messageTs}&limit=1&inclusive=true`,
        {
          method: "get",
          headers: {
            Authorization: `Bearer ${slackToken}`,
          },
        }
      );
      const messageData = JSON.parse(messageResponse.getContentText())
        .messages[0];

      // スレッドのメッセージを取得
      const threadTs = messageData.thread_ts || messageTs;
      const threadResponse = UrlFetchApp.fetch(
        `https://slack.com/api/conversations.replies?channel=${channelId}&ts=${threadTs}`,
        {
          method: "get",
          headers: {
            Authorization: `Bearer ${slackToken}`,
          },
        }
      );
      const threadMessages = JSON.parse(
        threadResponse.getContentText()
      ).messages;

      // Notionに書き込むデータを準備
      const data = threadMessages.map((msg) => ({
        object: "block",
        type: "paragraph",
        paragraph: {
          rich_text: [
            {
              type: "text",
              text: {
                content: msg.text,
              },
            },
          ],
        },
      }));

      // 絵文字リアクションが追加されたテキストの投稿日付をUTCからJSTに変換
      const postDate = new Date(messageData.ts * 1000 + 9 * 60 * 60 * 1000)
        .toISOString()
        .split("T")[0]; // 日付をYYYY-MM-DD形式に変換

      // データベースIDを取得
      // prettier-ignore
      // eslint-disable-next-line
      const databaseId = PropertiesService.getScriptProperties().getProperty("NOTION_DATABASE_ID");

      // Notionに送信するリクエストボディを構築
      const notionRequestBody = {
        parent: { database_id: databaseId },
        properties: {
          name: {
            title: [
              {
                text: {
                  content: "Slackのメモ", // ページのタイトル
                },
              },
            ],
          },
          date: {
            date: {
              start: postDate, // 日付
              end: null, // 今回は終了日付は不要
            },
          },
          Status: {
            status: {
              name: "未着手", // ステータス
            },
          },
        },
        children: data, // テキスト
      };

      // Notion APIトークンを取得
      // prettier-ignore
      // eslint-disable-next-line
      const notionToken = PropertiesService.getScriptProperties().getProperty("NOTION_TOKEN");

      // Notion APIにリクエストを送信
      UrlFetchApp.fetch("https://api.notion.com/v1/pages", {
        method: "post",
        headers: {
          Authorization: `Bearer ${notionToken}`,
          "Content-Type": "application/json",
          "Notion-Version": "2022-06-28",
        },
        payload: JSON.stringify(notionRequestBody),
      });
    }
  }

  return ContentService.createTextOutput("OK");
}

おとのおとの

Slack側の設定

  • デプロイ後に生成されたURLをSlack API Event Subscriptions に設定
  • eventsに必要な権限を追加する。
    • reactions:read
おとのおとの

この仕様だと、スレッドの最初のメッセージに絵文字リアクションをつけることで、そのメッセージに紐づくスレッド内のメッセージが全てNotionに書き込まれる。

スレッドの最初のメッセージと投稿日付が異なるスレッド途中のメッセージに絵文字リアクションをつけた場合、テキスト内容が正しくNotionに書き込まれない(書き込めるように暇あったら修正したい)

おとのおとの

Notion APIを使う時のメモ

指定したデータベースのプロパティを確認する

※事前に作成したインテグレーションをDBにアクセス許可する必要あり

curl -X GET 'https://api.notion.com/v1/databases/DATABASE_ID' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Notion-Version: 2022-06-28'

指定したデータベースにページを追加する

curl -X POST 'https://api.notion.com/v1/pages' \
-H 'Authorization: Bearer {YOUR_INTEGRATION_TOKEN}' \
-H 'Content-Type: application/json' \
-H 'Notion-Version: 2022-06-28' \
-d '{
  "parent": { "database_id": "YOUR_DATABASE_ID" },
  "properties": {
    "名前": {
      "title": [
        {
          "text": {
            "content": "プロジェクトA計画"
          }
        }
      ]
    },
    "日付": {
      "date": {
        "start": "2024-04-01",
        "end": null
      }
    },
    "Status": {
      "status": {
        "name": "計画中"
      }
    }
  },
  "children": [
    {
      "object": "block",
      "type": "paragraph",
      "paragraph": {
        "rich_text": [
          {
            "type": "text",
            "text": {
              "content": "プロジェクト概要の説明"
            }
          }
        ]
      }
    },
    {
      "object": "block",
      "type": "paragraph",
      "paragraph": {
        "rich_text": [
          {
            "type": "text",
            "text": {
              "content": "主要なマイルストーン"
            }
          }
        ]
      }
    },
    {
      "object": "block",
      "type": "paragraph",
      "paragraph": {
        "rich_text": [
          {
            "type": "text",
            "text": {
              "content": "予算と人員配置の検討"
            }
          }
        ]
      }
    }
  ]
}' 
このスクラップは3ヶ月前にクローズされました
ログインするとコメントできます