2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【コピペでOK!】続・Notion APIで、特定の雛形のページをもとに新しいページを作成する方法@Google Apps Script

Last updated at Posted at 2023-02-25

はじめに

こちらの記事は、以下の記事の続きです。
以下の記事では順を追って処理の説明等をしているので、詳細が気になる方は(長いですが)まず以下をご確認ください。

この記事では処理の詳細等の説明は特に行いません。
上記記事で作成したコードを一般化して使いやすくしたものを掲載することが目的です。

ソースコード

このプロジェクトは、global_variables.gs, page_properties.gs, functions.gsの3つのファイルから構成されています。

ファイル名 説明
global_variables.gs 参照するページやデータベース等の共通変数を格納しているファイルです。お使いのNotionにあわせて内容を設定してください。
page_properties.gs 新規作成するページのプロパティを設定するためのファイルです。実際に使用する雛形にあわせて編集してください。
functions.gs 各種関数を格納しているファイルです。編集は不要です。
global_variables.gs
//こちらとあわせて、NotionのAPIトークンを
//"notionApiToken"としてプロパティストアに保存すること。

//データベースをフルページで開いた際のURLの、?v=の前の32文字
const NOTION_DB_ID = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

//テンプレートページ(フルページ)のURLの最後の32文字。ハイフンがある場合はハイフンより後ろの部分
const NOTION_TEMPLETE_PAGE_ID = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

//Notionにデータを送る際には不要なプロパティ
const UNNECESSARY_PROPERTIES = [
  "object",
  "id",
  "created_time",
  "last_edited_time",
  "created_by",
  "last_edited_by",
  "annotations",
  "archived",
  "plain_text",
  "href",
  "type"
];
page_properties.gs
//テンプレートページのプロパティを取得
//まずはこの関数を実行し、テンプレートページのプロパティ構造を確認する
function checkSampleNotionPageProperties(){
  const pageId = NOTION_TEMPLETE_PAGE_ID;
  let response = fetchNotionPageProperties(pageId);
  response = deletePropertiesFromObj(response, UNNECESSARY_PROPERTIES);
  console.log(JSON.stringify(response, null, 4));
}

//上記で確認したプロパティ構造をもとに、ページ内容にあわせて書き換える
function setNewNotionPageProperties(properties){
  const obj = {
    "parent": {
      "database_id": NOTION_DB_ID
    },
    "properties": {
      "電話番号": {
        "phone_number": properties.tel
      },
      "メールアドレス": {
        "email": properties.mail
      },
      "利用開始日": {
        "date": {
          "start": properties.start_date,
          "end": null,
          "time_zone": null
        }
      },
      "Name": {
        "title": [{
          "text": {
            "content": properties.name
          }
        }]
      }
    }
  }

  return obj;
}
functions.gs
//指定した内容でNotion APIにリクエストを送る
function requestNotionApi(requestUrl, method, payload = null){
  const token = PropertiesService.getScriptProperties().getProperty("notionApiToken");

  const headers = {
    "Authorization" : "Bearer " + token,
    "Content-Type" : "application/json",
    "Notion-Version" : "2022-06-28" //最新バージョンは https://developers.notion.com/reference/versioning から確認
  };

  let requestOptions = {
    "method" : method,
    "headers" : headers,
    "muteHttpExceptions" : true
  }

  if(payload != null){
    requestOptions.payload = JSON.stringify(payload);
  }

  try {
    const response = JSON.parse(UrlFetchApp.fetch(requestUrl, requestOptions));
    return response;
  } catch(e) {
    console.log(e.message);
  }
}

//指定したページのプロパティを取得
function fetchNotionPageProperties(pageId){
  const requestUrl = "https://api.notion.com/v1/pages/" + pageId;
  const response = requestNotionApi(requestUrl, "get");
  return response;
}

