📚

NotionAPIを使って参考文献を抽出する

2022/12/18に公開約5,000字

kabewallです。
普段は、技術系の会社で信号処理アルゴリズムを書きつつ、モバイルアプリを作りつつ、Webアプリを作りつつ、AWSでインフラも設計しつつ、ESP32で組み込み処理もやりつつ、現場で音響測定もやりつつ、会社のNotionのまとめ役をやりつつの何でも屋さんです。
器用貧乏です。助けてください。

Notion x Notion API で大体なんでもできる

Notionとは、カスタマイズ性の高いメモアプリでデータベースのように扱えます。
https://www.notion.so/ja-jp

かれこれ2年半ほど使っていますが
プライベートから会社の業務まで使えて
幅広い要求を吸収してくれる懐の深いメモアプリで気に入っています。

NotionはAPIが公開されており、cURLとJavaScriptSDK(TypeScriptにも対応)が用意されています。
APIまで無料で使えます。神。

メモアプリとしての扱いやすさとAPIの使いやすさから
Notionをベースとして色々なことができます。

NotionをヘッドレスCMSとしたブログ作成

例えば、NotionをCMSとしてブログを作成するというのは簡単にできるし、多くの人がやっています。
このAdvent Calendarの3日目を担当されていたチャベスさんもNotionブログ作成について
記事を書かれていました。
https://chabelog.com/blog/notion-blog

蔵書管理

Notionはメモ機能付きのデータベースであるという考え方もできるので、
本の管理なんかもやっている人が多いです。
筆者が勤めている会社では、会社で所持している書籍をNotionで管理しています。
M5AtomやGoogleAppScriptなんかを組み合わせて、Notion図書館を構築しています。

クソ図失礼します
詳細は時間があったら書きます。

他にも、本AdventCalendar 12日目のYuta Kobarashiさんが活用事例をまとめてくださっています。
https://zenn.dev/yutakobayashi/articles/notion-api-advent-calendar-22

本題

現在、弟が運営しているWebマガジンのWebページ開発を進めています。

弟が運営しているWebページはこちら

「あの日の交差点」というWebマガジンおよびPodcast番組を運営しています。
現在はNotionのページを直に公開しています。
https://thosedaysjunction.notion.site/thosedaysjunction/fdd16d3538bd45acb1233fccd4ebd344
今後は、Notionを記事管理CMSとしたWebページに移行していく予定です。

Podcastはこちらから。
https://open.spotify.com/show/3x0uurSC31NONtpkyQRgvF?si=c8704027124e4a17

このWebマガジンは、記事ページと参考文献ページで構成されており、
記事ページから@で参考文献にメンションを付けることで紐づけています。

記事ページから参考文献ページをメンションします


実際にメンションするとNotion上ではこんな感じの表示になる。

この時、記事の最後に参考文献リストをまとめて表示したかったのですが、
Notion本体にはそのような機能はなさそうだったので、
Next.js + Typescriptで実装してみることにしました。

技術スタック

Next.js == 12.3.1
TypeScript == 4.8.3
@notionhq/client == 2.2.0

NotionAPIにおけるブロックの型

https://developers.notion.com/reference/block
Notionのブロックにはいろいろな種類がありますが、
今回はParagraph Blocks内のRich text objectのtypeがmentionのものを抜き出したいです。

NotionAPIで準備されているTypeScript用の型の定義を追っていくと、
それぞれ以下のように定義されていることがわかります。

https://github.com/makenotion/notion-sdk-js/blob/97c98419e7106a4865cdbb6230ceeebf40ae39e3/src/api-endpoints.ts#L5532-L5565

https://github.com/makenotion/notion-sdk-js/blob/97c98419e7106a4865cdbb6230ceeebf40ae39e3/src/api-endpoints.ts#L4768-L4784

https://github.com/makenotion/notion-sdk-js/blob/97c98419e7106a4865cdbb6230ceeebf40ae39e3/src/api-endpoints.ts#L765-L768

https://github.com/makenotion/notion-sdk-js/blob/97c98419e7106a4865cdbb6230ceeebf40ae39e3/src/api-endpoints.ts#L743-L755

すなわち、以下の手順でメンションページの抜き出しができそうです。

  1. BlockObjectResponseのリストの中から、
    ParabraphBlockの型を持つブロックだけを抜き出す。
  2. ParagraphBlockの中からArray<RichTextItemResponse>を取り出す。
  3. RichTextItemResponseの中から
    MentionRichTextItemResponseの型を持つブロックだけを抜き出す。
  4. MentionRichTextItemResponseのtypeがpageかどうか確認して、
    そうだったら、idを抜き出す。

箇条書きすると結構途方もなさそうですが
TypeScriptの型ガード機能を使えば、サクッと実装できます。

型ガードを使って所望のブロックをフィルタする

型ガードについては以下のページがわかりやすかったです。
https://zenn.dev/oreo2990/articles/5f75eaa285f2f9

上のページで言うユーザ定義型ガードをうまく使って、メンション箇所を抜き出していきます。

// ユーザ定義型ガードを作成
// BlockObjectResponseがParagraphBlockObjectResponseかどうかを判定する型ガード
const isParagraphBlock = (block: BlockObjectResponse): block is ParagraphBlockObjectResponse => {
  // typeの名前でparagraphかどうか判定
  return block?.type === "paragraph";
};
// RichTextItemResponseがMentionRichTextItemResponseかどうかを判定する型ガード
export const isMentionRichText = (text: RichTextItemResponse): text is MentionRichTextItemResponse => {
  // typeの名前でmentionかどうかを型ガード
  return text?.type === "mention";
};
// MentionRichTextItemResponseがpageのmentionかどうかを判定する型ガード
const isPageMentionRichText = (
  mention: MentionRichTextItemResponse
): mention is {
  type: "mention";
  mention: {
    type: "page";
    page: {
      id: IdRequest;
    };
  };
  annotations: AnnotationResponse;
  plain_text: string;
  href: string | null;
} => {
  return mention.mention.type === "page";
};

const blocks: BlockObjectResponse[] = [........]; // <- ここにブロックオブジェクトが入ってるとする 
const mentionedPageId: string[] = blocks
  .filter(isParagraphBlock) // 1. まずパラグラフだけ抽出
  .map((block) => { // 2. パラグラフ内のリッチテキストを抽出
    return block.paragraph.rich_text;
  })
 .flat() // 入れ子になった配列を1次元配列に戻す。
 .filter(isMentionRichText) // 3. メンション部分を抽出
 .filter(isPageMentionRichText)  // 4. ページのメンションだけを抽出
 .map((mention) => {
  return mention.mention.page.id; // メンションしたページのIDをリスト化
});

これで、記事中でメンションしたページのpageIdのリストが取得できました。
これをもとに、記事の最後にページのタイトルをリスト表示すれば完成です。

完成形

Notionでいくつかメンションすると、Webマガジン側では自動的に最後に参考文献リストとして表示されます。


Notionにはこんな感じで書く


自分で作ったWebページには参考文献リストが自動挿入されている

Discussion

ログインするとコメントできます