Skip to content

Instantly share code, notes, and snippets.

@aenain
Created June 18, 2019 20:16
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aenain/5a50e7f1c0177b35bf6afd3bf525145d to your computer and use it in GitHub Desktop.
Save aenain/5a50e7f1c0177b35bf6afd3bf525145d to your computer and use it in GitHub Desktop.
Wunderlist to Dynalist export. Go to Wunderlist web interface, backup data (JSON file), folders, list positions (from IndexedDB), use this script and import the output into Dynalist. Heavily inspired by https://gist.github.com/bryanph/fcddf602d60d7c6ecdaf6584f2db55a5
var download = (filename, text) => {
var element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
var openDatabase = (name, version) => {
return new Promise((resolve, reject) => {
const openRequest = window.indexedDB.open(name, version);
openRequest.onerror = function(event) {
reject(openRequest);
};
openRequest.onsuccess = function(event) {
resolve(openRequest.result);
};
});
}
var getAll = (db, tableName) => {
return new Promise((resolve, reject) => {
const models = [];
var objectStore = db.transaction(tableName).objectStore(tableName);
objectStore.openCursor().onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
models.push(cursor.value);
cursor.continue();
} else {
resolve(models);
}
};
});
}
var retrieveEntireDatabase = (db) => {
const tableNames = Array.from(db.objectStoreNames);
return Promise.all(tableNames.map(it => getAll(db, it))).then((values) => {
return tableNames.reduce((acc, tableName, index) => {
acc[tableName] = values[index];
return acc;
}, {});
});
}
openDatabase('wunderlist-3').then((db) => {
retrieveEntireDatabase(db).then((data) => {
const serializedData = JSON.stringify(data);
console.log(serializedData);
download('wunderlist-indexeddb.json', serializedData);
});
});
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
usage:
python3 wunderlist2dynalist.py > dynalist.opml
- Go to Wunderlist web interface
- backup data (JSON file) as `wunderlist.json`
- for lists, tasks and subtasks
- put in the same directory as this file
- backup folders and list ordering as wunderlist-indexeddb.json
- run `export-wunderlist-indexeddb.js` in Chrome DevTools
- copy-paste entire file contents to JS console
- save generated file as `./wunderlist-indexeddb.json` in the same directory as this file
- use this script and import the output as OPML into Dynalist
"""
import json
import html
with open('wunderlist.json') as f:
wunderlist = json.load(f)['data']
with open('wunderlist-indexeddb.json') as f2:
wunderlistDB = json.load(f2)
def printXml(content):
print('<?xml version="1.0"?>')
print('<opml version="2.0">')
print('<body>')
content()
print('</body>')
print('</opml>')
def printFolders():
for folder in getOrderedFolders():
printFolderLine(folder)
printListsForFolder(folder)
print('</outline>')
def printListsForFolder(folder):
for list in getOrderedListsForFolder(folder):
printListLine(list)
printTasksForList(list)
print('\t</outline>')
def printTasksForList(list):
for task in getOrderedTasksForList(list):
printTaskWithSubtasks(task)
def printTaskWithSubtasks(task):
printTaskLine(task)
printSubtasksForTask(task)
print('\t\t</outline>')
def printSubtasksForTask(task):
for subtask in getOrderedSubtasksForTask(task):
printSubtaskLine(subtask)
def printFolderLine(folder):
print('<outline text="{}">'.format('{} (folder:{})'.format(html.escape(folder['title']), folder['id'])))
def printListLine(list):
print('\t<outline text="{}">'.format('{} (list:{})'.format(html.escape(list['title']), list['id'])))
def printTaskLine(task):
title = formatTaskTitle(task)
note_content = getNoteForTask(task)
completed = task['completed']
if completed and note_content:
print('\t\t<outline text="{}" _note="{}" complete="true">'.format(html.escape(title), html.escape(note_content).replace("\n", "&#10;")))
elif note_content:
print('\t\t<outline text="{}" _note="{}">'.format(html.escape(title), html.escape(note_content).replace("\n", "&#10;")))
elif completed:
print('\t\t<outline text="{}" complete="true">'.format(html.escape(title)))
else:
print('\t\t<outline text="{}">'.format(html.escape(title)))
def formatTaskTitle(task):
if 'due_date' in task:
return task['title'] + ' !(' + task['due_date'] + ')'
else:
return task['title']
def getNoteForTask(task):
note_content = None
for note in wunderlist['notes']:
if note['task_id'] == task['id']:
if len(note['content']) != 0:
note_content = note['content'].strip()
return note_content
def printSubtaskLine(subtask):
if subtask['completed']:
print('\t\t\t<outline text="{}" complete="true" />'.format(html.escape(subtask['title'])))
else:
print('\t\t\t<outline text="{}" />'.format(html.escape(subtask['title'])))
def getOrderedFolders():
return sorted(wunderlistDB['folders'], key=lambda it: it['position'])
def getOrderedListsForFolder(folder):
lists = []
for list_position_id in sorted(folder['list_ids'], key=getListPositionById):
for list in wunderlist['lists']:
if str(list['id']) == list_position_id:
lists.append(list)
return lists
def getListPositionById(list_id):
return wunderlistDB['listPositions'][0]['values'].index(list_id)
def getOrderedTasksForList(list):
tasks = []
# wunderlist: {
# task_positions: [{ list_id: <LIST_ID>, values: [<TASK_ID>, ...] }],
# tasks: [{ list_id: <LIST_ID>, id: <TASK_ID> }, ...]
# }
for task_position in wunderlist['task_positions']:
if task_position['list_id'] == list['id']:
task_ids_list = task_position['values']
for task in wunderlist['tasks']:
if task['list_id'] == list['id'] and not task['id'] in task_ids_list:
task_ids_list.append(task['id'])
for task_id in task_ids_list:
for task in wunderlist['tasks']:
if task['id'] == task_id:
tasks.append(task)
return tasks
def getOrderedSubtasksForTask(task):
subtasks = []
for subtask_position in wunderlist['subtask_positions']:
if subtask_position['task_id'] == task['id']:
for subtask_id in subtask_position['values']:
for subtask in wunderlist['subtasks']:
if subtask['id'] == subtask_id:
subtasks.append(subtask)
return subtasks
printXml(printFolders)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment