Notion で作成されたページを日時でサマリしてSlack に投稿してみた

今回は Notion に投稿された複数のページのサマリを日時で Slack に投稿するアプリを作ってみました
2023.03.26

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちわ。西田@CX事業本部です

今回は Notion に投稿された複数のページのサマリを日時で Slack に投稿するアプリを作ってみました

全体の構成

CloudWatch Events で決まった時間に Lambda を起動し、その Lambda の中で Notion からデータを取得し、Slack にサマリを投稿します

背景

Notion のページの更新を Slack に通知する連携はすでにありますが、更新のたびに通知がきてしまうと、通知が多くなり過ぎてしまうケースもあります

そのため、前日に任意のデータベースに作成された Page のタイトルとリンクをまとめて Slack に投稿するアプリを作っていきたいと思います

前提

この記事は以下の前提で書かれています

  • Node.js がインストールされてる
  • AWSのアカウントを持っている
  • aws cli がインストールされてる

Slack アプリケーション を作成する

Slack に投稿するのに Slack API を使用します。Slack API を使用するには Slack アプリを登録する必要があります

以下のページを開き「Create new App」をクリックします

Slack API: Applications | Slack

「From Scrach」を選択し

任意の名前と、インストール先のワークスペースを選択し、Slack アプリを作成します

作成されたら左メニューの 「OAuth & Permissions」を選ん権限を付与する画面を開きます

Scopes > Bot Token Scopes で chat:write 権限を付与します

最後にワークスペースにアプリを登録して、その後に表示されるトークンを保存して完成です

Notion Integration を作成する

Notion データベースからデータのデータを読み込むには Notion Integration を作成する必要があります

連携する Notion Workspace にログインした状態で以下のページにアクセスします

My integrations | Notion Developers

「New Integration」を押します

今回必要なのは Notion データベースへの投稿なので、「Reacd Content」のみ権限を設定します

「Secrets」 の 「Show」 を押して内容を表示させ後ほど使うのでコピーしておきます

Notion Integration から Notion データベースへの接続を許可

通常の Notion を開き、Slackにサマリを投稿する対象の Notionデータベースのメニューから「Add Connections」を選び今回追加した Integration を選択し、Integration からデータベースにアクセスできるようにします

参考: Create an integration

Notion データベースのIDを取得する

データベースのページのIDを取得します。少しわかりにくいですが、データベースのページにアクセスした時のURLのパス部分がIDになります

