loader image

NotionとSlackを活用した究極のタスク管理術

NotionSlack連携ブログアイキャッチ

NotionとSlackを組み合わせて、究極のタスク管理体験を実現します。この強力な組み合わせにより、プロジェクトの効率と生産性が格段に向上します。

成果物

このように指定した時間にNotionに登録されているタスクが自動で送られてきます。

以下のコードをGoogle Apps Scriptに貼り付けてご利用ください。なお、WebhookおよびNotion API、Notionデータベースの設定はコメントアウト(//が書かれている部分)に従ってご変更ください。

var SLACK_WEBHOOK_URL = 'slackに追加したWebhookよりWebhook URLを挿入'; //WebhookURLの取得はSTEP1

//Notion上のタスクをスプレッドシートに同期させる関数(定期的に起動する)
function syncNotionDatabaseToSpreadsheet() {
  const notionApiKey = 'NotionAPIKeyを挿入'; //NotionAPIKeyを挿入(STEP2)
  const databaseId = ' NotionデータベースのIDを挿入'; //タスクを登録しているNotionデータベースのIDの挿入(STEP3)
  const url = 'https://api.notion.com/v1/databases/' + databaseId + '/query';
  const headers = {
    'Authorization': 'Bearer ' + notionApiKey,
    'Notion-Version': '2022-06-28',
    'Content-Type': 'application/json'
  };
  const options = {
    'method': 'post',
    'headers': headers,
  };

  const response = UrlFetchApp.fetch(url, options);
  const data = JSON.parse(response.getContentText());
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = spreadsheet.getSheetByName("インポート");
  const existingIds = sheet.getRange(1, 1, sheet.getLastRow()).getValues().flat();

  data.results.forEach((result) => {
    let taskname = "TODOデータベースのタスク名のプロパティ名に置き換え"; //タスクデータベースのTitle(見出し)の名前を登録してください(STEP4)
    let datename = "TODOデータベースの期限のプロパティ名に置き換え"; //タスクデータベースの期限を登録しているプロパティの名前を入れてください(STEP4)
    const id = result.id;
    const name = result.properties.taskname.title[0]?.plain_text || "";
    const status = result.properties.status.status.name;
    let formattedDate = "";

    if (result.properties.datename && result.properties.datename.date) {
      const date = new Date(result.properties.datename.date.start);
      formattedDate = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
    }

    const rowIndex = existingIds.indexOf(id) + 1;

    if (rowIndex > 0) {
      sheet.getRange(rowIndex, 2).setValue(name);
      sheet.getRange(rowIndex, 3).setValue(status);
      sheet.getRange(rowIndex, 4).setValue(formattedDate);
      sheet.getRange(rowIndex, 5).setValue()
    } else {
      sheet.appendRow([id, name, status, formattedDate]);
      existingIds.push(id);
    }
  });
}

//当日期限のタスク一覧の送信処理に使用する関数
function sendTodaysTasksToSlack() {
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = spreadsheet.getSheetByName("当日判定"); //雛形のスプレッドシートの「当日判定」シートの名称を変更する場合はこちらも変更
  const lastRow = sheet.getLastRow();
  
  // シートからページIDとタスク名を取得
  const pageIds = sheet.getRange(2, 1, lastRow - 1).getValues().flat();
  const tasks = sheet.getRange(2, 2, lastRow - 1).getValues().flat();
  const status = sheet.getRange(2,3 ,lastRow - 1).getValues().flat();
  
  // ページIDを基にNotionページのURLを動的に生成し、それとタスク名を組み合わせる
  let combinedTasks = [];
  for (let i = 0; i < pageIds.length; i++) {
    if (pageIds[i] && pageIds[i] !== "#N/A") {
      const notionUrl = `https://www.notion.so/nishiyuki0501/${pageIds[i].replace(/-/g, '')}`;
      combinedTasks.push(tasks[i]);
      combinedTasks.push(status[i]);
      combinedTasks.push(notionUrl);
      
    }
  }

  if (combinedTasks.length > 0) {
    sendTasksToSlack(combinedTasks);
  } else {
    sendTasksToSlack(["本日のタスクはNotion上には登録されていません。"]);
  }
}

//当日完了したタスク一覧の送信処理に使用する関数
function sendTodaysTasksToSlack_done() {
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = spreadsheet.getSheetByName("実績判定"); //雛形のスプレッドシートの「実績判定」シートの名称を変更する場合はこちらも変更
  const lastRow = sheet.getLastRow();
  
  // シートからページIDとタスク名を取得
  const pageIds = sheet.getRange(2, 1, lastRow - 1).getValues().flat();
  const tasks = sheet.getRange(2, 2, lastRow - 1).getValues().flat();
  
  
  // ページIDを基にNotionページのURLを動的に生成し、それとタスク名を組み合わせる
  let combinedTasks = [];
  for (let i = 0; i < pageIds.length; i++) {
    if (pageIds[i] && pageIds[i] !== "#N/A") {
      const notionUrl = `https://www.notion.so/nishiyuki0501/${pageIds[i].replace(/-/g, '')}`;
      combinedTasks.push6(tasks[i]);
      combinedTasks.push(notionUrl);
    }
  }

  if (combinedTasks.length > 0) {
    sendTasksToSlack_done(combinedTasks);
  } else {
    sendTasksToSlack_done(["本日のタスクはNotion上には登録されていません。"]);
  }
}

//sendTodaysTasksToSlack関数の送信処理
function sendTasksToSlack(tasks) {
  const today = new Date();
const formattedDate = `${today.getFullYear()}年${String(today.getMonth() + 1).padStart(2, '0')}月${String(today.getDate()).padStart(2, '0')}日`;
var payload = {
    text: '*' + formattedDate + 'のタスク一覧:*' + '\n' + tasks.join('\n' ) 
};

  var options = {
    method: 'POST',
    contentType: 'application/json',
    payload: JSON.stringify(payload)
  };

  UrlFetchApp.fetch(SLACK_WEBHOOK_URL, options);

}

//sendTodaysTasksToSlack_done関数の送信処理
function sendTasksToSlack_done(tasks) {
  const today = new Date();
const formattedDate = `${today.getFullYear()}年${String(today.getMonth() + 1).padStart(2, '0')}月${String(today.getDate()).padStart(2, '0')}日`;
var payload = {
    text: '*' + formattedDate + 'の完了タスク一覧:*' + '\n' + tasks.join('\n')
};


  var options = {
    method: 'POST',
    contentType: 'application/json',
    payload: JSON.stringify(payload)
  };

  UrlFetchApp.fetch(SLACK_WEBHOOK_URL, options);

}

各ツールのご紹介

まず、最初にNotionとslackの紹介をします。弊社でも大変お世話になっているツールで、この組み合わせで管理されている方も多いかと思いますので、導入されていない方はまずこちらからご覧ください。

Notionでのタスク管理

Notionは柔軟なタスク管理とプロジェクト計画のための多機能プラットフォームです。タスクのリスト化、進捗の追跡、期限の設定が一箇所で管理でき、独自の管理システムの構築も可能になります。

Slackでのコミュニケーション最適化

Slackは、チームコミュニケーションを効率化し、プロジェクト関連情報の共有を迅速化します。Notionで管理されているタスクの更新情報をSlackでリアルタイムに共有することで、情報の透明性が向上し、チームワークが促進されます。

NotionとSlackの統合

NotionとSlackを統合することで、タスク管理の自動化が実現します。Notionでのタスク更新時にSlackに自動通知が送られ、プロジェクトの進捗状況をチーム全体でリアルタイムに共有できます。これにより、プロジェクトの効率性が大幅に向上し、タスクの見逃しや遅延を防ぎます。

各設定項目の説明

STEP1 Webhook URLの取得

  1. slack画面からインストールアプリ画面を開く
  2. ブラウザの右上の検索窓に「webhook」と入力して、「Incoming Webhook」を選択して、アプリを追加する

STEP2 NotionAPIの取得

  1. Notion APIの公式ページに移動してください。(https://developers.notion.com/
  2. 右上のMy Integrationを押します。
  3. ご自身がわかるようなインテグレーションの名前を入力して下の写真のようになるように選択してsubmitボタンを押してください。(基本触らなくて良いはずです)
  4. Secretsのトークンキーをコピーしてください。最初のコードにコピペして使います。

STEP3 NotionデータベースのIDの取得

  • 自身のNotionページを開きます。(ブラウザ版を奨励します)
  • データ元(slackに共有したいデータが集まっているデータベース)を開きます。開くときはデータベース右上にある最大化ボタンを押してください。
  • データベースのフルページを開くと、右上の3点から連携(connection)を選択してください。こちらから先ほどのステップで追加したAPIをこのページと繋いでいきます。
  • 3点ボタンを押して、下部にある連携(connection)の中にある先ほどのステップ2で発行したNotion APIの名前を選択してください。
  • 最後にデータベースのIDを探します。リンクは以下の構成になっています。ワークスペースの部分の文字列をコピーしてください。後ほど最初のコードに埋め込みます。
    ※もしもアプリ版(パソコンソフト版)を使っている場合は⌘+Lでそのページのリンクをコピーできるのでテキストエディタなどに貼り付けて上記の文字列を探してみてください。

リンクの構成)

https://notion.so/[ワークスペースのリンク]/[データベースID]?v=[viewのリンク]

STEP4 Notionデータベースのプロパティ名の確認

  1. タスク名および期日として利用しているdateプロパティのそれぞれの名前を確認します。
  2. 最初のコードのSTEP4の部分におきかえます。(ダブルコーテーション(“)は残してテキストのみ変更してください)

STEP5 コードに反映されていく

  1. Google Apps Script(以下、GASと表記)の公式ページを開きます。(https://script.google.com/home
  2. (初めての方はログインします)
  3. エディタを開いたら、最初のコードを全てコピペしてください。最初のfunction(){}も消して、全て置き換えます。
  4. 保存して、実行を押すと権限確認が必要というポップアップが表示されるので「権限を確認」を押してください。
  5. Googleでログインすると「このアプリはGoogleに接続されていません」と出てくるので左下の「詳細」を押してください。
  6. 「詳細」を押すと「安全でないページへ移動」と出てくるので押してください。
    ※外部サービスとの連携により、情報漏洩などのリスクがあるという意味で接続には注意してくださいという意味です。
  7. 外部サービスとの接続を許可してください。(GASでプロジェクトを初めて動かすときは毎回この作業が必須になります。)

実際に動かした通知

実際に動かしてみるとslackの指定したチャンネルにその日期限(またはその日期限の完了タスク)が共有されます。

タスクとそのNotionページのURLが表示されます

通知のタイミングはGASのトリガーを用いて指定の時間にチェックするなどすれば完全に自動で送られるようになります。

結論

NotionとSlackの統合により、タスク管理はより効率的かつ効果的になります。この強力なツールの組み合わせを活用することで、チームの生産性を最大化し、プロジェクトを成功に導くことができます。

報告書作成を劇的に速くする!GoogleスプレッドシートデータをGoogleドキュメントに自動挿入

Googleスプシからドキュメントに自動転記

はじめに

記事をご覧いただきありがとうございます。私はノーコードでのアプリ開発やNotion、GASを用いた効率化を行っています。

この記事ではスプレッドシートでまとめたデータ一覧を1行改行を入れてドキュメントに列挙してコピペします。完成形として、スプシで作ったデータを報告書に転記することができます。

基本は以下のコードをコピペすることで可能ですので、ご活用ください。

前提条件

このスクリプトを使用するには、GoogleスプレッドシートとGoogleドキュメントにアクセスできるGoogleアカウントが必要です。また、Google Apps Scriptの基本的な知識があると、このガイドをより容易に理解できます。

スクリプトの概要

このスクリプトは、特定のスプレッドシートの列にあるテキストデータを取得し、新しいGoogleドキュメントにテキストとして挿入します。この例では、’G’列のデータを取得し、それを「ユーザーインタビュー報告書」という名前の新しいドキュメントに挿入します。

ステップ 1: スプレッドシートの準備

ユーザーインタビューのデータを含むスプレッドシートを用意します。データは’G’列に配置してください。列はカスタマイズ可能ですが、このガイドでは’G’列を使用します。

スプシでデータを上記のようにまとめる

ステップ 2: スクリプトエディタの開始

  1. スプレッドシートを開きます。
  2. メニューから「拡張機能」>「Apps Script」を選択します。
GAS起動画面
メニューバーの拡張機能>Apps ScriptよりGASエディタを起動

ステップ 3: スクリプトの記述

Apps Scriptエディタに以下のスクリプトをコピー&ペーストします。

function exportInterviewDataToDoc() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  var column = sheet.getRange('G:G').getValues();//G列のデータを上から順に列挙していきます
  var doc = DocumentApp.create('ユーザーインタビュー報告書');
  var body = doc.getBody();
  
  var text = '';
  for (var i = 0; i < column.length; i++) {
    if (column[i][0] !== '') {
      text += column[i][0] + '\n\n';
    }
  }
  
  body.appendParagraph(text);
}

ステップ 4: スクリプトの実行

  • エディタ上で、上記の関数exportInterviewDataToDocを選択し、実行ボタン(▶︎)をクリックします。
  • 初回実行時には、Googleによる認証が必要になります。

ステップ 5: 結果の確認

スクリプト実行後、Googleドキュメントに「ユーザーインタビュー報告書」という新しいドキュメントが作成されていることを確認します。このドキュメントには、スプレッドシートの’G’列にあったテキストデータが挿入されています。

スプレッドシートのデータ一覧がドキュメント内に列挙されます。

まとめ

この方法を用いれば、スプレッドシートのデータを簡単にドキュメントに転記し、整理・共有することができます。さらに、今回はA列からF列の内容をG列にまとめて表示するように関数を挿入しています。このように関数も併用することで、レポート作成やデータの文書化が効率的に行えるようになります。自動化スクリプトのカスタマイズにより、さまざまなニーズに応じた報告書作成プロセスを構築することが可能です。

【コピペ可】二つのGoogleカレンダーを片方に自動でコピーする

GASを使ったGoogleカレンダーアイキャッチ

はじめに

Googleカレンダーの仕事用とプライベート用を分けて管理している人も多いのではないでしょうか。そして、予定をご友人や恋人と共有したいという方もいらっしゃるのではないでしょうか。

しかし、仕事用のカレンダーはセキュリティの観点やモラルの観点から共有できないこともあります。また、プライベートカレンダーを共有したとしても仕事用の予定はそのアカウントには入っておらず、結局プライベート用のカレンダーに複製しないといけないことになります。

この記事では仕事用のカレンダーに追加・更新された予定を自動でプライベートカレンダーにコピーすることで違うアカウントのカレンダー情報を一言かすることができるようになります。それでは見ていきましょう。

完成はこちら(Google Apps Scriptに貼り付けてください)

function copyWorkEventsToPrivateCalendar() {
  // 仕事用カレンダーとプライベートカレンダーのIDを設定する
  var workCalendarId = '仕事用のメールアドレスを入力してください(コピー元)';
  var privateCalendarId = 'プライベートのメールアドレスを入力してください(コピー先)';

  var workCalendar = CalendarApp.getCalendarById(workCalendarId);
  var privateCalendar = CalendarApp.getCalendarById(privateCalendarId);

  if (workCalendar === null || privateCalendar === null) {
    console.error("Invalid calendar ID(s). Please check your calendar ID settings.");
    return;
  }

  // 仕事用カレンダーの予定を取得する
  var now = new Date();
  var futureDate = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000); // 30日後までの予定を取得
  var workEvents = workCalendar.getEvents(now, futureDate);

  // 仕事用カレンダーの予定をプライベートカレンダーに同期する
  for (var i = 0; i < workEvents.length; i++) {
    var workEvent = workEvents[i];
    var eventId = workEvent.getId();

    // プライベートカレンダーに同じIDの予定があるか確認する
    var privateEvents = privateCalendar.getEvents(workEvent.getStartTime(), workEvent.getEndTime());
    var privateEvent = null;
    

    for (var j = 0; j < privateEvents.length; j++) {
      if (privateEvents[j].getDescription() === eventId) {
        privateEvent = privateEvents[j];
        break;
      }
    }

    if (privateEvent === null) {
      // 新しい予定をプライベートカレンダーに追加する
      privateEvent = privateCalendar.createEvent(workEvent.getTitle(), workEvent.getStartTime(), workEvent.getEndTime());
      privateEvent.setDescription(eventId);
    } else {
      // 既存の予定を更新する
      privateEvent.setTitle(workEvent.getTitle());
      privateEvent.setTime(workEvent.getStartTime(), workEvent.getEndTime());
    }
  }

  // 変更前の予定を削除する
  var privateEvents = privateCalendar.getEvents(now, futureDate);
  for (var i = 0; i < privateEvents.length; i++) {
    var privateEvent = privateEvents[i];
    var eventId = privateEvent.getDescription();

    var workEvent = workCalendar.getEventById(eventId);
    if (workEvent !== null) {
      if (!(workEvent.getTitle() === privateEvent.getTitle() && workEvent.getStartTime().getTime() === privateEvent.getStartTime().getTime() && workEvent.getEndTime().getTime() === privateEvent.getEndTime().getTime())) {
        privateEvent.deleteEvent();
      }
    }
  }
 
}


