Notion 繰り返しイベント作成を Google Spreadsheet で管理する : Notion 解説(21)

はじめに

土曜日に書いた「定期的に Notion ページを作成する : Notion 解説(19) - hkob’s blog」によって、毎朝「雑務・振り返り」タスクが Notion に自動登録されるようになりました。

実は、繰り返しタスクについては、まいにち Notion No.7 を参考にして、「リマインダの作成: Notion 解説(7) - hkob’s blog」において、リマインダテーブルという形で LaunchPad に表示されるようになっています。ただ、リマインダはだたのリマインダであって、実際にタスクにしたければ、別途タスクテーブルにタスクを作成する必要がありました。

せっかくなので、この部分も自動タスク作成にしてみようと思いました。Yuka さんの基準日からのインターバルというアイディアだけでなく、指定月、指定週、指定日なども設定できるように拡張しています。

準備

スプレッドシートの作成

作成したスプレッドシートはこんな感じです。各行が表示される条件になります。複数の条件がある場合には AND 条件になっています。各列の説明をします。

  • 雑務・振り返り: インターバルが1なので毎日表示されます。
  • 燃えるゴミの日、資源ゴミの日、週報: 週ごとなのでインターバルが 7 になっています。基準日を変えることで特定の週に設定できます。OR は書けないので複数行に同じものを書いてください。
  • 燃えないゴミの日: 月の第2・第4火曜日なので、インターバルだけでなく、特定の週を設定しています。これも OR は書けないので 2 行書いています。
  • 月報: 月末に表示したいので最終日を指定します。ただし最終日は月毎に異なるので、-1 で指定します(-2 だと月末の前日)。正の値の場合には日付が一致すると表示します。
  • 誕生日: 月と日の両方を設定します。1年に1回だけ表示されることになります。

f:id:hkob:20210517222117p:plain
作成したスプレッドシート

スクリプトの修正

GAS のスクリプトは以下のように変更しました。中身はこれまでとほとんど変わりません。 (6/13修正: getMonth() の戻り値は0〜11でした。一月後に誕生日と言われて気づきました。以下のスクリプトは修正しています)

function readCalendar() {
  let calendarId = "この部分にカレンダーIDを書きます。";
  let token = getSyncToken(calendarId);
  Logger.log(token);
}

// Notion に payload をポストして登録
function postNotion(payload){
  let MY_NOTION_TOKEN = "この部分にIntegration Token を書きます。";
  let url = "https://api.notion.com/v1/pages"; // API URL
  let options = {
    "method" : "POST",
    "headers": {
      "Content-type": "application/json",
      "Authorization": "Bearer " + MY_NOTION_TOKEN,
      "Notion-Version": "2021-05-13",
    },
    "payload" : JSON.stringify(payload)
  };
  Logger.log(options);
  UrlFetchApp.fetch(url, options);
}

// title と date_hash から payload を作成
function createPayload(title, date_hash) {
  let DATABASE_ID = "この部分にDatabase ID を書きます";
  return {
    "parent": {
      "database_id": DATABASE_ID
    },
    "properties": {
      "タスク名": {
        "title": [
          {
            "text": {
              "content": title
            }
          }
        ]
      },
      "日付": {
        "type": "date",
        "date": date_hash
      },
      "非ポモ?": {
        "type": "checkbox",
        "checkbox": true
      }
    }
  }
}

// カレンダーイベントが変更されたら呼ばれるメソッド
function doCalendarPost(event) {
  let calendarId = event.calendarId; // カレンダーIDのの取得
  let token = getSyncToken(calendarId); // 前回実行時に取得したカレンダーTokenの取得
  let events = Calendar.Events.list(calendarId, {'syncToken': token}); // Token から後のカレンダーを取得
  let filteredItems = events.items.filter(e => {return e.status == "confirmed"}); // ステータスを見て登録、もしくは更新の予定のみにフィルタリング

  filteredItems.map(e => {return eventToPayload(e)}).filter(e => {return e}).forEach(p => {
    postNotion(p);
  });
  saveSyncToken(events.nextSyncToken); // 今回のTokenを保存する(次回のScript実行時に利用)
}