//指定したページのBlock object(ページの本文部分)を取得
//callRecursivelyをtrueにすると、children含めて再帰的にすべて取得
function fetchNotionPageBlocks(blockId, callRecursively=false){
  const requestUrl = "https://api.notion.com/v1/blocks/" + blockId + "/children?page_size=100";
  const response = requestNotionApi(requestUrl, "get");

  let results = response.results;

  if(callRecursively){
    for(const key of Object.keys(results)){
      if(results[key].has_children){
        results[key].children = fetchNotionPageBlocks(results[key].id, true);
      }
    }
  }

  return results;
}

//テンプレートページのBlock object(ページの本文部分)を取得
function fetchSampleNotionPageBlocks(callRecursively=false){
  const pageId = NOTION_TEMPLETE_PAGE_ID;
  const response = fetchNotionPageBlocks(pageId, callRecursively);
  return response;
}

//新しいNotionページとして利用するオブジェクトの作成
function createNotionPageObj(properties){
  //プロパティ部分の定義
  let page = setNewNotionPageProperties(properties);

  //Block Object(本文部分)の定義
  let blocks = fetchSampleNotionPageBlocks();
  deletePropertiesFromObj(blocks, UNNECESSARY_PROPERTIES);

  //結合
  page.children = blocks;

  return page;
}

//オブジェクトをもとに、新しいNotionページを作成
function createNewNotionPage(notionPageObj) {
  const requestUrl = "https://api.notion.com/v1/pages";
  const response = requestNotionApi(requestUrl, "post", notionPageObj);
  return response;
}

//テンプレートページをもとに、新規ページを作成
function duplicateTempletePageWithNewProperties(properties){
  const notionPageObj = createNotionPageObj(properties);
  const newlyCreatedPageProperties = createNewNotionPage(notionPageObj);
  
  //APIで新たに作成したNotionページのブロック要素(本文部分)を取得
  //この段階ではインデントされた行(子孫要素)は取得できない
  const pageBlocks = fetchNotionPageBlocks(newlyCreatedPageProperties.id);

  //テンプレートとなるページを、子孫要素を含めて再帰的に取得
  const templetePageBlocks = fetchSampleNotionPageBlocks(true);

  //テンプレートページに合わせて、新たに作成したページに子孫要素を追加
  cloneChildrenFromTemplete(pageBlocks, templetePageBlocks);
}

//指定したblockIdにchildrenArrayの内容の子要素を追加する
function appendChildren(blockId, childrenArray){
  const requestUrl = "https://api.notion.com/v1/blocks/" + blockId + "/children";
  const payload = {
    "children" : childrenArray
  };
  const response = requestNotionApi(requestUrl, "patch", payload);
  return response;
}

//指定したページに対して、テンプレートページを照応させながら子要素を複製する
function cloneChildrenFromTemplete(pageBlocks, templetePageBlocks){
  for(const key of Object.keys(pageBlocks)){
    const blockId = pageBlocks[key].id;
    if(templetePageBlocks[key].has_children){
      //オブジェクトはデフォルトだと参照渡しされるため、元データを変えてしまわないように複製したものを用意する
      let childrenTemplete = duplicateObject(templetePageBlocks[key].children);
      
      //不要なプロパティの削除
      childrenTemplete = deletePropertiesFromObj(childrenTemplete, UNNECESSARY_PROPERTIES);
      
      //孫となるchildrenを含むオブジェクトを投げるとエラーになるため、削除
      childrenTemplete = deletePropertiesFromObj(childrenTemplete, ["children"]);

      const response = appendChildren(blockId, childrenTemplete);

      //再帰的に関数を実行し、孫要素がある場合には同様の処理を行う
      cloneChildrenFromTemplete(response.results, templetePageBlocks[key].children);
    }
  }
}

//引数がオブジェクトまたは配列であればtrueを返す
function isObject(value) {
  return value !== null && typeof value === 'object';
}

//オブジェクトをディープコピーする
function duplicateObject(obj){
  const retObj = JSON.parse(JSON.stringify(obj));
  return retObj;
}

