こんにちわ。西田@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 からデータベースにアクセスできるようにします
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が生成した .tsconfig
の noImplicitAny
を false
にしておきます
{
"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 が対応してる全てのクエリは以下のリンクを参考にしてください
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しています。こちらもご参考ください
この記事が誰かの参考になれば幸いです