Scriptone

Automatically Register Completed TickTick Tasks to Notion

Table of Contents

Build a system to automatically register tasks completed in TickTick to a Notion database.

Overview

Notion allows for all-in-one information management, including note creation, Kanban-style management, and database building. While it offers benefits like building GUI flows with databases or stocking information, sometimes using tools specialized for specific purposes provides higher convenience. In this case, we will manage ToDo with TickTick and create a function to accumulate completed tasks in Notion for aggregation and review.

What to Do

We will accumulate tasks completed in TickTick into a Notion database. The general flow is as follows.

  • Complete a task in TickTick
  • Detect task completion with IFTTT and send a Webhook (POST) to GAS
  • In GAS, handle Notion authentication and value conversion, then send a Webhook (POST)
  • Insert the completed task into the Notion database

Tools like IFTTT are convenient for triggering events. However, since IFTTT makes it difficult to add authentication information to Webhook headers or convert values, we will use GAS to fill this gap.

Preparing to Use Notion API

Refer to Integrations > Steps to prepare the Notion side for API usage.

Database Construction

The information that can be carried via Webhook from TickTick through IFTTT is as above. Since there are no current disadvantages to retaining all data, we will create a database (table) in Notion to store this information.

Set date-related columns to Date type, Tag to MultiSelect type, and all others to String type. Once database construction is complete, go to the page's top-right Share menu, invite the created Integration, and enable API access.

[FYI] https://www.notion.so/mikohei/TicktickToNotion-025088cb072748fbba1a1586346d108d#4377d6f1fc2b4efbad100d0b54c4a06f

GAS Construction

HTTP-POST Testing

[FYI] Memo on Parameters for Receiving IFTTT Webhook via GAS POST

Referring to the above, write sample code to receive the Webhook from IFTTT. Open the Google Apps Script Editor and input the following code.

function doPost(e) {
  var jsonString = e.postData.getDataAsString()
  const options = {name: "webhookTest"}
  GmailApp.sendEmail("yourAlias@gmail.com", "webhookTest", jsonString, options)
}

Replace yourAlias in yourAlias@gmail.com with your Google account ID or any desired address. To test receiving POST, deploy the API.

  • Click [Deploy]
  • Click [New deployment]
  • Click [Select type]
  • Click [Web app]
  • Fill in the form:
    • Write a description (e.g., Receive a webhook from IFTTT)
    • Select yourself for "Execute as"
    • Set "Who has access" to "Anyone"
  • Click [Deploy]
  • Copy the web app URL and paste it into a notepad or similar

Once the steps are complete, use tools like ARC or IFTTT to send an HTTP-POST to the copied URL. The request content of the POST will be sent to the email address you set. If it doesn't work, check the deployment settings or code for errors.

Library Introduction

Introduce the library to operate the Notion API. The source code is as follows.

Error handling has been omitted, but it was written based on notion-sdk-py and the official documentation.

There might be individuals who have adapted the official library for GAS, so before using notion-sdk-gs, check for reusable resources. Also, since the API might be updated to include more features, please refer to the official documentation as much as possible.

Below are the steps to introduce notion-sdk-gs.

  • From the GAS Editor's left side menu, click [Library]
  • Enter the following in the script ID:
    • 1C6kLuU1Ugclg-8C7hsbK221IgepJoGTrbh2VI4itdExvyFDCzc4adK8h
  • Click [Look up]
  • Enter "Notion" in the ID field
  • Click [Add]

Once added, "Notion" will appear in the side menu libraries. If something other than "Notion" is displayed, click it and correct the ID to "Notion".

Registering Notion Token

Execute the following code to register the Notion Token in the GAS project. Replace {{yourToken}} with the Token obtained in "Preparing to Use Notion API" and run setNotionToken. After execution, run readProp to confirm that the Token is displayed.

function setNotionToken() {
  PropertiesService.getScriptProperties().setProperty("NOTION_TOKEN", "{{yourToken}}")
}
function readProp() {
  var props = PropertiesService.getScriptProperties()
  console.log(props.getProperty("NOTION_TOKEN"))
}

If the Token is registered and output correctly, you can delete the above code.

