はじめに
こちらの記事は、以下の記事の続きです。
以下の記事では順を追って処理の説明等をしているので、詳細が気になる方は(長いですが)まず以下をご確認ください。
この記事では処理の詳細等の説明は特に行いません。
上記記事で作成したコードを一般化して使いやすくしたものを掲載することが目的です。
ソースコード
このプロジェクトは、global_variables.gs
, page_properties.gs
, functions.gs
の3つのファイルから構成されています。
ファイル名 | 説明 |
---|---|
global_variables.gs | 参照するページやデータベース等の共通変数を格納しているファイルです。お使いのNotionにあわせて内容を設定してください。 |
page_properties.gs | 新規作成するページのプロパティを設定するためのファイルです。実際に使用する雛形にあわせて編集してください。 |
functions.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"
];
//テンプレートページのプロパティを取得
//まずはこの関数を実行し、テンプレートページのプロパティ構造を確認する
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;
}
//指定した内容で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
です。
取得方法については以下を参考にしてください。
global_variables.gs
NOTION_DB_ID
には、操作対象となるNotionのデータベースをフルページで開いた際のURLの?v=
の前の32文字を入力してください。
NOTION_TEMPLETE_PAGE_ID
には、雛形となるページをフルページで開いた際のURLの最後の32文字を入力してください。ハイフンがある場合は、ハイフンより後ろの部分が対象となります。
page_properties.gs
ここが少し複雑です。まずはエディタからcheckSampleNotionPageProperties
関数を実行し、ログを確認してください。このログが、雛形ページのプロパティの設定内容となっています。
そのログ内容にあわせて、setNewNotionPageProperties
関数内のobj
の宣言内容を調整してください。
調整方法は、以下のとおりです。
-
checkSampleNotionPageProperties
のログのproperties
の中身をコピーし、obj
のproperties
にペーストする。 - 変数として用いたいプロパティの値を、
properties.xxx
と書き換える。電話番号であればproperties.tel
のような形式。※内容は引数として渡すオブジェクトにあわせて調整してください - 新規カード作成時に定義不要なプロパティを削除する。
使い方
設定したいプロパティを持ったオブジェクトを引数として、duplicateTempletePageWithNewProperties
関数を呼び出してください。
使用例
以前の記事で紹介したGoogleフォームの回答を渡してあげるケースだと、以下のようなコードになります。
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;
}