[https://notion.so/${ここがID}?v=xxx](https://notion.so/${ここがID}?v=xxx)

CDKで Lambda をデプロイする環境を構築

cdk コマンドでプロジェクトを作成します

cdk init --language typescript

作成されたプロジェクトのディレクトリで必要な npm パッケージを追加します

npm install @slack/web-api @notionhq/client @types/aws-lambda

Lambd で使用するパラメーターをSSMに登録します

Lambda で使用する以下のパラメーターをSSMに登録します。パラメーター名は任意です

  • notionReport-notionAuth : Notion Integration のシークレット
  • notionReport-notionDbId : Notion のデータベースID
  • notionReport-slackBotToken : Slack のトークン
aws ssm put-parameter --name notionReport-notionAuth --value "xxxxxxxxxx" --type "String"
aws ssm put-parameter --name notionReport-notionDbId --value "xxxxxxxxxx" --type "String"
aws ssm put-parameter --name notionReport-slackBotToken --value "xxxxxxxxxx" --type "String"

定期的に投稿する Lambda のハンドラーを作成

作成前に Notion SDK の型定義がまだないところがあるので、CDKが生成した .tsconfignoImplicitAnyfalse にしておきます

{
  "compilerOptions": {
		// ... 省略
    "noImplicitAny": false,
		// ... 省略
  },
  "exclude": [
    "node_modules",
    "cdk.out"
  ]
}

src/handler.ts

import { WebClient } from "@slack/web-api";
import { Client } from "@notionhq/client";
import { ScheduledEvent, Context } from "aws-lambda";
import { PageObjectResponse } from "@notionhq/client/build/src/api-endpoints";

const notionClient = new Client({
  auth: process.env["NOTION_AUTH"],
});
const slackClient = new WebClient(process.env["SLACK_BOT_TOKEN"]);
const notionDbId = process.env["NOTION_DB_ID"]!;

const extractTitleText = (page: PageObjectResponse) => {
  return page["properties"]["Name"]["title"].reduce(
    (title, titleObject) => (title += titleObject["plain_text"]),
    ""
  );
};

const genYesterdayString = () => {
  const yesterdayJST = new Date(
    Date.now() +
      new Date().getTimezoneOffset() * 60 * 1000 +
      9 * 3600 * 1000 -
      24 * 3600 * 1000
  );
  return yesterdayJST.toISOString().split("T")[0];
};

export const handler = async (event: ScheduledEvent, context: Context) => {
  const yesterday = genYesterdayString();

  const response = await notionClient.databases.query({
    database_id: notionDbId,
    filter: {
      and: [
        {
          property: "作成日",
          date: { equals: yesterday },
        },
      ],
    },
  });

  const pages = response.results as PageObjectResponse[];

  const message = pages
    .filter((page) => {
      return page["properties"]["Name"]["title"].length > 0;
    })
    .map((page) => `<${page["url"]}|${extractTitleText(page)}>`)
    .join("\n");

  if (message) {
    await slackClient.chat.postMessage({
      channel: "#test",
      text: message,
    });
  }

  return {};
};

Lambda の中身を何点かピックアップして説明します

以下のコードは Notion データベースに、作成日が先日のページをクエリしています。クエリするにはデータベースのIDが必要です

Notion が対応してる全てのクエリは以下のリンクを参考にしてください

Filter database entries

const response = await notionClient.databases.query({
  database_id: notionDbId,
  filter: {
    and: [
      {
        property: "created_time",
        date: { equals: yesterday },
      },
    ],
  },
});

以下のコードは Notion の page オブジェクトからタイトルを取得するコードです。タイトル内にリンクなど装飾が使われて場合に ["Name"]["title"] の中が複数に分割されてしまうので、それを一つの文字列になるよう連結しています

const extractTitleText = (page: PageObjectResponse) => {
  return page["properties"]["Name"]["title"].reduce(
    (title, titleObject) => (title += titleObject["plain_text"]),
    ""
  );
};

以下のコードはJST(日本時間)で昨日の日付をISO8601形式で取得しています。ライブラリを使えばもっと簡単にできますが、今回はライブラリを使用せずに対応しました

現在日時から時差を引きその後で日本とUTC(世界標準時)の時差を足して、日付部分のみ文字列として抜き出しています

(ISO8601形式: 2023-03-26T17:07:17.77から、記号Tで区切り前半の日付部分 2023-03-26を抜き出し)

const genYesterdayString = () => {
  const yesterdayJST = new Date(
    Date.now() +
      new Date().getTimezoneOffset() * 60 * 1000 +
      9 * 3600 * 1000 -
      24 * 3600 * 1000
  );
  return yesterdayJST.toISOString().split("T")[0];
};

CDKを作成する

lib/xxx.ts

import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";

export class NotionreportStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // Notion と Slack の認証情報等をSSMパラメーターから取得
    const notionAuth = cdk.aws_ssm.StringParameter.valueForStringParameter(
      this,
      "notionReport-notionAuth"
    );
    const notionDbId = cdk.aws_ssm.StringParameter.valueForStringParameter(
      this,
      "notionReport-notionDbId"
    );
    const slackBotToken = cdk.aws_ssm.StringParameter.valueForStringParameter(
      this,
      "notionReport-slackBotToken"
    );

    const lambda = new cdk.aws_lambda_nodejs.NodejsFunction(this, "Fn", {
      runtime: cdk.aws_lambda.Runtime.NODEJS_18_X,
      entry: "src/handler.ts",
      environment: {
        NOTION_AUTH: notionAuth,
        NOTION_DB_ID: notionDbId,
        SLACK_BOT_TOKEN: slackBotToken,
      },
      bundling: {
        sourceMap: true,
      },
      timeout: cdk.Duration.seconds(29),
    });

    // CloudWatch Events で Lambda を定期実行する
    new cdk.aws_events.Rule(this, "Schedule", {
      schedule: cdk.aws_events.Schedule.cron({
        minute: "55",
        hour: "0", // UTCなので日本時間だと+9時間される
      }),
      targets: [new cdk.aws_events_targets.LambdaFunction(lambda)],
    });
  }
}

デプロイ

デプロイして完成です

cdk deploy

まとめ

今回はNotionの日時レポートを Slack に投稿する Lambda を作成しました。思ってたより手軽に作れたので、ぜひ皆様もアレンジして作ってみてください

全てのソースを Github にPushしています。こちらもご参考ください

この記事が誰かの参考になれば幸いです