function syncCalendars() {
  // 仕事用カレンダーとプライベートカレンダーのIDを設定する
  var workCalendarId = '仕事用のメールアドレスを入力してください(コピー元)';
  var privateCalendarId = 'プライベートのメールアドレスを入力してください(コピー先)';

  var workCalendar = CalendarApp.getCalendarById(workCalendarId);
  var privateCalendar = CalendarApp.getCalendarById(privateCalendarId);

  if (workCalendar === null || privateCalendar === null) {
    console.error("Invalid calendar ID(s). Please check your calendar ID settings.");
    return;
  }

  // 仕事用カレンダーの予定を取得する
  var now = new Date();
  var futureDate = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000); // 30日後までの予定を取得
  var workEvents = workCalendar.getEvents(now, futureDate);

  // 仕事用カレンダーの予定をプライベートカレンダーに同期する
  for (var i = 0; i < workEvents.length; i++) {
    var workEvent = workEvents[i];
    var eventId = workEvent.getId();

    // プライベートカレンダーに同じIDの予定があるか確認する
    var privateEvents = privateCalendar.getEvents(workEvent.getStartTime(), workEvent.getEndTime());
    var privateEvent = null;
    

    for (var j = 0; j < privateEvents.length; j++) {
      if (privateEvents[j].getDescription() === eventId) {
        privateEvent = privateEvents[j];
        break;
      }
    }

    if (privateEvent === null) {
      // 新しい予定をプライベートカレンダーに追加する
      privateEvent = privateCalendar.createEvent(workEvent.getTitle(), workEvent.getStartTime(), workEvent.getEndTime());
      privateEvent.setDescription(eventId);
    } else {
      // 既存の予定を更新する
      privateEvent.setTitle(workEvent.getTitle());
      privateEvent.setTime(workEvent.getStartTime(), workEvent.getEndTime());
    }
  }

  // 変更前の予定を削除する
  var privateEvents = privateCalendar.getEvents(now, futureDate);
  for (var i = 0; i < privateEvents.length; i++) {
    var privateEvent = privateEvents[i];
    var eventId = privateEvent.getDescription();

    var workEvent = workCalendar.getEventById(eventId);
    if (workEvent !== null) {
      if (!(workEvent.getTitle() === privateEvent.getTitle() && workEvent.getStartTime().getTime() === privateEvent.getStartTime().getTime() && workEvent.getEndTime().getTime() === privateEvent.getEndTime().getTime())) {
        privateEvent.deleteEvent();
      }
    }
  }
 
}