//オブジェクト(子孫含む)から、指定したプロパティの一覧をすべて削除
function deletePropertiesFromObj(obj, properties){
  for(const property of properties){
    delete obj[property];
  }
  for(const key of Object.keys(obj)){
    if(isObject(obj[key])){
      deletePropertiesFromObj(obj[key], properties);
    }
  }
  return obj;
}

設定が必要な項目

スクリプトプロパティ

エディタの「プロジェクトの設定」画面から、notionApiTokenのスクリプトプロパティを追加してください。
こちらはNotionで発行したsecret_から始まるInternal Integration Tokenです。
取得方法については以下を参考にしてください。

スクリーンショット 2023-02-25 19.23.08.png

global_variables.gs

NOTION_DB_IDには、操作対象となるNotionのデータベースをフルページで開いた際のURLの?v=の前の32文字を入力してください。

NOTION_TEMPLETE_PAGE_IDには、雛形となるページをフルページで開いた際のURLの最後の32文字を入力してください。ハイフンがある場合は、ハイフンより後ろの部分が対象となります。

page_properties.gs

ここが少し複雑です。まずはエディタからcheckSampleNotionPageProperties関数を実行し、ログを確認してください。このログが、雛形ページのプロパティの設定内容となっています。
そのログ内容にあわせて、setNewNotionPageProperties関数内のobjの宣言内容を調整してください。

調整方法は、以下のとおりです。

  1. checkSampleNotionPagePropertiesのログのpropertiesの中身をコピーし、objpropertiesにペーストする。
  2. 変数として用いたいプロパティの値を、properties.xxxと書き換える。電話番号であればproperties.telのような形式。※内容は引数として渡すオブジェクトにあわせて調整してください
  3. 新規カード作成時に定義不要なプロパティを削除する。

使い方

設定したいプロパティを持ったオブジェクトを引数として、duplicateTempletePageWithNewProperties関数を呼び出してください。

使用例

以前の記事で紹介したGoogleフォームの回答を渡してあげるケースだと、以下のようなコードになります。

main.gs
function onFormSubmit(){
  let response = {};
  for(let i = 1; i <= 10; i++){
    try{
      response = getNewestFormResponse();
      break;
    } catch(error) {
      console.log(error.message);
      if(i === 10){
        console.log("フォームの回答取得に失敗したため、スクリプトを終了します");
        return;
      }
      Utilities.sleep(10000);
    }
  }

  const parsedFormResponse = parseResponse(response);
  duplicateTempletePageWithNewProperties(parsedFormResponse);
}

function getNewestFormResponse(){
  const form = FormApp.getActiveForm();
  let yesterday = new Date();
  yesterday.setDate(yesterday.getDate() - 1);
  const responses = form.getResponses(yesterday);
  const newestResponse = responses.pop();
  return newestResponse;
}

function parseResponse(e){
  //すべての質問と回答を取得する
  const itemResponses = e.getItemResponses();

  //質問と回答から各要素を取得してオブジェクトを再生成。
  //選択式の質問で無回答だとインデックスがスキップされるため、インデックスは使用しない。
  let parsedResponses = {};

  //メールアドレスのみ取得方法が特殊なので注意(フォーム設定で「メールアドレスを収集する」にチェックを入れた場合)
  parsedResponses.mail = e.getRespondentEmail();

  //フォームの質問と回答を1件ずつチェックして、オブジェクトに収納
  for(let i = 0; i < itemResponses.length; i++){
    const title = itemResponses[i].getItem().getTitle();
    const response = itemResponses[i].getResponse();
    
    switch (title) {
      case "お名前":
        parsedResponses.name = response;
        break;
      case "携帯電話番号":
        parsedResponses.tel = response;
        break;
      case "利用開始日":
        parsedResponses.start_date = response;
        break;
    }
  }

  return parsedResponses;
}
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?