見出し画像

NotionとGASでデータベースの内容をランダム表示させよう

Notionを今年から使っているのですが、自分用のホーム画面を作っている際に、学習した内容や偉人の名言などをランダムで1~2つくらい表示させて「復習やモチベーションアップをしたいな~」なんて思いました。

ところが
2024/04/15時点ではNotionだけでランダム表示はできないようでした。
いや、できるにはできるようですが、プロパティを大量に設定してランダム値を用意してリレーションして云々と、Notion初心者にはちょっと面倒そうでしたのでGASでやります。

前に投稿した記事(Notionの繰り返しタスクをGASで自動追加する)を見た知人に「makeとかzapierでやればいいじゃん」とのお言葉をいただきましたが、貴重な無料枠をこんなので使いたくないのですよ。ノーコードで作成できるので「素人の組んだ怪しいプログラムなんて使いたくない」って方はmakeやzapierでやってください。

では作成していきます。



準備するもの

  • NotionAPIインテグレーショントークン

  • 追加する単語や名言などを登録しているデータベースとID *以後「単語帳DB」

  • 表示用データベースとID *以後「表示用DB」

  • Googleアカウント

それぞれの確認の仕方は前回の記事(Notionの繰り返しタスクをGASで自動追加する)を参照してください。


ランダム表示の流れ

今回は1件をランダム表示したいと思います。処理のおおまかな流れは以下の通りです。

  1. 単語帳DBからすべてのレコードを取得する

  2. 取得したレコードからランダムで1件取得する

  3. 表示用DBの中身を空にする

  4. 取得した1件を表示用DBへ登録する

単純ですね。


データベースの設定


単語帳DBはプロパティに

  • タイトル「名前」

  • マルチセレクト「タグ」

を持ちます。今回は
名前に単語名
タグにカテゴリ
本文に単語の説明
が入力されている想定です。


表示用DBはプロパティに

  • タイトル「名前」

  • マルチセレクト「タグ」

を持ちます。


それぞれのDBとNotionAPIインテグレーショントークンの接続をしておきましょう。
接続方法は前回の記事(Notionの繰り返しタスクをGASで自動追加する)で確認してください。


GAS(Google Apps Script)の設定

設定は前回の記事(Notionの繰り返しタスクをGASで自動追加する)で確認してください。

コードをコピーして使いたい場合はスクリプトプロパティをそれぞれ以下のようにしてください。

  • プロパティ:NOTION_API_KEY  値:NotionAPIトークン

  • プロパティ:ADD_RANDOM_WORD_DB  値:表示用DBのID

  • プロパティ:COLLECTION_OF_WORD_DB  値:単語帳DBのID


コードと実行

コードはこんな感じです。

// NotionAPIキー
const NOTION_API = PropertiesService.getScriptProperties().getProperty("NOTION_API_KEY");
// NotionDBのID
const ADD_DB = PropertiesService.getScriptProperties().getProperty("ADD_RANDOM_WORD_DB");
const GET_WORD_DB = PropertiesService.getScriptProperties().getProperty("COLLECTION_OF_WORD_DB");
// NotionAPIのバージョン情報
const NOTION_VERSION = "2022-06-28";

/**
 * 単語帳DBからランダムで1件、表示用DBへ追加
 */
function myFunction() {

  // 取得対象データベースからすべてのレコードを取得する
  var records = getNotionDB(GET_WORD_DB, NOTION_API, NOTION_VERSION);
  if (!records || records.length === 0) {
    console.log('No records found.');
    return;
  }
  
  // 取得レコードからランダムで1件取得する
  var randomRecord = records[Math.floor(Math.random() * records.length)];
  
  // 追加対象データベースにレコードが存在する場合はレコードをすべて削除する
  deleteAllPagesInDatabase(ADD_DB, NOTION_API, NOTION_VERSION);

  // 追加対象データベースへ1件追加する
  addRecordToDatabase(ADD_DB, NOTION_API, NOTION_VERSION, randomRecord);
  
}


// レコードのブロックを取得する
function getBlocks(pageId, integrationToken, version) {
  const url = `https://api.notion.com/v1/blocks/${pageId}/children`;
  const headers = {
    "Authorization": `Bearer ${integrationToken}`,
    "Notion-Version": version,
    "Content-Type": "application/json"
  };

  const response = UrlFetchApp.fetch(url, {
    "method": "get",
    "headers": headers
  });

  const responseCode = response.getResponseCode();
  const responseData = JSON.parse(response.getContentText());

  if (responseCode === 200) {
    return responseData.results;
  } else {
    console.error("Failed to retrieve blocks:", responseData);
    return [];
  }
}