// トリガーを設定する関数
function createTrigger() {
  ScriptApp.newTrigger('syncCalendars')
    .timeBased()
    .everyMinutes(30) // 30分ごとに実行されるように設定
    .create();
}

手順について

ステップ1 カレンダーの共有を設定する

二つのアカウント別に設定する必要があります。

–仕事用カレンダー(コピーしたい予定が入っている)側の設定–

  1. Googleカレンダーを開きます。
  2. 右上の設定を開きます。
  3. 左のメニューバーの「マイカレンダーの設定」からコピーしたいカレンダーを選択して下さい。
  4. 「ユーザーやグループを追加」を選択して、プライベート用カレンダー(コピー先のカレンダー)のメールアドレスを入力して下さい。
「ユーザーやグループを追加」を選択
「ユーザーやグループを追加」を選択
プライベート用のカレンダーを招待
プライベート用のカレンダーを招待

–プライベート用カレンダー(コピー先)側の設定–

  1. Googleカレンダーを開きます。
  2. 右上の設定を開きます。
  3. 左のメニューバーの「カレンダーを追加」>「カレンダーに登録」を選択して下さい。
  4. 仕事用カレンダーのメールアドレスを入力してEnterを押して下さい。
「カレンダーを追加」>「カレンダーに登録」を選択
「カレンダーを追加」>「カレンダーに登録」を選択
仕事用カレンダーのメールアドレスを入力してEnter
仕事用カレンダーのメールアドレスを入力してEnter

ステップ2 コードを編集する

変更が必要な部分は「仕事用のメールアドレス」と「プライベート用のメールアドレス」の二つを変更する場所が計4カ所あります。メールアドレスを変更しておいて下さい。

また、コピーする期間はデフォルトで30日間になっています。基本的には30日間で問題がないと思います。もし変更したい場合copyWorkEventsToPrivateCalendar関数及びsyncCalendars関数「// 30日後までの予定を取得」の30の部分の数字を任意の数字に変更して下さい。
※基本的は自動化のプログラムを1時間毎に起動基本的には変更する必要はないかと思います。

var futureDate = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000);

ステップ3 Google Apps Scriptのエディターに貼り付ける

こちらを含めると記事が長くなります。Google Apps Scriptのプロジェクトの始め方はこちらの記事をご覧ください。

ステップ4 トリガーを設置する

トリガーとは

Google Apps Scriptのエディタ画面の左のメニューバーにある「トリガー」から設定します。トリガーとはいわば「いつ自動化のシステムを動かしますか」というものです。

トリガーには以下の種類があります。

  • 時間主導型
  • カレンダー更新型
  • フォーム更新型

今回は上二つの「時間主導型」と「カレンダー更新型」を使います。これらを使う理由は以下の通りです。

  • 時間主導型・・・仕事用カレンダーがプライベートカレンダーに追加されているのか定期的にチェックする。もし、仕事用カレンダーの予定がプライベートカレンダーに追加されていなければ追加する。
  • カレンダー更新型・・・仕事用のカレンダーのタイトルや時間が更新された時にプライベートカレンダーの予定も同様の処理をする。

