TickTick에서 완료된 작업을 Notion에 자동 등록하기

Table of Contents
TickTick에서 완료된 작업을 Notion의 데이터베이스에 자동으로 등록하는 메커니즘을 구축합니다.
개요
Notion은 노트 작성, 칸반 관리, 데이터베이스 구축 등 모든 것을 하나의 플랫폼에서 정보를 관리할 수 있습니다. 데이터베이스를 사용한 GUI로 흐름을 구축하거나 정보를 저장할 수 있는 점이 장점인 반면, 특정 목적에 특화된 도구를 사용하는 것이 더 높은 편의성을 제공할 때도 있습니다.
이번에는 ToDo 관리를 TickTick으로 하면서, 완료된 작업을 Notion에 축적하여 집계로 되돌아볼 수 있도록 기능을 만듭니다.
할 일
TickTick에서 완료된 작업을 Notion의 데이터베이스에 축적합니다. 대략적인 흐름은 다음과 같습니다.
- TickTick에서 작업을 완료합니다.
- IFTTT로 작업 완료를 감지하여 GAS로 Webhook(POST)합니다.
- GAS에서 Notion의 인증과 값 변환을 한 후 Webhook(POST)합니다.
- Notion의 데이터베이스에 완료된 작업을 삽입합니다.
이벤트 발화를 위해 IFTTT와 같은 웹 서비스를 연결하는 도구가 편리합니다. IFTTT만으로는 Webhook의 헤더에 인증 정보를 추가하거나 값을 변환하는 것이 어렵기 때문에, 이 격차를 GAS로 메웁니다.
Notion API 준비
Integrations>절차를 참고하여 Notion 측에서 API를 사용할 수 있도록 준비합니다.
데이터베이스 구축
TICKTick에서 IFTTT로 Webhook에 실릴 수 있는 정보는 위와 같습니다. 모든 데이터를 유지하는 데 현재는 단점이 없으므로, 위 정보를 저장할 수 있는 데이터베이스(테이블)를 Notion에 만듭니다.
날짜 관련 열은 Date 형식, Tag는 MultiSelect 형식, 나머지는 String 형식으로 설정합니다. 데이터베이스 구축이 완료되면, 페이지 오른쪽 상단의 Share에서 생성한 Integrations를 초대하여 API를 사용할 수 있도록 해주세요.
GAS 구축
HTTP-POST 테스트
[FYI]IFTTT의 Webhook을 GAS의 POST로 받을 때의 파라미터 메모
위를 참고하여 IFTTT에서 Webhook을 받는 코드를 테스트로 작성합니다. Google Apps Script의 Editor를 열고, 다음 코드를 입력합니다.
function doPost(e) {
var jsonString = e.postData.getDataAsString()
const options = {name: "webhookTest"}
GmailApp.sendEmail("yourAlias@gmail.com", "webhookTest", jsonString, options)
}
yourAlias@gmail.com
의 yourAlias
는 사용 중인 Google 계정의 ID 등으로, 임의의 주소로 변경합니다. doPost
함수로 POST를 테스트로 받기 위해, API를 배포합니다.
- [배포]를 클릭
- [새 배포]를 클릭
- [유형 선택]를 클릭
- [웹 앱]를 클릭
- 폼에 입력
- 설명문을 작성
- e.g. Receive a webhook from IFTTT
- 다음 사용자로서 실행에서 자신을 선택
- 액세스할 수 있는 사용자를 “모두”로 함
- 설명문을 작성
- [배포]를 클릭
- 웹 앱의 URL을 복사하여 메모장 등에 붙여넣기
절차가 완료되면, ARC나 IFTTT 등을 사용하여 복사한 URL에 HTTP-POST를 보내 보세요. POST의 Request 내용이 위에서 설정한 이메일 주소로 전송됩니다. 실패할 경우, 배포 설정이나 코드 오류 등을 확인하세요.
라이브러리 도입
Notion API를 조작하는 라이브러리를 도입합니다. 소스 코드는 다음과 같습니다.
Error 처리 등은 생략했지만, notion-sdk-py와 공식 문서를 참고하여 작성한 것입니다.
공식 라이브러리를 GAS용으로 재작성한 분도 있을 수 있으니, notion-sdk-gs
를 사용하기 전에 재사용 가능한 리소스가 없는지 확인하세요. 또한, API의 업데이트로 기능이 강화될 가능성도 있으므로, 공식 문서도 최대한 보세요.
아래는 notion-sdk-gs
의 도입 절차입니다.
- GAS Editor의 왼쪽 사이드 메뉴에서 [라이브러리]를 클릭
- 스크립트 ID에 다음을 입력
- 1C6kLuU1Ugclg-8C7hsbK221IgepJoGTrbh2VI4itdExvyFDCzc4adK8h
- [검색]을 클릭
- ID에 “Notion”을 입력
- [추가]를 클릭
추가가 완료되면 사이드 메뉴의 라이브러리에 “Notion”이 추가됩니다. “Notion” 이외의 표시가 되어 있으면, 그것을 클릭하여 ID를 “Notion”으로 수정하세요.
Notion의 Token 등록
다음 코드를 실행하여 Notion의 Token을 GAS의 프로젝트에 등록합니다. {{yourToken}}
을 “Notion API 준비”에서 얻은 Token으로 변경하여 setNotionToken
을 실행하세요. 실행 후, readProp
을 실행하여 Token이 표시되는지 확인합니다.
function setNotionToken() {
PropertiesService.getScriptProperties().setProperty("NOTION_TOKEN", "{{yourToken}}")
}
function readProp() {
var props = PropertiesService.getScriptProperties()
console.log(props.getProperty("NOTION_TOKEN"))
}
Token이 등록되고 올바르게 출력되면 위 코드는 삭제해도 됩니다.
데이터베이스를 분석하기
https://www.notion.so/1349f9e927674a03a87d772483dd5b1b?v=05282e02290749fd95decb0df0dc3f5c&p=de3635356a224c569f103a2038dc3521
|--------- DatabaseID ---------| |--------- Page ID ------------|
데이터베이스의 레코드(페이지)를 열면 PageID를 얻을 수 있습니다. PageID를 사용하여 데이터베이스의 구조를 얻습니다.
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")
}
PageID가 올바르게 설정되어 있으면, JSON으로 데이터베이스의 행 정보를 얻을 수 있습니다. Response의 properties
에 각 열 이름이 있습니다. 열 이름 아래에는 텍스트 내용, 색상, 텍스트 장식 등의 정보가 있습니다.
단순히 데이터를 삽입하는 경우, 텍스트 등 필요한 정보만 사용하여 업데이트할 수 있으므로, JSON에서 필요한 부분만 추출합니다. 추출한 정보를 참고하여, TickTick의 JSON을 Notion용 형식으로 변환하는 작업을 합니다.
날짜 형식 변환
TICKTick에서는 날짜/시간 정보가 July 4 2021 at 01:13PM
형식으로, 날짜 정보는 July 4 2021
으로 표현합니다. Notion에서는 각각 2021-07-04T13:13:00.000+09:00
, 2021-07-04
로 표현됩니다. 이에 맞게 TickTick → Notion으로 날짜 형식을 변환합니다.
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)
}
시간 정보를 포함하는지 여부에 따라 처리를 분기시키기 위해, _isDatetime
함수로 날짜/시간 정보를 포함하는지 판단합니다. _rmDtNoise
함수에서는 at
등을 제거하면서, 시간 정보가 있으면 AM/PM 앞에 반각 스페이스를 replace를 사용하여 삽입합니다.
Data.parase
로 날짜의 숫자 정보를 얻어, Data
객체에 숫자를 전달하여 Date 형식의 객체를 만듭니다. _formatDate
함수로 Date 형식 → String 형식으로 변환하여 Notion용 형식으로 만듭니다.
위의 일련의 처리를 dtFormatter
함수로 실행합니다. 아래는 dtFormatter
로 날짜 형식을 변환하는 코드입니다. 테스트용이므로 실행 후 삭제해도 됩니다.
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) 변환
Notion의 MultiSelect 형식 구조를 간단히 표현하면 다음과 같습니다.
{
"Tag": {
"multi_select": [
{"name": "Private",},
{"name": "Work", }
]
}
}
TICKTick 측의 Tag 정보는 #{tagName}
형식입니다. 여러 태그가 있는 경우에는 반각 스페이스로 구분하여 태그를 여러 개 표기하는 방식입니다. 이 사양 때문에 {tagName}
에 #
이나 반각 스페이스를 포함할 수 없는 사양입니다. 이 사양을 사용하여 multi_select
의 값을 만드는 함수를 작성합니다.
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
}
아래는 테스트용 코드입니다.
function test() {
var ret = genMultiSel("#test1 #test2")
console.log(ret)
// [ { name: 'test1' }, { name: 'test2' } ]
}
Body(JSON) 생성
Notion에 POST하기 위한 JSON을 만듭니다.
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
}
DatabaseID를 사용하여 데이터를 흘려 넣을 대상 데이터베이스를 식별합니다. 데이터베이스의 형식에 맞게 properties
를 설정합니다. MultiSelect는 빈 문자열의 경우도 있으므로, 태그의 유무에 따라 나중에 값을 추가하는 처리를 하고 있습니다. 이로써 TickTick → Notion의 변환 처리는 완료입니다.
doPost 배포하기
위 처리를 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);
}
}
변경을 반영하기 위해 다시 배포 작업을 해주세요. 작업 후, 새로운 URL이 발급되므로, 그 URL을 사용하여 Notion의 데이터베이스에 레코드가 추가되는지 확인합니다. 에러 메일이나 에러 메시지가 있으면 메시지에 따라 디버그하세요.
IFTTT 설정
마지막으로, IFTTT에서 GAS의 API를 호출하도록 설정합니다. Trigger에는 TickTick의 New completed task
를 선택합니다. New completed task에서는 대상 List, Tag, Priority를 선택할 수 있습니다. Notion의 데이터베이스에서 집계나 필터링이 가능하므로, 여기서는 다음과 같이 설정하여 모든 작업을 저장하는 설정으로 합니다.
항목 | 값 |
---|---|
List | All Lists |
Tag | Please Select(기본) |
Priority | Please Select(기본) |
설정한 [Create trigger]를 클릭합니다. Then That에는 Webhook을 선택하고 다음 설정을 합니다.
항목 | 값 |
---|---|
URL | 배포한 GAS API의 URL |
Method | POST |
Content Type | application/json |
Body | 아래 참조 |
[Body]
{
“TaskName”: “{{TaskName}}”,
“TaskContent”: “{{TaskContent}}”,
“CompleteDate”: “{{CompleteDate}}”,
“StartDate”: “{{StartDate}}”,
“EndDate”: “{{EndDate}}”,
“List”: “{{List}}”,
“Priority”: “{{Priority}}”,
“Tag”: “{{Tag}}”,
“LinkToTask”: “{{LinkToTask}}”,
“CreatedAt”: “{{CreatedAt}}”
}
설정이 완료되면, [Create action]를 클릭합니다.
동작 확인
TICKTick에서 작업을 만들고, 작업을 완료 상태로 합니다. 작업 완료와 함께 Notion에 완료된 작업이 등록되면 정상 동작입니다. IFTTT에서 이벤트가 발화하는 데 몇 분의 지연이 있을 수 있으므로, IFTTT의 ‘View activity’나 에러 메일, Notion의 데이터베이스 등으로 동작 상황을 확인하세요.
요약
GAS나 IFTTT에서 Notion API를 호출할 수 있게 되었습니다. 이로 인해, 시간 트리거나 이벤트 트리거로 Notion을 사용할 수 있게 되어, 더 유연하게 데이터를 저장할 수 있습니다. 작업, 습관, 건강 관리, 분석 등 광범위하게 활용할 수 있으니, 자신의 용도에 맞게 자유롭게 사용하세요.