// 前回保存したカレンダーのSyncTokenを取り出す、前回保存分が無い場合は今回のSyncTokenを利用する
function getSyncToken(calendarId){
  var token = PropertiesService.getScriptProperties().getProperty('SYNC_TOKEN');
  if (token) {
    return token;
  }
  let events = Calendar.Events.list(calendarId, {'timeMin': (new Date()).toISOString()});
  return events.nextSyncToken;
}

// カレンダーイベントから payload を作成 (削除時にも呼ばれるので、イベントに日付が設定されていなければ null を返却)
function eventToPayload(e) {
  let hash = null;
  if ('dateTime' in e.start){
    hash = {
      "start": e.start.dateTime.replace("T", " "),
      "end": e.end.dateTime.replace("T", " ")
    }
  }
  if ('date' in e.start) {
    hash = {
      "start": e.start.date
    }
  }
  return hash ? createPayload(e.summary, hash) : null;
}

// SyncTokenをプロパティに保存する
function saveSyncToken(token){
  PropertiesService.getScriptProperties().setProperty('SYNC_TOKEN', token);
}

function weekToStr(date) {
  let w = Utilities.formatDate(date, "JST", "u");
  return ["日", "月", "火", "水", "木", "金", "土", "日"][w]
}

// date の日付がテーブルの行ごとに当てはまるか確認し、当てはまれば Notion にポスト
// デバッグテスト用に date を変えながらテストしていた
function selectEvents(date) {
  var sheet = SpreadsheetApp.getActiveSheet();
  var lastrow = sheet.getLastRow();
  for (let row = 1; row < lastrow; row++) {
    let use = false;
    let lines = sheet.getRange("A"+(row+1)+":G"+(row+1)).getValues()[0];
    if (isValidEvent(lines, date)) {
      let title = lines[0] + ((lines[1] && lines[1].toFixed() == 1) ? Utilities.formatDate(date, "JST", " M/d (" + weekToStr(date) + ")") : "");
      let date_hash = {"start": Utilities.formatDate(date, "JST", "yyyy-MM-dd")};
      postNotion(createPayload(title, date_hash));
    }
  }
}

// lines の繰り返しイベント情報を元に date が当てはまる時に true を返す
// and 条件なので、一つでも条件に満足しない時に false を返す (最後まで引っ掛からなければ true)
function isValidEvent(lines, date) {
  let aDay = 86400000;
  // 基準日とインターバルが設定されている時、インターバルの倍数の日以外なら false
  let baseDate = lines[2];
  let interval = lines[3] && lines[3].toFixed();
  if (baseDate && interval > 0) {
    let diff = ((date - baseDate) / aDay).toFixed();
    if (diff % interval != 0) {
      return false;
    }
  }
  // 月が設定されている時、月が一致しなければ false
  let month = lines[4] && lines[4].toFixed();
  if (month > 0 && month != date.getMonth()+1) {
    return false;
  }
  let day = lines[6] && lines[6].toFixed();
  // 日(day)が設定されていて day > 0 なら (day - 1)日前が 1 日でなければ false
  if (day > 0 && new Date(date.getYear(), date.getMonth(), date.getDate() - (day - 1)).getDate().toFixed() != 1) {
    return false;
  }
  // 日(day)が設定されていて day < 0 なら |day|日後が 1 日でなければ false
  if (day < 0 && new Date(date.getYear(), date.getMonth(), date.getDate() - day).getDate().toFixed() != 1) {
    return false;
  }
  // 週(week)が設定されていて、(date.day - 1) / 7 == week - 1 でなければ false
  let week = lines[5] && lines[5].toFixed();
  if (week > 0 && ((date.getDate() - 1) / 7).toFixed() != (week - 1).toFixed()) {
    return false;
  }
  return true;
}

// これまで通り、毎朝呼ばれる関数
function doEverydayPost() {
  selectEvents(new Date());
}

実行

とりあえずテストでコマンドを実行したが、月曜日なので今朝と同じものしか登録されなかった。水曜日に燃えるゴミの日が表示されれば成功かな。

5/18 追記

115 行目に以下の行を追加し、ログを残すようにしてみました。

      sheet.getRange("I" + (row+1)).setValue(date);

こんな感じで最後に登録した日が Spreadsheet に残るようになります。

f:id:hkob:20210518063107p:plain
最終登録日を記入


はてなブログに書いた Notion 記事一覧