Skip to content

Instantly share code, notes, and snippets.

@gauchy
Last active July 16, 2024 12:18
Show Gist options
  • Save gauchy/3866fa059eecd2081ec92bea07a4a29f to your computer and use it in GitHub Desktop.
Save gauchy/3866fa059eecd2081ec92bea07a4a29f to your computer and use it in GitHub Desktop.
Notion to Habitica Sync tool
import requests, json
#Notion's token and databaseId
token = 'XXX'
databaseId = 'XXX'
#Notion's headers
headers = {
"Authorization": "Bearer " + token,
"Content-Type": "application/json",
"Notion-Version": "2021-05-13"
}
#Habitica's headers
headersHabitica = {
"x-api-user": "XXX",
"x-api-key": "XXX"
}
#Completed Status
def completed():
return 'Completed';
#Prefix to the task when created in Habitica
def prefixChar():
return "N2H_"
def readDatabaseOfNotion(databaseId, headers):
readUrl = f"https://api.notion.com/v1/databases/{databaseId}/query"
res = requests.request("POST", readUrl, headers=headers)
data = res.json()
print(res.status_code)
# print(res.text)
with open('./notion.json', 'w', encoding='utf8') as f:
json.dump(data, f, ensure_ascii=False)
def readHabiticaData(headersHabitica):
url = "https://habitica.com/api/v3/tasks/user?type=todos"
res = requests.request("GET", url, headers=headersHabitica)
data = res.json()
print(res.status_code)
# print(res.text)
with open('./habitica.json', 'w', encoding='utf8') as f:
json.dump(data, f, ensure_ascii=False)
def readHabiticaDoneData(headersHabitica):
url = "https://habitica.com/api/v3/tasks/user?type=completedTodos"
res = requests.request("GET", url, headers=headersHabitica)
data = res.json()
print(res.status_code)
# print(res.text)
with open('./habitica_done.json', 'w', encoding='utf8') as f:
json.dump(data, f, ensure_ascii=False)
def createTodoInHabitica(name, headersHabitica):
name = prefixN2H(name)
url = "https://habitica.com/api/v3/tasks/user"
res = requests.post(url, headers=headersHabitica, json={"text": name, "type": "todo", "priority":"2"})
data = res.json()
#print(res.text)
def scoreTaskInHabitica(id):
url = f"https://habitica.com/api/v3/tasks/{id}/score/up"
res = requests.post(url, headers=headersHabitica)
print(res.status_code)
def scoreTaskInNotion(name, headers):
url = f"https://api.notion.com/v1/pages/{name}"
res = requests.patch(url, headers=headers, json={"properties": {"Status" : {"select" :{"name": completed()}}}})
data = res.json()
#print(res.text)
print(res.status_code)
def prefixN2H(taskName):
return prefixChar()+taskName
def isAbsentInHabitica(taskName, habiticaList):
taskName = prefixN2H(taskName)
for i in habiticaList:
if taskName == i['name']:
return False
return True
def getHabiticaList(habitica_file):
lst = []
f = open(habitica_file,encoding="utf8")
data = json.load(f)
for i in data['data']:
name = i['text']
id = i['id']
dict = {'name':name , 'id':id}
lst.append(dict)
f.close()
return lst
def getNotionList(condn):
lst = []
f = open("notion.json")
data = json.load(f)
for i in data['results']:
name = i['properties']['Name']['title'][0]['text']['content']
status = i['properties']['Status']['select']['name']
id = i['id']
if condn(status):
dict = {'name':name , 'id':id}
lst.append(dict)
f.close()
return lst
def notionDoneCondn(status):
return status == completed()
def notionNotDoneCondn(status):
return status != completed()
def getDoneListOfNotion():
return getNotionList(notionDoneCondn)
def getNotDoneListOfNotion():
return getNotionList(notionNotDoneCondn)
def getTaskId(name, list):
for i in list:
if name == i['name']:
return i['id']
def syncNotionToHabitica():
print('==========================')
print('Syncing Notion to Habitica')
print('==========================')
habiticaList = getHabiticaList("habitica.json")
notionDoneList = getDoneListOfNotion()
for task in notionDoneList:
print('Processing completed Notion Task in Habitica ' + task['name'])
name = prefixN2H(task['name'])
habiticaid = getTaskId(name,habiticaList)
if habiticaid is not None:
print('Scoring in Habitica for ' + name + ':' + habiticaid)
scoreTaskInHabitica(habiticaid)
notionNotDoneList = getNotDoneListOfNotion()
for task in notionNotDoneList:
print('Processing Incomplete Notion Task ' + task['name'])
if isAbsentInHabitica(task['name'],habiticaList):
print('Missing in Habitica, Creating '+ task['name'] )
createTodoInHabitica(task['name'], headersHabitica)
def syncHabiticaToNotion():
print('==========================')
print('Syncing Habitica to Notion')
print('==========================')
notionNotDoneList = getNotDoneListOfNotion()
habiticaDoneList = getHabiticaList("habitica_done.json")
for task in notionNotDoneList:
try:
print('Processing Notion task ' + task['name'])
habitica_name = prefixN2H(task['name'])
for i in habiticaDoneList :
if habitica_name == i['name']:
print('Scoring in Notion for ' + habitica_name + ':' + task['id'])
scoreTaskInNotion(task['id'], headers)
except Exception as e:
#print(e)
print('Cannot score : Old or absent Habitica todo ' + task['name'])
def readDB():
print('==========================')
print('Reading data')
print('==========================')
print('Reading Notion Data')
readDatabaseOfNotion(databaseId, headers)
print('Reading Habitica Data')
readHabiticaData(headersHabitica)
print('Reading Habitica Done Data')
readHabiticaDoneData(headersHabitica)
readDB()
syncNotionToHabitica()
syncHabiticaToNotion()
@elliotfontaine
Copy link

May I suggest you replace status = i['properties']['Status']['select']['name'] by status = i['properties']['Status']['status']['name'] in line 100 ? I guess the Notion API changed its nomenclature since you wrote this script.

To add to what Hydrock said, do you think it could even be possible to sync back, Habitica2Notion ? I've got no experience with their API.

@gauchy
Copy link
Author

gauchy commented Nov 6, 2022

@elliotfontaine - i see according to the official documentation this is right -> status = i['properties']['Status']['select']['name']

It has been a long time since i update this gist. I will try to create both way syncing between notion and habitica, perhaps within a week from now.

@elliotfontaine
Copy link

elliotfontaine commented Nov 6, 2022

@gauchy Well, it didn't work with 'select', I had to change it to 'status' for the script to work. What led me to the answer was the structure of the json files created by the script.

EDIT: I would be so glad if you managed to make two-way syncing! For me the best way would be to change the name of the new task to sync on both databases, adding your current N2H_ prefix. Then, if you detect one of the two is missing (either by keeping track of the tasks in a json or simply by checking if there is a lonely N2H_ task), you would ✅ the task on both DB.
I have no experience with web services yet, but I'm eager to learn so if you need any help with this little project just ask me :)

@gauchy
Copy link
Author

gauchy commented Nov 7, 2022

@elliotfontaine - did a few changes for two way sync, have a look if it works.
Why status is working for you instead of select is perhaps because the property type you have chosen is of 'status' type not 'select'. See here - https://developers.notion.com/reference/property-object

@callumzhong
Copy link

callumzhong commented Nov 12, 2022

The following is machine translation, English is not my native language.

Great project 👍
two way sync is work, but if the task name is duplicated, there will be problems.

I made some small modifications 62 - 190

  • Notion create new property "HabiticaID" type "text" => Two-way sync key
  • syncNotionToHabitica function check "habitica.json" and "habitica_done.json" for absences
def createTodoInHabitica(name, headersHabitica):
    name = prefixN2H(name)
    url = "https://habitica.com/api/v3/tasks/user"

    res = requests.post(url, headers=headersHabitica, json={"text": name,"type": "todo", "priority":'2'})
    data = res.json()
    # print(res.text)
    return data['data']['id']

def updateNotionInHabiticaID(pageId,habiticaId,headers):
    url = f"https://api.notion.com/v1/pages/{pageId}"
    res = requests.patch(url, headers=headers, json={"properties": {
        "HabiticaID":{"rich_text":[
            {"type":"text","text":{"content":habiticaId}}
        ]}
    }})
    data =res.json()
    print(res.status_code)

def scoreTaskInHabitica(id):
    url = f"https://habitica.com/api/v3/tasks/{id}/score/up"
    res = requests.post(url, headers=headersHabitica)
    print(res.status_code)
    