追加の仕方

  1. 左のメニューバーの上から3つ目の「トリガー」を選択する。
  2. 右下の「トリガーを追加」をクリックする。
  3. copyWorkEventsToPrivateCalendar関数を選択。イベントソースを「時間主導型」トリガータイプを「時間ベースのタイマー」時間の間隔を「1時間おき」で設定する。(詳細は下に添付しております画像を参考にして下さい。)
  4. syncCalendars関数を選択してイベントソースを「カレンダーから」カレンダーの詳細を入力を「カレンダー更新済み」オーナーのメールアドレスに「仕事用のメールアドレス」を入力しておいて下さい。(詳細は下に添付しております画像を参考にして下さい。)
左の上から三つ目の「トリガー」を選択
左の上から三つ目の「トリガー」を選択
右下の「トリガーを追加」をクリック
右下の「トリガーを追加」をクリック
copyWorkEventsToPrivateCalendar関数
copyWorkEventsToPrivateCalendar関数
syncCalendars関数を選択して上記のように設定
syncCalendars関数を選択して上記のように設定

注意点

仕事用のカレンダーを削除した際に、プライベートカレンダーは削除されません。そのため、削除の対象を検索する際に「仕事用の予定のタイトル」と「時間」で検索する仕組みになっております。しかし、この精度がGASのみだと低く、他の予定も削除してしまう可能性があります。

仕事用のカレンダーを削除された際にはご自身でプライベート用のカレンダーにコピーされている予定を削除するようにお願い致します。

最後に

いかがだったでしょうか。

今回はGoogle Apps Scriptとカレンダーについてご紹介しました。ぜひ、プロジェクトを実際に作って見て下さい。GASを使ってNotionのコンテンツをLINEに流すなどもできますので、興味のある方はぜひご覧ください。

追記:2024年4月17日

現在、複数のカレンダーの情報を連携させるサービスがすでに作られています。

本記事の作成者である私西坂も現在はこちらを愛用しております。もし、GASがわかりにくいと言う方はぜひこちらも参考にしてください。