Slack から Notion に登録 : Notion 解説 (62)

(7/25追記: ここで使っている Outgoging webhooks はレガシーな API とのことで使用が推奨されていないようです。現在、Event API のバージョンを作成中です。しばらくお待ちください → SlackToNotion の改訂版はこちらで配布しています。こちらはリアクションするだけでデータベースに登録するようになっていて、より使いやすくなっています。こちらをお使いください。)

hkob.notion.site

1. はじめに

Slack の無料プランの変更が発表され、90日以前の情報が見られなくなります。この情報が流れてから、Slack のフロー情報を Notion などのストック情報に流したいという要望があちこちから上がってきました。要望があったので、Slack から Notion のデータベースに登録する方法を紹介します。

2. Notion データベースの準備

Slack 情報を保存する Notion のデータベースは以下のような形にしました。

  • text: タイトルプロパティとし、Slack のテキストの 1 行目を記録します。
  • channel_name: セレクトプロパティとし、投稿したチャンネル名を記録します。
  • user_name: セレクトプロパティとし、投稿者を記録します。
  • Created time: ページ作成時刻を記録します。

Slack の 2 行目以降の情報は本文として、ページ内テキストに記録します。URL があった場合には、link も設定します。

Notion データベース

インテグレーションキーの作成方法については、ここでは省略します。データベースをフルページで開き、利用するインテグレーションキーを招待することを忘れないでください。

インテグレーションの招待

また、このページの URL を取得し、URL からデータベース ID を記録しておいてください。以下のような URL になった場合、?v= の前の部分である771391e755c245b2a31290f40f187ab9 がデータベース ID になります。

https://www.notion.so/hkob/771391e755c245b2a31290f40f187ab9?v=19b6c58912764cc2900e8af0afe204e3

3. GAS の準備

まずスプレッドシートを作成し、そこから拡張機能から Apps Script を起動します。

Spreadsheet の作成と Apps Script

スクリプト画面が出るので、以下のテキストを貼り付けます。

// MY_NOTION_TOKEN と DATABASE_ID をプロパティに登録する(1回だけ使い、終わったらIDを消す)
function storeTokenAndIds() {
  const scriptProperties = PropertiesService.getScriptProperties()
  scriptProperties.setProperties({
    "MY_NOTION_TOKEN": "ここに「Notion Token」を記述",
    "DATABASE_ID": "ここに「データベースID」を記述",
    "SLACK_TOKEN": "ここに「SLACK_TOKEN」を記述(いたずら防止のため)"
  })
  // 登録できたことを確認
  console.log("myNotionToken = " + myNotionToken())
  console.log("databaseId = " + databaseId())
  console.log("slackToken = " + slackToken())
}

function testNotionAPI() {
  const testData = {
    "parameter": {
      "channel_name": "書類",
      "user_name": "hkob",
      "token": slackToken(),
      "text": "notion:タイトル\nGoogle: <https://google.com>\nTwitter: <https://twitter.com/hkob/status/1551102278672990208>",
    }
  }
  doPost(testData)
}

function doPost(e) {
  if (slackToken() != e.parameter.token) {
    throw new Error("invalid token.");
  }
  const text = e.parameter.text.replace(/^notion:/, "")
  const channel_name = e.parameter.channel_name
  const user_name = e.parameter.user_name
  createPage(createPayload(text, user_name, channel_name))
}

// Notion に payload を send する
function sendNotion(url, payload, method) {
  let options = {
    "method": method,
    "headers": {
      "Content-type": "application/json",
      "Authorization": "Bearer " + myNotionToken(),
      "Notion-Version": "2022-06-28",
    },
    "payload": payload ? JSON.stringify(payload) : null
  };
  // デバッグ時にはコメントを外す
  // Logger.log(options)
  Utilities.sleep(400);
  return JSON.parse(UrlFetchApp.fetch(url, options))
}

function createPage(payload) {
  return sendNotion("https://api.notion.com/v1/pages", payload, "POST")
}

function createChildren(text) {
  const strs = text.split(/[<>]/)
  const rich_texts = strs.filter(function(str) {
    return str != ""
  }).map(function(str) {
    const rich_text = {
      "type": "text",
            "text": {
                "content": str
            }
        }
    if (str.startsWith("https://")) {
      rich_text["text"]["link"] = {"url": str}
    }
    return rich_text
  })
  return [
    {
      "object": "block",
      "type": "paragraph",
      "paragraph": {
        "rich_text": rich_texts
      }     
    }
  ]
}