def scoreTaskInNotion(name, headers):
    url = f"https://api.notion.com/v1/pages/{name}"
    res = requests.patch(url, headers=headers, json={"properties": {"Status" : {"select" :{"name": completed()}}}})
    data = res.json()
    #print(res.text)
    print(res.status_code)

def prefixN2H(taskName):
    return prefixChar()+taskName

def isAbsentInHabitica(habiticaId, habiticaList,habiticaDoneList):
    isAbsent = True
    for i in habiticaList:
        if habiticaId == i['id']:
            isAbsent = False
    for i in habiticaDoneList:
         if habiticaId == i['id']:
            isAbsent = False
    return isAbsent

def getHabiticaList(habitica_file):
    lst = []
    f = open(habitica_file,encoding="utf8")
    data = json.load(f)

    for i in data['data']:
        name = i['text']
        id = i['id']
        dict = {'name':name , 'id':id}
        lst.append(dict)
    f.close()
    return lst

def getNotionList(condn):
    lst = []
    f = open("notion.json")
    data = json.load(f)

    for i in data['results']:
        name = i['properties']['Name']['title'][0]['text']['content']
        status = i['properties']['Status']['select']['name']

        temp = i['properties']['HabiticaID']['rich_text']
        if not temp:
            habiticaId = ''
        else:
            habiticaId = temp[0]['text']['content']
        
        id = i['id']
        if condn(status):
            dict = {'name':name , 'id':id, 'HabiticaID':habiticaId}
            lst.append(dict)
    f.close()
    return lst


def notionDoneCondn(status):
    return status == completed()


def notionNotDoneCondn(status):
    return status != completed()

def getDoneListOfNotion():
    return getNotionList(notionDoneCondn)


def getNotDoneListOfNotion():
    return getNotionList(notionNotDoneCondn)


def getTaskId(habiticaId, list):
    for i in list:
        if habiticaId == i['id']:
            return i['id']


def syncNotionToHabitica():
    print('==========================')
    print('Syncing Notion to Habitica')
    print('==========================')
    habiticaList = getHabiticaList("habitica.json")
    habiticaDoneList = getHabiticaList("habitica_done.json")
    notionDoneList = getDoneListOfNotion()

    for task in notionDoneList:
        print('Processing completed Notion Task in Habitica ' + task['name'])
        name = prefixN2H(task['name'])
        habiticaId = getTaskId(task['HabiticaID'],habiticaList)
        if habiticaId is not None:
            print('Scoring in Habitica for ' + name + ':' + habiticaId)
            scoreTaskInHabitica(habiticaId)


    notionNotDoneList = getNotDoneListOfNotion()

    for task in notionNotDoneList:
        print('Processing Incomplete Notion Task ' + task['name'])
        if isAbsentInHabitica(task['HabiticaID'],habiticaList,habiticaDoneList):
            print('Missing in Habitica, Creating '+ task['name'] )
            habiticaId = createTodoInHabitica(task['name'], headersHabitica)
            print(f'updated HabiticaID in Notion,  '+ task['name'] )
            updateNotionInHabiticaID(task['id'], habiticaId, headers)

def syncHabiticaToNotion():
    print('==========================')
    print('Syncing Habitica to Notion')
    print('==========================')
    notionNotDoneList = getNotDoneListOfNotion()
    habiticaDoneList = getHabiticaList("habitica_done.json")
    for task in notionNotDoneList:
        try:
            print('Processing Notion task ' + task['name'])
            habitica_name = prefixN2H(task['name'])
            habiticaId = task['HabiticaID']
            for i in habiticaDoneList :
                if habiticaId == i['id'] :
                    print('Scoring in Notion for ' + habitica_name + ':' + task['id'])
                    scoreTaskInNotion(task['id'], headers)
            
        except Exception as e:
            #print(e)
            print('Cannot score : Old or absent Habitica todo ' + task['name'])

@vandaref
Copy link

vandaref commented Jan 19, 2024

Hi there, I updated some steps of the tutorial like adding the integration and add few code inside like the difficulty of the task in Habitica depends on the priority in Notion or removing the prefix.
https://github.com/vandaref/from_notion_to_habitica/tree/main
My bad I forgot to make a fork of your work

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment