はじめに
Notionで、日付付きのデータベース(データのページ)を作って、それをNotion上でカレンダービューにすることはできるけど、普段はGoogleカレンダーを使っているから、その上にレイヤーを重ねるように反映させたい。Notionカレンダーを、Googleカレンダーの「他のカレンダー>「+」で追加 したい。
つまり、Notion→Googleカレンダーの方向の連携をGASで作ってみたお話です。
連携ツールは有料のものがあるけど、ここで示す方法なら無料です。
準備
Notionで、インテグレーションを追加して、ページに接続。Notionのデータを読めるようにします。解説ページがたくさんあるので、その方法は割愛。
作ったインテグレーションの設定の中から、"内部インテグレーションシークレット"を取得します。すごい名前ですが、APIトークンと言い換えていいでしょう。
GASでの実装
役割は大きく2つです。
作る順で説明すると
- ①Notionからカレンダー情報を取得
- ②Googleカレンダーから呼び出しがかかるので、それに答えるときに、Notionからカレンダー情報を取得(①)し、ICS形式に変換し、返答する
実質は、②の中で①を呼び出すので1つといえば1つです。
①Notionからカレンダー情報を取得
APIに投げるだけ。
const url = `https://api.notion.com/v1/databases/${NOTION_DATABASE_ID}/query`;
const options = {
method: "POST",
headers: {
"Authorization": `Bearer ${NOTION_API_TOKEN}`,
"Content-Type": "application/json",
"Notion-Version": "2022-06-28",
},
};
const response = UrlFetchApp.fetch(url, options);
const data = JSON.parse(response.getContentText());
APIの仕様書はこちら。
data.results
に、Notionのデータベース(=1つの予定)の配列が入ります。
"データベース"という言葉からは、レコード群を含むテーブルを含むハコみたいな大きなものを想定してしまいますが、Notionにおけるデータベースオブジェクトとは、1つのレコードです。以下では、一般的な意味と区別するために、Notionのデータベースオブジェクトを、Database とアルファベットで書きます。
data.results
から得られる配列の1要素であるDatabaseの公式のAPI仕様書はこちら。
下記のように、1要素ずつ、properties['場所']
のように取り出します。
const events = data.results.map(item => {
const props = item.properties;
//console.log(props);
const place0 = props['場所'].rich_text[0]?.plain_text || '';
const place = place0.length > 0 ? `場所: ${place0}\n` : '';
const category0 = props['カテゴリ'].multi_select?.map(c => c.name).join(',') || '';
const category = category0.length > 0 ? `カテゴリ: ${category0}\n` : '';
const url = item.url || '';
const description = `${place}${category}${url}`;
return {
title: props['イベント名'].title[0]?.plain_text || 'No Title',
start: props['日付'].date?.start || null,
end: props['日付'].date?.end || props['日付'].date?.start || null,
description: description,
id: item.id,
};
});
②Googleカレンダーから呼び出されたとき、Notionからカレンダー情報を取得し、ICS形式に変換し、返答する
後で説明しますが、Googleカレンダーでは、他のカレンダー>+>URLで追加
でカレンダーを追加します。そうすると、Googleカレンダー側から指定したURLへGETリクエストが投げられて、受けた側が正しくICS形式で返すと、Googleカレンダーに表示されるという仕組みです。
まずGETリクエストの関数は、GASではdoGet(e)
を定義することでその役割を果たします。なので以下はその関数内の実装。
(1)Notionからデータを取得し、(2)ICS形式へ変換、それを (3)returnするという処理を実装します。
②-(1) Notionからデータを取得
これは①のことです。
②-(2) ICS形式へ変換
ICS形式と言っても結局文字列です。それを作成する処理。
VACENDAR
という要素の中に、VEVENT
という要素を繰り返すという形式を作ります。
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Notion to iCal//EN
CALSCALE:GREGORIAN
<<中身を繰り返す>>
END:VCALENDAR
BEGIN:VEVENT
UID:(一意のID)
SUMMARY:(タイトルになる文字列)
DTSTAMP:(作成日、必須)
DTSTART:(予定の開始日時)
DTEND:(予定の終了日時)
DESCRIPTION:(予定の詳細)
END:VEVENT
Notionで得た情報をもとに、ループで作っていくだけです。
VEVENT
の属性は、他にも設定できるようですが、Googleカレンダーが採用していないと最終的には意味がないので、必要に応じて調べてください。
UIDは、Notion側で更新したり削除したときに、このUIDを頼りに反映されると思うので、1つのDatabaseから一意になる値を設定してください。幸い、NotionのDatabaseにもid
という項目があるので、それをそのまま設定すればよいです。
②-(3) ICS形式をreturn
MimeTypeでICALを指定してdoGet()
でreturn
します。ここが特殊。
return ContentService
.createTextOutput(icsText)
.setMimeType(ContentService.MimeType.ICAL);
応用
URLのパラメータを使って、doGet()
で分岐ができます。例えば、特定の部署だけを抽出するとか。
それができると、1つのURLだけどパラメータを変えることで、Databaseのプロパティに"部署"があるなら、"部署A用のカレンダー"、"部署B用のカレンダー"を簡単に作れます。部署Aの人向けに、"部署Aだけ"と"部署A以外"みたいなカレンダーもできます。必要に応じて簡単に実装できる。
まとめ
Notionからどの情報を抽出するかはケースバイケースなので、あまり詳しく書きませんでしたが、キモはこれがすべてです。
気になる点としては、長期運用するに従って単純に予定が増え、渡すデータが増えていくと思います。Notionから抽出するとき、"過去2か月以降の予定のみを抽出"などとフィルターすればよいといえばよいですが、過去の分はどう表示されるのか、前回の問い合わせにあったVEVENT
が今回ないとき、どうなるのか。Googleカレンダー側の仕様なので、今の仕様が今後もずっとそうであるかはわからないし、どうしよっかなーと気になっています。
まぁ、2か月以降の予定にフィルターして、それより過去はGoogleカレンダーでは見えなかったとしても問題なさそうではあります。Notionにはちゃんと残っていますので。
いい方法をご存じの方がいらしたら、コメントで教えてください。
このICSという仕様は、ベタベタの文字列の仕様で簡単に作れるので、もし他で利用できる場面があるなら簡単に対応できます。また、GASのdoGet()
との相性もいい。横展開はできるけど、その場面がどれだけあるのか。
ではよき計画的な生活を。