Analyzing the Database

https://www.notion.so/1349f9e927674a03a87d772483dd5b1b?v=05282e02290749fd95decb0df0dc3f5c&p=de3635356a224c569f103a2038dc3521
                      |--------- DatabaseID ---------|                                      |--------- Page ID ------------|

Opening a database record (page) allows you to obtain the PageID. Use the PageID to acquire the database structure.

function dispoGetDbPgStructure(client, pgId){
  const ret = client.pages.retrieve(pgId)
  console.log(JSON.stringify(ret))
}
function testMain() {
  var props = PropertiesService.getScriptProperties()
  const notion = new Notion.client(props.getProperty("NOTION_TOKEN"))
  dispoGetDbPgStructure(notion, "8aa1faecaba847f488f277a4b711a42e")
}

If the PageID is set correctly, you will get the database row information in JSON. The properties in the response contain the column names. Under each column name, there is information like text content, color, and text decoration. For simple data insertion, you can update by using only the necessary information, such as text, so extract only the required parts from the JSON. Use the extracted information as a reference to convert TickTick's JSON to Notion's format.

Date Format Conversion

In TickTick, date-time information is expressed as July 4 2021 at 01:13PM, and date information as July 4 2021. In Notion, these are expressed as 2021-07-04T13:13:00.000+09:00 and 2021-07-04, respectively. Convert the date format from TickTick to Notion accordingly.

function  _isDatetime(dtString) {
  return (dtString.indexOf("AM") >= 0 || dtString.indexOf("PM") >= 0)
}
function  _rmDtNoise(dtString) {
  var  rmAt = dtString.replace(" at ", " ")
  var  blAM = rmAt.replace("AM", " AM")
  var  blPM = blAM.replace("PM", " PM")
  return  blPM
}
function  _formatDate(dt, isDt, timezone="+09:00", sep="-") {
  var  year = dt.getFullYear()
  var  month = ("00" + (dt.getMonth()+1)).slice(-2)
  var  day = ("00" + dt.getDate()).slice(-2)
  var  date = `${year}${sep}${month}${sep}${day}`
  if (!isDt) {
    return  date;
  }
  var  hour = ("00" + (dt.getHours())).slice(-2)
  var  min = ("00" + (dt.getMinutes())).slice(-2)
  var  datetime = `${date}T${hour}:${min}:00.000${timezone}`
  return  datetime
}
function  dtFormatter(dtString) {
  var  fmtDtString = _rmDtNoise(dtString)
  var  isDT = _isDatetime(fmtDtString)
  var  dtParse = Date.parse(fmtDtString)
  var  dt = new  Date(dtParse)
  return _formatDate(dt, isDT)
}

To branch processing based on whether it includes date-time information, the _isDatetime function checks for date-time inclusion. The _rmDtNoise function removes at and inserts a half-width space before AM/PM using replace.

Date.parse acquires the numerical information of the date and passes it to a Date object to create a Date-type object. The _formatDate function converts from Date type to String type in Notion's format.

Execute the above series of processes with the dtFormatter function. The following is code to convert the date format using dtFormatter. This is for testing, so you can delete it after execution.

function dispoTest(){
  var ret1 = dtFormatter("July 4 2021 at 01:13PM")
  console.log(ret1) // 2021-07-04T13:13:00.000+09:00
  var ret2 = dtFormatter("July 4 2021")
  console.log(ret2) // 2021-07-04
}

Tag (MultiSelect) Conversion

The simple structure of Notion's MultiSelect type is as follows.

{
  "Tag": {
    "multi_select": [
        {"name": "Private",},
        {"name": "Work", }
      ]
    }
  }
}

TickTick's Tag information is in the format #{tagName}. If there are multiple tags, they are written separated by half-width spaces. Due to this specification, #{tagName} cannot include # or half-width spaces. We will write a function to create multi_select values using this specification.

function genMultiSel(multiSelStr) {
  var ret = []
  var selList = multiSelStr.split(" ")
  selList.forEach((tag) => {
    if (tag.length > 0) {
      var rmSharp = tag.replace("#", "")
      ret.push({name: rmSharp})
    }
  })
  return ret
}