// レコードを追加する プロパティ値  名前:title  タグ:multi_select
function addRecordToDatabase(databaseId, integrationToken, version, record) {
  // レコードのブロックを取得する
  var contentBlocks = getBlocks(record.id, NOTION_API, NOTION_VERSION);

  const url = "https://api.notion.com/v1/pages";
  const headers = {
    "Authorization": `Bearer ${integrationToken}`,
    "Notion-Version": version,
    "Content-Type": "application/json"
  };

  const payload = {
    "parent": { "database_id": databaseId },
    "properties": {
      "名前": {
        "title": [
          {
            "text": {
              "content": record.properties["名前"].title[0]?.plain_text
            }
          }
        ]
      },
      "タグ": {
        "multi_select": record.properties["タグ"].multi_select.map(tag => ({ "name": tag.name }))
      }
    },
     "icon": {
      "type": "emoji",
      "emoji": "🅰"
    },
    "children": contentBlocks
  };

  const response = UrlFetchApp.fetch(url, {
    "method": "post",
    "headers": headers,
    "payload": JSON.stringify(payload)
  });

  console.log('Record added:', response.getContentText());
}

// 対象データベースからすべてのレコードを削除する
// APIでは直接削除はできないようなのでarchivedをtrueに設定することでゴミ箱へ移動する
function deleteAllPagesInDatabase(databaseId, integrationToken, version) {
  const headers = {
    "Authorization": `Bearer ${integrationToken}`,
    "Notion-Version": version,
    "Content-Type": "application/json"
  };

  const pagesUrl = `https://api.notion.com/v1/databases/${databaseId}/query`;
  let hasMore = true;
  let startCursor = undefined;

  while (hasMore) {
    const payload = startCursor ? { start_cursor: startCursor } : {};
    
    const pageResponse = UrlFetchApp.fetch(pagesUrl, {
      "method": "post",
      "headers": headers,
      "payload": JSON.stringify(payload),
      "muteHttpExceptions": true
    });

    const responseCode = pageResponse.getResponseCode();
    const pageData = JSON.parse(pageResponse.getContentText());

    if (responseCode !== 200) {
      console.error(`Failed to fetch pages: ${pageData.message}`);
      return;
    }

    hasMore = pageData.has_more;
    startCursor = pageData.next_cursor;

    // 取得したページのIDを使用して各ページをゴミ箱へ
    pageData.results.forEach(function(page) {
      const archiveUrl = `https://api.notion.com/v1/pages/${page.id}`;
      const archivePayload = {
        "archived": true
      };

      const archiveResponse = UrlFetchApp.fetch(archiveUrl, {
        "method": "patch",
        "headers": headers,
        "payload": JSON.stringify(archivePayload),
        "muteHttpExceptions": true
      });

      if (archiveResponse.getResponseCode() !== 200) {
        const archiveData = JSON.parse(archiveResponse.getContentText());
        console.error(`Failed to archive page ${page.id}: ${archiveData.message}`);
      } else {
        console.log(`Archived page ${page.id} successfully`);
      }
    });
  }
}



// databaseIDのDBからデータを取得する
function getNotionDB( databaseID, integrationToken, version ){

  var url = "https://api.notion.com/v1/databases/" + databaseID + "/query";
  var headers = {
    "Authorization": "Bearer " + integrationToken,
    "Notion-Version": version
  };
  var options = {
    "method" : "post",
    "headers": headers
  };

  var response = UrlFetchApp.fetch(url, options);
  var json = JSON.parse(response.getContentText());
  
  return json.results;
}


実行すると単語帳DBから1件が表示用DBに登録されるはずです。

最初の1回は認証云々でますので許可をしてください。それについても前回の記事(Notionの繰り返しタスクをGASで自動追加する)で画像付きでありますので、そちらを見てください。

アイコンのところは気分で設定したものなので好きに変えてください。とりあえず絵文字🅰を設定していますので、以下のコードの部分の絵文字を好きな絵文字に変更するとその絵文字でアイコンが登録されます。

     "icon": {
	      "type": "emoji",
	      "emoji": "🅰"
      }


このコードを利用する際には注意点があります。

今回は単語帳での想定で作成したので本文は短めのものしか対応していません。NotionAPIの仕様上1リクエストで登録できるブロック配列の中身は100ブロックまでと決まっています。1行1ブロックなため100行より多い内容だとエラーになります。
ブロックの制限などについて詳しく知りたい場合はNotionAPIの公式リファレンスを参照してください。


他にも、画像が入っていたり、インデントされていたりしてもエラーだったり表示されなかったりします。今回は自分用の単語帳のイメージで作成したので、単純に文章が入っているだけの想定で作成してあります。自分の使用する環境でそれらが必要になればそのうち対応します。

あと、自分が使う想定で作ってるので細かくエラー処理を行っていません。


定期実行の設定

さて、あとは定時実行するように設定すれば終わりです。

これも前回の記事(Notionの繰り返しタスクをGASで自動追加する)で書いてますので説明が必要であればそちらで確認してください。

時間は好きに決めてください。各々の学習内容とかによって最適な時間は変わるかと思います。1日3回変わるとか、1時間おきに変えるとか自分のスタイルにあった設定にしましょう。


最後に

いかがでしたでしょうか。自分用に作ってるものですが、お役に立てば幸いです。
いいねとかサポートとかしてくれるとモチベが上がります。
Notionだけで簡単にできれば一番いいんですけどね~ アップデート期待してます。


*このプログラムは個人的に使うためにてきとーに組んだプログラムです。テストも細かくはしていません。利用する際には自己責任でお願いします。このプログラムを利用することによって不利益を被ったとしても責任は取りません。


この記事が気に入ったらサポートをしてみませんか?