前回の投稿 Google Photosからランダムな1枚の画像を取得できるようにする にて、Google Photosに登録しておいた画像をランダムに表示されるフォトフレームを作りました。
今回は、少し拡張して、Google Calendarに登録しておいたイベントも合わせて表示するようにします。
Google PhotosのAPIの呼び出しの時には、REST呼び出しでしたが、Google CalendarはNode.js用のライブラリが用意されているのでそれを使います。
ですが、Googleアカウントログイン部分は、REST呼び出しのものを流用したいので、REST呼び出しで認証したGoogleアカウントの認証結果(アクセストークン)をGoogle Calendarライブラリに引き継ぐようにします。
ソースコードもろもろは、以下に上書きしています。
poruruba/GooglePhotosGallery
Google Calendar APIを利用できるようにする
Google Cloud Platformのコンソールから、Google Calendar APIを使えるように有効化します。
GCP:APIとライブラリ
https://console.cloud.google.com/apis/library
検索のところに、Calendarと入力すると、Google Calendar APIが出てきますので、選択してEnableにします。
認証結果をGoogleライブラリに引き継ぐ
以前の投稿 GoogleAPIライブラリを使わずにGoogleアカウントでログインできるようにする で認証結果(アクセストークン・IDトークン・リフレッシュトークン)をファイル出力していました。
それを使います。
function get_calendar(token){
const oAuth2Client = new google.auth.OAuth2(CLIENT_ID);
oAuth2Client.setCredentials(token);
return google.calendar({ version: 'v3', auth: oAuth2Client });
}
上記のtokenには、access_tokenがあれば大丈夫です。
ただし、このトークンを取得したときのGoogleアカウント認証時のScopeに、以下が含まれている必要があります。
https://www.googleapis.com/auth/calendar.readonly
CLIENT_IDは、認証時に利用していたクライアントIDを指定してください。
あとは、以下のように呼び出すだけです。
var result = await new Promise((resolve, reject) => {
calendar.events.list(params, (err, res) => {
if (err)
return reject(err);
resolve(res);
});
});
paramsには、以下で示されるパラメータを指定します。
取得結果には、nextPageTokenが含まれている場合があります。その場合には、すべてのイベントを取得しきれていないので、pageTokenにnextPageTokenを指定して、続けて呼び出す必要があります。
結局、こんな感じです。
async function get_event_list(calendar, date){
var startTime = date.toISOString();
var endDate = new Date(date.getTime());
endDate.setHours(23);
endDate.setMinutes(59);
endDate.setSeconds(59);
endDate.setMilliseconds(999);
var endTime = endDate.toISOString();
var params = {
calendarId: 'primary',
timeMin: startTime,
timeMax: endTime,
singleEvents: true,
orderBy: 'startTime',
};
var result = await new Promise((resolve, reject) => {
calendar.events.list(params, (err, res) => {
if (err)
return reject(err);
resolve(res);
});
});
var items = result.data.items || [];
while (result.nextPageToken) {
params.pageToken = result.nextPageToken;
result = await new Promise((resolve, reject) => {
calendar.events.list(params, (err, res) => {
if (err)
return reject(err);
resolve(res);
});
});
items = items.concat(result.data.items);
}
var list = [];
for (const item of items) {
var term = convert_date(item.start, item.end);
list.push({
summary: item.summary,
term: term
});
}
return list;
}
イベントの変更通知を受け取る
Google Calendarのイベントは、他のGoogleアプリから編集されるため、イベントリスト取得後にも変更されている場合があります。
そこで、変更されたことをNotificationとして通知してくれる機能があります。以下のように登録します。
(参考)
https://developers.google.com/calendar/api/guides/push?hl=ja
const CALENDAR_WEBHOOK_URL = 'https://【Node.jsサーバのURL】/googlecalendar-webhooks';
・・・
var params = {
id: uuidv4(),
type: "web_hook",
address: CALENDAR_WEBHOOK_URL
};
var result = await do_post_with_token('https://www.googleapis.com/calendar/v3/calendars/primary/events/watch', params, token.access_token);
URLのところで「primary」に指定することで、ログインユーザのプライマリカレンダーが変更検知対象になります。
idには、毎回異なる値を指定します。今回はuuidにしました。
addressには、通知を受けたいWebAPIのURLを指定します。
戻り値はJsonです。戻り値のresultには、idとresourceIdが含まれており、登録解除時に必要ですので、ファイルに保存しておきます。
解除は以下の通りです。
var params = {
id: json.notification.id,
resourceId: json.notification.resourceId
};
var result = await do_post_text_with_token('https://www.googleapis.com/calendar/v3/channels/stop', params, token.access_token);
console.log(result);
戻り値は、Jsonではなくテキストです。
以下は、通知を受けた時の処理の例です。
case '/googlecalendar-webhooks': {
console.log(event);
if( event.headers['x-goog-resource-state'] == 'exists'){
var token = await read_token();
const calendar = get_calendar(token);
var list = await read_event_list(calendar, true);
client.publish(TOPIC_CMD, "1");
}
return new Response({});
}
登録したURLに届く通知は、Google Calendarのイベントの変更時以外にも、いくつかのタイミングで届きます。HTTPヘッダー「x-goog-resource-state」がexistsの時が、イベント変更時の通知です。
上記の例では、イベントリストを更新したのち、MQTTにパブリッシュして他のデバイス(今回の場合は、LCD付のESP32がSubscribeしている)に通知しています。
ついでにGoogle Tasks(toDo)のタスクリストも取得してみます
こちらも、Googleライブラリ化されていますので、それを使います。
毎度の通り、以下で、Tasks APIを有効にします。
GCP:APIとライブラリ
https://console.cloud.google.com/apis/library
また、Googleアカウント認証時には、以下をScopeに含めます。
https://www.googleapis.com/auth/tasks.readonly
あとは、以下のようにtasksのインスタンスを生成して、呼び出すだけです。
case '/googletasks-list': {
var token = await read_token();
const tasks = get_tasks(token);
var list = await get_tasklist_list(tasks);
console.log(list);
for( var i = 0 ; i < list.length ; i++ ){
list[i].list = await get_task_list(tasks, list[i].id);
}
return new Response({ list: list });
}
・・・
function get_tasks(token) {
const oAuth2Client = new google.auth.OAuth2(CLIENT_ID);
oAuth2Client.setCredentials(token);
return google.tasks({ version: 'v1', auth: oAuth2Client });
}
async function get_tasklist_list(tasks){
var params = {};
var result = await new Promise((resolve, reject) => {
tasks.tasklists.list(params, (err, res) => {
if (err)
return reject(err);
resolve(res);
});
});
var items = result.data.items;
while (result.nextPageToken) {
params.pageToken = result.nextPageToken;
result = await new Promise((resolve, reject) => {
tasks.tasklists.list(params, (err, res) => {
if (err)
return reject(err);
resolve(res);
});
});
items = items.concat(result.data.items);
}
var list = [];
for (const item of items) {
list.push({
title: item.title,
id: item.id
});
}
return list;
}
async function get_task_list(tasks, id) {
var params = {
tasklist: id
};
var result = await new Promise((resolve, reject) => {
tasks.tasks.list(params, (err, res) => {
if (err)
return reject(err);
resolve(res);
});
});
var items = result.data.items || [];
while (result.nextPageToken) {
params.pageToken = result.nextPageToken;
result = await new Promise((resolve, reject) => {
tasks.tasks.list(params, (err, res) => {
if (err)
return reject(err);
resolve(res);
});
});
items = items.concat(result.data.items);
}
var list = [];
for (const item of items) {
list.push({
title: item.title,
notes: item.notes,
id: item.id,
parent: item.parent || null,
due: item.due ? new Date(item.due).getTime() : null
});
}
return list;
}
流れとしては、タスクリストというタスクを束ねたもののリストを取得したのち、タスクリストごとに含まれるタスクのリストを取得する、という流れです。
流れさえわかれば、おおよそ理解できると思いますので、詳しくは、GitHubのソースコード参照してください。
poruruba/GooglePhotosGallery
https://github.com/poruruba/GooglePhotosGallery
LCD付ESP32の実装
Google Calendarのイベントの取得は、以下のURLに対してJSON-POST呼び出ししているだけです。
https://【Node.jsサーバのホスト名】/googlecalendar-list
一方で、イベントの変更検知するために、MQTTでトピック「calendar/notify」でSubscribeして待ち受けています。通知を受けたら、Google Calendarイベントの再取得を含むLCD再描画処理を実行しています。
詳しくは、GitHubのソースコード参照してください。
poruruba/GooglePhotosGallery
https://github.com/poruruba/GooglePhotosGallery
以上