The following is test code.

function test() {
  var ret = genMultiSel("#test1 #test2")
  console.log(ret)
  // [ { name: 'test1' }, { name: 'test2' } ]
}

Generating the Body (JSON)

Create JSON for POSTing to Notion.

function _addDate(params, dateStrings, key, value) {
  if (dateStrings.length > 0) {
    params["properties"][key] = { "date": { "start": value } }
  }
  return params
}
function genParams(data, databaseId) {
  var params = {
    "parent": { "database_id": databaseId },
    "properties": {
        "TaskName": {
            "title": [{ "text": { "content": data.TaskName } }]
        },
        "TaskContent": {
            "rich_text": [{ "text": { "content": data.TaskContent } }]
        },
        "List": {
            "rich_text": [{ "text": { "content": data.List } }]
        },
        "Priority": {
            "rich_text": [{ "text": { "content": data.Priority } }]
        },
        "LinkToTask": {
            "rich_text": [{ "text": { "content": data.LinkToTask } }]
        },
        "CreatedAt": { "date": { "start": dtFormatter(data.CreatedAt) } }
    }
  }
  var tags = genMultiSel(data.Tag)
  if (tags.length > 0) {
    params["properties"]["Tag"] = {"multi_select": tags}
  }
  params = _addDate(params, data.StartDate.length, "StartDate", dtFormatter(data.StartDate))
  params = _addDate(params, data.EndDate.length, "EndDate", dtFormatter(data.EndDate))
  return params
}

Specify the database using DatabaseID as the destination for data insertion. Set properties according to the database type. For MultiSelect, add values later based on the presence of tags. This completes the conversion process from TickTick to Notion.

Deploy doPost

Reflect the above processing in doPost.

function doPost(e) {
  // Init
  var props = PropertiesService.getScriptProperties();
  const notion = new Notion.client(props.getProperty("NOTION_TOKEN"));
  var jsonString = e.postData.getDataAsString();
  var data = JSON.parse(jsonString);

// Generate a Parameter const params = genParams(data, “cf0bc59e33734173838e0370c210fa3d”)

// Create a record var ret = notion.pages.create(params) if (ret[“status”] >= 300 && ret[“status”] < 200 ) { const options = {name: “IFTTT Webhooks Error”}; GmailApp.sendEmail(yourAlias@gmail.com, “webhookTest”, JSON.stringify(ret), options); } }

Perform the deployment again to reflect the changes. After deployment, a new URL will be issued, so use that URL to confirm if records are added to the Notion database. If there are error emails or messages, debug according to the message.

IFTTT Settings

Finally, set IFTTT to call the GAS API. For the Trigger, select TickTick's New completed task. In New completed task, you can select the target List, Tag, or Priority. Since Notion's database allows for aggregation and filtering, set it as follows to stock all tasks.

Item

Value

List

All Lists

Tag

Please Select(Default)

Priority

Please Select(Default)

Click [Create trigger]. For Then That, select Webhook and set as follows.

Item

Value

URL

Deployed GAS API URL

Method

POST

Content Type

application/json

Body

See below

[Body]

{
    "TaskName": "{{TaskName}}",
    "TaskContent": "{{TaskContent}}",
    "CompleteDate": "{{CompleteDate}}",
    "StartDate": "{{StartDate}}",
    "EndDate": "{{EndDate}}",
    "List": "{{List}}",
    "Priority": "{{Priority}}",
    "Tag": "{{Tag}}",
    "LinkToTask": "{{LinkToTask}}",
    "CreatedAt": "{{CreatedAt}}"
}

Click [Create action].

Operation Confirmation

Create a task in TickTick and set it to completed. If the completed task is registered in Notion, it is operating normally. Note that there may be a lag of a few minutes until the event fires in IFTTT, so check the operation status via IFTTT's "View activity", error emails, or the Notion database.

Summary

We have now made it possible to call the Notion API from GAS or IFTTT. This allows for more flexible data stocking using time triggers or event triggers with Notion. It can be widely used for tasks, habits, health management, analysis, etc., so feel free to adapt it to your own purposes.