function createPayload(text, user_name, channel_name) {
  var lines = text.split("\n")
  const title = lines.shift()
  const children = createChildren(lines.join("\n"))
  return {
    "parent": {
      "database_id": databaseId()
    },
    "properties": {
      "text": {
        "title": [
          {
            "text": {
              "content": title
            }
          }
        ]
      },
      "user_name": {
        "type": "select",
        "select": {
          "name": user_name
        }
      },
      "channel_name": {
        "type": "select",
        "select": {
          "name": channel_name
        }
      }
    },
    "children": children
  }
}

// MY_NOTION_TOKEN を取得
function myNotionToken() {
  return PropertiesService.getScriptProperties().getProperty("MY_NOTION_TOKEN")
}

// DATABASE_ID を取得
function databaseId() {
  return PropertiesService.getScriptProperties().getProperty("DATABASE_ID")
}

function slackToken() {
  return PropertiesService.getScriptProperties().getProperty("SLACK_TOKEN")
}

4. scriptProperties の設定

上のスクリプトの storeTokenAndIds の部分を以下のように書き換えます。NOTION_TOKEN は自分のトークンに変更し、データベース ID も上で設定したものにします。SLACK_TOKEN はまだ不明なので、後で設定します。ここまで設定したら、

    "MY_NOTION_TOKEN": "secret_XXXXXXXXXXXXXXXXXXXXXXX",
    "DATABASE_ID": "771391e755c245b2a31290f40f187ab9",
    "SLACK_TOKEN": "ここに「SLACK_TOKEN」を記述(いたずら防止のため)"

ここまで設定したら、storeTokenAndIds を選択し、実行します。下のログに登録したものが表示されたら成功です。 MY_NOTION_TOKEN と DATABASE_ID の行は消してください。

storeTokenAndIds の実行

5. Notion 書き出しの確認

その下に testNotionAPI という関数があります。Slack から送られるパラメータを模擬したテストデータで Notion データベースが登録されたかを確認するものです。

testNotionAPI の実行

設定が正しいと Notion データベースにページが作成されます。これが成功したら Slack の設定を行います。

登録されたテストページ

6. Slack App の作成

設定をしたい Slack を開き、App を選択します。

Slack App の設定

outgoing webhooks を検索し追加とします。

Outgoing Webhooks

ブラウザが開くので、「Slack に追加」をクリックします。

Slack に追加

追加するとインテグレーションの設定画面に移行します。さまざまな設定をする前に、そこに書かれている「トークン」をコピーします。

Slack トークン

7. GAS の設定とデプロイ

ここで GAS のスクリプトに戻り、SLACK_TOKEN の部分に貼り付け、再度 storeTokenAndIds を実行してください。先ほど設定したものと合わせて、3つの情報が表示されていれば設定は完了です。

    "SLACK_TOKEN": "YYYYYYYYYY"

これで GAS 側の記述は終了しました。これで Web アプリとしてデプロイします。右上のデプロイをクリックし、新しいデプロイを実行します。

デプロイ

新しい説明文を記述し、アクセスできるユーザを「全員」に設定します。URL を知っている人は誰でもアクセスできてしまうので、スクリプト内では Slack の Token でフィルタをしています。最後にデプロイをすると、

デプロイの設定

デプロイすると以下のような画面になり、Web アプリの URL が取得できます。これをコピーリンクで取得しておきます。

Web アプリの URL

8. Slack API の設定

チャンネルは特定のチャンネルのみを設定することもできます。引き金となる言葉としては、「notion:」としました。この文言を変える時には、スクリプト内のテキストも修正してください。

Slack API の設定

設定が終わったら、「設定を保存する」としてください。

9. テスト

ここまで終わったら、Slack にテストでメッセージを書いてみます。

Slack のメッセージ

実行すると Notion 側にページが作成されます。

作成されたページ (URL のみ展開)

10. 終わりに

とりあえず URL のみ本文に展開するようにしてみました。私の環境だと twitter リンクがあると Outgoing webhooks がなぜか発火しなかったので、embed ブロックを作成するような処理は実施しませんでした。その辺りは Notion 側に入ってから処理するのでもいいかなということで、最低限のリンク処理のみとしました。参考になれば幸いです。


www.notion.so