Notion で(自分なりの)タスク・時間管理を行う「新たな」方法(予定管理編)
前回の記事では、「終了予定日時」の算出方法などについて、補足というか、改良を行いましたが、
今回は、打って変わって、Google カレンダーとの連携に関するお話です。
とは言え、実は、過去にも何度か、話題にしているテーマでもありますし、
そもそも「RoutineGarden」自体、あまり初心者向けではないところもあるので、細かい設定手順の説明は、以下の記事に委ねるとしましょう。
正直、この辺の解説を省くために、上のような記事を書いてきたというところもあります。
もちろん、「RoutineGarden」特有の部分もあるので、大まかな流れは追っていくつもりですが、
肝心なのは、なぜ Google カレンダーと連携させるのか、という点を、改めて、確認することだとも言えるかもしれません。
より具体的には、カレンダーによる「予定管理」と、その他の「ルーチン管理」との、私なりの使い分けについて、紹介しておこうというわけですね。
それでは、始めていきましょう。
Google カレンダーから今日の予定を取得する
さて、早速ですが、Google カレンダーから「今日の予定」を取得する方法については、上にリンクを貼った記事を参照してもらうとして、
今回、基本的な説明は、大幅に割愛させてもらいます。
その代わりと言ってはなんですが、実際に私が利用しているコードを紹介するついでに、
どれくらいの需要があるかはともかく、複数のカレンダーの予定を合算する場合の書き方も見せておきましょう。
function myFunction() {
const secret = "ntn_XXXXXXXXXX";
const now = new Date();
const max = new Date(new Date().setHours(23, 59, 0, 0));
const privateEvents = Calendar.Events.list('primary', {
timeMin: now.toISOString(),
timeMax: max.toISOString(),
singleEvents: true,
orderBy: 'startTime'
}).items;
const publicEvents = Calendar.Events.list('xxxxxxxxxx@gmail.com', {
timeMin: now.toISOString(),
timeMax: max.toISOString(),
singleEvents: true,
orderBy: 'startTime'
}).items;
const events = privateEvents.concat(publicEvents)
if (!events || events.length === 0) {
return;
}
(...)
}
ここで、「Calendar.Events.list()」によって「今日の予定」を取得しているカレンダーの内、
上の「primary」の方が、自分の Google アカウントで作成した、デフォルトのカレンダーであり、
下の、何やら Gmail アドレスで指定されている方が、他の Google アカウントによって作られ、共有されているカレンダーです。
私の場合は、プライベート用の Google アカウントに対して、仕事の予定が入ったアカウントのカレンダーも共有しているので、
それら二つのカレンダーから、「今日の予定」をまとめて取ってきて、「今日のプラン」に追加しようといった感じですね。
ちなみに、各 Google アカウントの、デフォルトのカレンダー以外のカレンダーを指定する場合には、
どのような文字列を「Calendar.Events.list()」に渡せば良いのか、すなわち、「カレンダー ID」を調べておく必要があります。
例えば、以下のような関数を作って、実行してみると良いかもしれません。
function test() {
const calendars = CalendarApp.getAllCalendars();
for (const calendar of calendars) {
console.log(calendar.getName(), calendar.getId());
}
}
まぁ、この辺の話は、一応、参考までに。
Notion に今日のタスクを追加する
あとは、「今日の予定」を、Notion 上で「今日のプラン」に表示されるようにしていくだけなのですが、
もちろん、「RoutineGarden」の場合は、単純にタスクを追加するというわけにもいきません。
そもそも、「ルーチンデータベース」を絞り込んで「今日のプラン」を表示する仕組みなので、必要に応じて、ルーチンを追加することになります。
ルーチンデータベースを確認する方法
つまり、取得した予定に対して、
同じ名前のルーチンがなければ、新規に追加し、
既にあれば、そのルーチンを「今日のプラン」に表示させるわけですね。
それには、ルーチンがあるか(過去に追加しているか)どうかを確認するため、以下のようにして、ルーチンリストを取得する必要があります。
function getRoutines(secret) {
const data = { "filter": { "property": "周期", "multi_select": { "contains": "予定" } } }
const options = {
'method': 'post',
'headers': {
'Authorization': 'Bearer ' + secret,
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28'
},
'payload': JSON.stringify(data)
};
const response = UrlFetchApp.fetch('https://api.notion.com/v1/databases/xxxxxxxxxx/query', options);
const contents = JSON.parse(response.getContentText());
const object = {};
for (const result of contents.results) {
object[result.properties["名前"].title[0].plain_text] = result.id;
}
return object;
}
厳密に言うと、この「getRoutines()」関数が返すのは、ルーチンの名前から ID を取得できるオブジェクトです。
ID があれば、そのルーチン(ページ)のプロパティを、API で、簡単に変更できますからね。
ちなみに、「filter」を指定して、「周期」プロパティが「予定」のものだけを取ってくるように、絞り込んでいますが、
Google カレンダーから追加したルーチンを、他と区別するためにも、
これは、そもそも新規作成の際に設定しておく必要があることを(下でも出てくるので)覚えておいてください。
セクションを設定する方法
また、予定の開始時刻から、セクションも設定しておきたいので、セクションデータベースを元に、該当するセクションを算出する手段も必要です。
例えば、以下のような感じで、こちらも関数を準備しておきましょう。
function hours2section(sections, hours) {
const keys = Object.keys(sections);
const start = keys.find(key => hours >= key);
return sections[start];
}
function getSections(secret) {
const data = { "sorts": [{ "property": "開始", "direction": "descending" }] }
const options = {
'method': 'post',
'headers': {
'Authorization': 'Bearer ' + secret,
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28'
},
'payload': JSON.stringify(data)
};
const response = UrlFetchApp.fetch('https://api.notion.com/v1/databases/xxxxxxxxxx/query', options);
const contents = JSON.parse(response.getContentText());
const object = {};
for (const result of contents.results) {
object[result.properties["開始"].number.toString().padStart(2, '0') + ':00'] = result.id;
}
return object;
}
時分の「時」の部分だけで判定しているため、そこは少し異なりますが、基本的な流れは、以前、説明したことのある方法と同様ですね。
ルーチンを追加/更新する方法
それでは、いよいよ、「myFunction()」関数の後半、Google カレンダーから予定を取得した後の処理を、一気に紹介しましょう。
function myFunction() {
(...)
const routines = getRoutines(secret);
const sections = getSections(secret);
for (const event of events) {
const title = event.summary;
let section = "";
let minutes = 0;
const date = { "time_zone": null };
if (event.start.date) {
const start = new Date(event.start.date);
date["start"] = start.toISOString().split('T')[0];
} else {
const hours = new Date(event.start.dateTime).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false });
section = hours2section(sections, hours);
const start = new Date(new Date(event.start.dateTime).getTime() + (9 * 60 * 60 * 1000));
const end = new Date(new Date(event.end.dateTime).getTime() + (9 * 60 * 60 * 1000));
minutes = (end - start) / 1000 / 60;
date["start"] = start.toISOString().replace('Z', '') + '+09:00';
date["end"] = end.toISOString().replace('Z', '') + '+09:00';
}
const routine = routines[title];
if (routine) {
const data = {
"properties": {
"見積": { "type": "number", "number": minutes },
"予定日時": { "type": "date", "date": date }
}
};
if (section) {
data.properties["セクションリスト"] = { "relation": [{ "id": section }] };
}
const options = {
'method': 'patch',
'headers': {
'Authorization': 'Bearer ' + secret,
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28'
},
'payload': JSON.stringify(data)
};
UrlFetchApp.fetch('https://api.notion.com/v1/pages/' + routine, options);
} else {
const data = {
"parent": { "type": "database_id", "database_id": "xxxxxxxxxx" },
"properties": {
"名前": { "type": "title", "title": [{ "type": "text", "text": { "content": title } }] },
"見積": { "type": "number", "number": minutes },
"開始日": { "type": "date", "date": { "start": date["start"].split('T')[0], "time_zone": null } },
"周期": { "type": "multi_select", "multi_select": [{ "name": "予定" }] },
"予定日時": { "type": "date", "date": date }
}
};
if (section) {
data.properties["セクションリスト"] = { "relation": [{ "id": section }] };
}
const options = {
'method': 'post',
'headers': {
'Authorization': 'Bearer ' + secret,
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28'
},
'payload': JSON.stringify(data)
};
UrlFetchApp.fetch('https://api.notion.com/v1/pages', options);
}
}
}
長くなるので、細かい説明は省きますが、重要なのは「if (routine)」による分岐で、カレンダーから取得した各予定に対して、
既にルーチンが存在すれば、その「見積」や「予定日時」を更新し、
そうでなければ「else」の方で、ルーチンリストに新しいルーチンを追加しています。
ここで、ルーチンを新規作成する際には、確かに、「周期」プロパティに「予定」を設定していますね。
ちなみにですが、Notion 上で、あらかじめ「予定」ルーチンを作成しておいても、特に問題はありません。
こうして、ここまでに記載した、「test()」以外の、全てのコードを組み合わせれば、
結果として、Google カレンダーから取得した「今日の予定」を、「今日のプラン」に追加しておくことができるはずです。
トリガーを追加して自動実行させる方法
最後に、メインの「myFunction()」関数が、毎日、自動的に実行されるよう、トリガーを追加しておきましょう。
とは言え、詳細については、例によって、過去の記事をご参照ください。
まとめ
というわけで、今回は、ルーチン管理とは別で、Google カレンダーで予定を管理する場合の、連携方法について、紹介してみました。
コードの内容を、細かい部分まで理解するのは、JavaScript が分かっていないと、なかなか難しいと思いますが、
ともかく、何をやろうとしているのかは、伝わりましたでしょうか?
要するに、「RoutineGarden」では、「周期」プロパティの設定は、かなりシンプルな形式になっており、
例えば、隔週でやるとか、毎月15日にやるとか、そういった特殊な間隔での繰り返しは、指定できないのですが、
そういったルーチンの繰り返し設定は、いっそのこと、他の不定期の予定と一緒に、Google カレンダーに任せてしまって、
なるべく Notion 上では考えないようにすることで、「今日やるタスク」の判定を、簡略化してしまっているわけですね。
本システムでは、「周期」が設定されていなくても、「予定日時」が「今日の日付」と一致していれば、「今日のプラン」に表示されるというか、
逆に「予定日時」が設定されていると、「周期」プロパティは無視されるようになっているので、その点には、要注意ですが。
まぁ、ある意味では、一部のルーチンの管理は、Google カレンダーによって補ってやる必要があると言えるかもしれませんし、
もとより、不定期の予定すら、最終的にはルーチンとして管理していくシステムだという言い方もできるでしょう。
ともあれ、これで、「RoutineGarden」の仕様の説明において、特に重要な部分は、一通り話せたような気がしますが、
今後も、システムを拡張したり、修正したりしながら、順次、細かいトピックを取り上げていく予定です。
ではまた。