NotionAPI×GASでチェックボックスにチェックを入れた日時を記録する

# 概要

GASでNotionAPIを叩いてDBからチェックボックスにチェックが付けられたもの
の日時(タイムラグあり)を記録する方法を紹介します

:::note warn
どこかの記事にFormulaとして
prop("完了") ? formatDate(now(), "YYYY/MM/DD") : ""
を書けばいい、のような記述がありましたが、now()では当然常に現在時刻を取得するだけなのでそのような記述では実現できなかったので、GASでの実装になります。
:::

:::note warn
GASの時間主導型のトリガーを利用して「チェックされているもの」かつ「日付の入力が無いもの」を取得→更新とするため、その分のタイムラグは発生します。
:::

# 手順

## NotionAPIで使用するトークンの取得

下記記事や、公式ドキュメント等を参考にNotionAPIで使用するトークンの取得と、
更新を行いたいDBに対してインテグレーションの招待を行います。
https://qiita.com/g-iki/items/cc44fa7573dc3f6e39de


## コード

properties.gs

```javascript
/**
 * 手動実行で設定する
 */
function setScriptProps() {
  let scriptProperties = PropertiesService.getScriptProperties();
  Logger.log(PropertiesService.getScriptProperties().getProperties());
  scriptProperties.deleteAllProperties();

  scriptProperties.setProperties({
    'NOTION_VERSION': '2021-08-16',
    'NOTION_TOKEN': 'secret_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
  });

  // データベースとチェックボックスの列名、日付の列名を定義
  // データベースIDはデータベースのページを開き、URLから取得
  // https://www.notion.so/{userName}/{DatabaseID}?v={ViewID} となっているURLの{DatabaseID}の部分
  let checkItemList = JSON.stringify(
    [
      {
        // WebScrap既読日
        'DATABASE_ID': 'XXXXXXXXXXXXXXXXXXXXXX',
        'CHECKBOX_PROPERTY': 'Read',
        'DATE_PROPERTY': 'ReadDate'
      }
    ]
  );

  scriptProperties.setProperties({ 'CHECK_ITEM_LIST': checkItemList });
}
```

:::note info
上記コードは環境変数用設定用のコードで手動で実行します。
NOTION_TOKEN は取得したトークン
checkItemListで指定している
DATABASE_ID はデータベースのID
CHECKBOX_PROPERTY はチェックボックスプロパティ名
DATE_PROPERTY は日時を入力したいプロパティ名
を設定します。(配列として複数設定可能)
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127686/41b86823-1b7f-d281-7377-e97b66175323.png)

↑例えば自分の場合、WebScrapの既読日を記録したいので、
`CHECKBOX_PROPERTY`に`Read`、`DATE_PROPERTY`に`ReadDate`を指定しています。

updateCheckedTime.gs

```javascript
function UpdateCheckedTime() {
  const props = PropertiesService.getScriptProperties().getProperties();
  const strDate = Utilities.formatDate(new Date(), "JST", "yyyy-MM-dd");
  const list = JSON.parse(props['CHECK_ITEM_LIST']);

  for (let i = 0; i < list.length; i++) {
    const item = list[i];
    const dbID = item["DATABASE_ID"];
    const checkboxPropery = item["CHECKBOX_PROPERTY"];
    const dateProperty = item["DATE_PROPERTY"];
    // チェックボックス=true で日付が空のものを取得
    const getPayload = {
      "filter": {
        "and": [
          {
            "property": checkboxPropery,
            "checkbox": {
              "equals": true
            }
          },
          {
            "property": dateProperty,
            "date": {
              "is_empty": true
            }
          }
        ]
      }
    }

    const nonCheckedItemList = getDatabaseQuery(props, dbID, getPayload);
    Utilities.sleep(1000); // 平均3回/秒のリミット回避のためスリープ

    if (nonCheckedItemList.results.length === 0) {
      Logger.log("更新対象なし")
      continue;
    }

    // 更新処理
    nonCheckedItemList.results.forEach(t => {
      
      const updatePayload = {
        "properties": {
          [dateProperty]: {
            "date": {
              "start": strDate
            }
          }
        }
      };

      let pageId = t["id"]
      updatePage(props, pageId, updatePayload)
      Utilities.sleep(1000); // 平均3回/秒のリミット回避のためスリープ
    });
  }
}

/**
 * ページの内容を更新する
 * https://developers.notion.com/reference/patch-page
 */
function updatePage(props, pageId, payload) {
  let url = "https://api.notion.com/v1/pages/" + pageId
  let options = {
    "method" : "PATCH",
    "headers": {
      "Content-type": "application/json",
      "Authorization": "Bearer " + props.NOTION_TOKEN,
      "Notion-Version": props.NOTION_VERSION,
    },
    "payload": JSON.stringify(payload)
  };

  try {
    let res = UrlFetchApp.fetch(url, options);
    Logger.log(res);
    return JSON.parse(res);
  } catch (e) {
    Logger.log(e);
    return undefined;
  }

}

/**
 * DBから指定条件に合うものを取得する
 * https://developers.notion.com/reference/post-database-query
 */
function getDatabaseQuery(props, databaseId, payload) {
  let url = "https://api.notion.com/v1/databases/" + databaseId + "/query"
  let options = {
    "method" : "POST",
    "headers": {
      "Content-type": "application/json",
      "Authorization": "Bearer " + props.NOTION_TOKEN,
      "Notion-Version": props.NOTION_VERSION,
    },
    "payload" : JSON.stringify(payload),
    "muteHttpExceptions" : true
  }

  try {
    let res = UrlFetchApp.fetch(url, options);
    Logger.log(res);
    return JSON.parse(res);
  } catch (e) {
    Logger.log(e);
    return undefined;
  }
}
```

## 動作確認

環境変数設定用の関数に必要項目を入力後、`setScriptProps()`を実行して環境変数を設定します。

その後、チェックボックスの状態監視、日付の入力を行いたいDBに
チェックボックスをTrueにしたページを用意して、`UpdateCheckedTime()`関数を実行し、
日付の更新が行われることを確認します。

## GASの定期実行設定

`UpdateCheckedTime()`関数を時間主導型で実行するように設定します。
頻繁に更新を行いたい場合は1分や5分、1日に1回更新すればいい程度なら日付ベースのタイマーで適当な時間を指定してあげればOKです


# おわり

自分はこれを活用して、NotionSaverで後で読むように保存していた記事を読み終わったらチェックをつけて、読んだ日付は自動で入力されるように仕込んでいます。

https://chrome.google.com/webstore/detail/save-to-notion/ldmmifpegigmeammaeckplhnjbbpccmm

他にも課題管理の完了チェック＋完了日の記録に使ったりできると思います。

---

役に立ったらモチベーションに繋がるのでLGTMボタンを押して頂けると幸いです。