Skip to content

Instantly share code, notes, and snippets.

@kozak127
Created April 16, 2023 15:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kozak127/f6ca5c7df719bf2a56c4eb54f721a283 to your computer and use it in GitHub Desktop.
Save kozak127/f6ca5c7df719bf2a56c4eb54f721a283 to your computer and use it in GitHub Desktop.
Microsoft ToDo -> Google Calendar Tasks migration script
## MS TODO -> Google Calendar/Tasks migration script
Script will add pending and completed tasks, with the completion date to Google Tasks. It also recognizes notes in the tasks. It ignores nested tasks and repeat rules.
## HOW TO USE
(abridged version of the abridged version. Sorry. You will figure this out)
1. Download all tasks from MS TODO list using https://www.todovodo.com/home in JSON format. Move it to the script folder
2. Add string ```{ "list" : ``` at the start of the JSON file
3. Add string ```}``` at the end of the JSON file
4. In Google Cloud Console, enable Task API
5. In Google Cloud Console, configure OAuth consent screen. Add yourself as a test user. Add Task API as scope
6. In Google Cloud Console, configure and download OAuth2 credential
7. Rename downloaded credential to ```credentials.json```. Move it to the script folder
8. Create desired task list in Google Tasks
9. Run code for the first time. It will blow up with errors. Ignore that, it printed task list IDs. Copy them, and put in TASKLIST_ID dictionary
10. In LIST_JSON variable, put the path to the JSON downloaded from todovodo
11. In LIST_NAME, specify the list name (the same as in TASKLIST_ID)
12. Run the code
Note: sometimes, Google will return error "Quota Exceeded" in the logs. Ignore that, the script will try to resume operations in 30 seconds
from __future__ import print_function
import os.path
import json
import time
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
# If modifying these scopes, delete the file token.json.
SCOPES = ['https://www.googleapis.com/auth/tasks']
TASKLIST_ID = {
"Zakupy": "YndMd1g2QWJFUHNCaTR3TQ",
"Żyjątka": "QUZnbklIelYwY1p0azl5aw",
"TODO": "MEhyUkVuQ1dxUi1wd1VmTw",
"Static": "b1RZN3JTdkh2dXRsc1dvRg",
"R-Ka": "clhlMTdWYzVCQkhkVElRZg",
"Rachunki": "LVRPRmlIY3ZHTWdHeWNBSA",
"Praca": "Q2ZWTjR6Yy10RnhaaldiWQ",
"Porządki": "c0gzMFZXTHl5T1J0XzhfTQ",
"Mieszkanie": "TnQxREk2Q2ZEcFVua2t1cw",
"Lekarze": "R2oxTlNSZlJaajAtVURZQQ",
"CBF1000": "eV9OSVBPa3lCd29zVWZYNA"
}
LIST_JSON = "zakupy.json"
LIST_NAME = "Zakupy"
def main():
creds = get_credentials()
try:
service = build('tasks', 'v1', credentials=creds)
results = service.tasklists().list(maxResults=20).execute()
items = results.get('items', [])
if not items:
print('No task lists found.')
return
print('Task lists:')
for item in items:
print(u'{0} ({1})'.format(item['title'], item['id']))
print('######')
tasks_ms = read_tasks_from_json(LIST_JSON)
for task_ms in tasks_ms:
task_gcal = map_task(task_ms)
insert_task(service, task_gcal, LIST_NAME)
except HttpError as err:
print(err)
def get_credentials():
creds = None
# The file token.json stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists('token.json'):
creds = Credentials.from_authorized_user_file('token.json', SCOPES)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open('token.json', 'w') as token:
token.write(creds.to_json())
return creds
def read_tasks_from_json(filename):
json_file = open(file=filename, encoding="utf-8-sig")
open_json_file = json.load(json_file)
return open_json_file["list"]
def map_task(task_ms):
task_gcal = {
"kind": "tasks#task",
"title": task_ms["title"],
"status": "needsAction"
}
last_modified_date_time = task_ms["lastModifiedDateTime"]
last_modified_date_time = fix_datetime_formatting(last_modified_date_time)
task_gcal["updated"] = last_modified_date_time
if task_ms["status"] == "completed":
task_gcal["status"] = "completed"
completed_date_time = task_ms["completedDateTime"]["dateTime"]
completed_date_time = fix_datetime_formatting(completed_date_time)
task_gcal["completed"] = completed_date_time
try:
due_date = task_ms["dueDateTime"]["dateTime"]
if due_date is not None:
due_date = fix_datetime_formatting(due_date)
task_gcal["due"] = due_date
except Exception:
pass
notes = task_ms["body"]["content"]
if (notes is not None) and (notes != ""):
task_gcal["notes"] = notes
print(task_gcal["title"] + " ### " + task_gcal["updated"])
return task_gcal
def fix_datetime_formatting(datetime):
if (datetime is not None) and (not datetime.endswith("Z")):
datetime = datetime + "Z"
return datetime
def insert_task(service, task, tasklist_name):
while(True):
try:
tasklist_id = TASKLIST_ID[tasklist_name]
service.tasks().insert(tasklist=tasklist_id, body=task).execute()
return
except HttpError as err:
print(err)
time.sleep(30)
if __name__ == '__main__':
main()
google-api-python-client
google-auth-httplib2
google-auth-oauthlib
{
"list": [
{
"@odata.etag": "W/\"I0svlcvxtUC4XtS1gMFc9gAD4iK+Bg==\"",
"importance": "normal",
"isReminderOn": false,
"status": "completed",
"title": "Test title 1",
"createdDateTime": "2022-08-01T08:37:58.6549082Z",
"lastModifiedDateTime": "2022-08-01T10:56:24.7930901Z",
"hasAttachments": false,
"categories": [],
"id": "AQMkADAwATM3ZmYAZS0xMjIAMC0wN2MxLTAwAi0wMAoARgAAA1eYUkldCT1Kgvrj5HJ0bzcHACNLL5XL8bVAuF7UtYDBXPYAAZwvaY0AAAAjSy_Vy-G1QLhe1LWAwVz2AAPiAXOtAAAA",
"body": {
"content": "test_note",
"contentType": "text"
},
"completedDateTime": {
"dateTime": "2022-07-31T22:00:00.0000000",
"timeZone": "UTC"
},
"dueDateTime": {
"dateTime": "2022-07-31T22:00:00.0000000",
"timeZone": "UTC"
}
},
{
"@odata.etag": "W/\"I0svlcvxtUC4XtS1gMFc9gADGGyaHA==\"",
"importance": "normal",
"isReminderOn": false,
"status": "notStarted",
"title": "Test title 2",
"createdDateTime": "2020-02-23T20:13:38.2540287Z",
"lastModifiedDateTime": "2021-09-29T22:31:23.7018542Z",
"hasAttachments": false,
"categories": [],
"id": "AQMkADAwATM3ZmYAZS0xMjIAMC0wN2MxLTAwAi0wMAoARgAAA1eYUkldCT1Kgvrj5HJ0bzcHACNLL5XL8bVAuF7UtYDBXPYAAZwvaY0AAAAjSy_Vy-G1QLhe1LWAwVz2AAGcMB5YAAAA",
"body": {
"content": "",
"contentType": "text"
}
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment