🆙

Notion APIを使って画像をアップロードしてみました

に公開

はじめに

チームで情報をnotionに集約したいという要望があり、google spread sheetやBoxからデータを引っ張ってきてnotionにページを作る対応をしていました。

最近Notion APIで画像がアップロードできるようになったので使ってみました。

参考

こちらの記事を参考にさせていただきました、ありがとうございます。

コード

// png_upload_test.js
const { Client } = require('@notionhq/client')
const { openAsBlob } = require('node:fs')

const notion = new Client({ auth: process.argv[2] });
const fs = require('fs');
const path = require('path');

const CHUNK_SIZE = 10 * 1024 * 1024; // 10MB chunks

// 容量の小さい画像ファイルをアップロード
async function uploadSmallImage(filePath) {
  const fileName = path.basename(filePath);
  const contentType = 'image/png';
  const stats = fs.statSync(filePath);

  const fileUpload = await notion.fileUploads.create({
    mode: 'single_part',
  });

  const send = await notion.fileUploads.send({
    file_upload_id: fileUpload.id,
    file: {
      filename: fileName,
      data: new Blob([await openAsBlob(filePath)], {
        type: 'image/png',
      }),
    },
  });

  return send.id;
}

// 容量の大きい画像ファイルをアップロード
async function uploadBigImage(filePath) {
  const fileName = path.basename(filePath);
  const contentType = 'image/png';
  const stats = fs.statSync(filePath);
  const totalParts = Math.ceil(stats.size / CHUNK_SIZE);

  const fileUpload = await notion.fileUploads.create({
    number_of_parts: totalParts,
    filename: fileName,
    mode: 'multi_part',
  });

  const blob = await openAsBlob(filePath);
  const uploadPromises = [];

  for (let i = 1; i <= totalParts; i++) {
    const start = (i - 1) * CHUNK_SIZE;
    const end = Math.min(start + CHUNK_SIZE, stats.size);
    const chunk = blob.slice(start, end);

    const promise = notion.fileUploads.send({
      file_upload_id: fileUpload.id,
      part_number: String(i),
      file: {
        filename: fileName,
        data: new Blob([chunk], {
          type: 'image/png',
        }),
      },
    });
    uploadPromises.push(promise);
  }

  await Promise.all(uploadPromises);

  // multiで画像を送った場合は、sendのstatusがpendingで返ってくる
  // その場合完了を待ち受けなければブロック追加時にエラーが出るのでcompleteで待つ
  const complete = await notion.fileUploads.complete({
    file_upload_id: fileUpload.id,
  });

  return complete.id;
}

// 指定したページにimageブロックを追加
async function addImage(pageId, filePath) {
  const fileName = path.basename(filePath);
  const contentType = 'image/png';
  const stats = fs.statSync(filePath);

  var sendId = "";
  if (stats.size < (20 * 1024 * 1024)) {
    // 20MB未満
    sendId = await uploadSmallImage(filePath);
  } else {
    // 20MB以上
    sendId = await uploadBigImage(filePath);
  }

  // アップロードしたファイルのsendIdを使ってimageブロックを追加
  await notion.blocks.children.append({
    block_id: pageId,
    children: [{
      type: 'image',
      image: {
        type: 'file_upload',
        file_upload: { id: sendId }
      }
    }]
  });
}

// メイン
async function main() {
  // notionのページのID
  const pageId = 'XXXXXXXXXXXXXXXXXXXX';

  // 容量の小さい画像ファイルへのパス(約5MB)
  const smallPngFilePath = './small.png';

  // 容量の大きい画像ファイルへのパス(約24MB)
  const bigPngFilePath = './big.png';

  await addImage(pageId, smallPngFilePath);
  await addImage(pageId, bigPngFilePath);
}

main();

処理概要

容量の小さい画像のアップロード

  1. notion.fileUploads.createで準備をします
  2. notion.fileUploads.sendで送信します
  3. 戻り値のidを使ってnotion.blocks.children.appendします

容量の大きい画像のアップロード

  1. notion.fileUploads.createで準備をします
    ファイル名といくつに分割して送るのかの指定が必要です
  2. 分割した数だけ、notion.fileUploads.sendで送信します
    part_numberに何個目かを指定しないといけないのですが、String型にしないとエラーがでます
  3. notion.fileUploads.sendの戻り値をawait Promise.all(uploadPromises);で待ち受けないとエラーがでます
  4. 最後にnotion.fileUploads.completeで完了を待たないとブロック追加時にpendingなので処理できないという旨のエラーがでます
  5. 戻り値のidを使ってnotion.blocks.children.appendします

使い方

@notionhq/clientの最新版をインストールします。 (使用したのは4.0.1でした)
上記のコード(png_upload_test.js)と、small.pngとbig.pngを同じフォルダに置きます。

コードのpageIdの部分はアクセス可能なページIdをいれてください。

ターミナルで以下を実行します。

node png_upload_test.js (notionトークン)

まとめ

single_partの方は参考にさせていただいた記事からすんなりできたのですが、multi_partの方もnotion apiを使って画像アップロードしようとしたのでいろいろと苦労しました。

同じような対応をしようとしている方の参考になれば幸いです。

Happy Elements

Discussion