Skip to content

Instantly share code, notes, and snippets.

@matellis
Forked from cdzombak/things-to-of.applescript
Last active January 31, 2024 18:53
Show Gist options
  • Star 25 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save matellis/69954d4212b1a36c13aad3de4e75187e to your computer and use it in GitHub Desktop.
Save matellis/69954d4212b1a36c13aad3de4e75187e to your computer and use it in GitHub Desktop.
Script to import from Things 3 into Omnifocus 3
--------------------------------------------------
--------------------------------------------------
-- Import tasks from Things to OmniFocus
--------------------------------------------------
--------------------------------------------------
--
-- Script taken from: http://forums.omnigroup.com/showthread.php?t=14846&page=2 && https://gist.github.com/cdzombak/11265615
-- Added: OF3 & Things 3 compatibility; task order; areas/folders; tags
-- Empty your Things Trash first.
--
-- Does not support:
-- Recurring tasks
-- Sub-tasks
-- Someday tasks (can't get them from T3 unless they are not in a project)
-- Tag short cuts (not an OF3 feature)
-- Cancellation date (no OF3 equivalent)
-- Contact for a task (no OF3 equivalent)
-- Modification date from Things (it's a read only attribute in OF3)
--
-- Title of project for stuff in things with no project (avoids going into the Inbox)
--
property noProjectTitle : "No Project In Things"
set answer to the button returned of ¬
(display alert ¬
¬
"Things 3 to Omnifocus 3 Importer" & return & return & "PLEASE READ BEFORE PROCEEDING" message ¬
"The script does not yet import sub-tasks or recurrences.
>> 'Some Day' tasks that are assigned to a project will not be imported. <<
Omnifocus does not support tag short cuts, cancellation dates or contact info for a task,
and does not permit the importation of modification date.
1. TAKE BACKUPS!
2. Empty your trash on Things 3.
3. Reset your database on OmniFocus 3 & delete default tags.
4. Run Clean Up when done on OmniFocus to remove logged tasks.
5. Setup sync after import has completed.
Then press 'I Understand' to get going.
Importation will take a while, about 3,000 tasks per hour.
" buttons {"How do I reset Omnifocus?", "I Understand", " Cancel "} default button 3)
if answer is " Cancel " then return
if answer is "How do I reset Omnifocus?" then
display dialog "Make sure you take backups of your Omnifocus database if you've used it before. Resetting will delete even the backups unless they are copied elsewhere!" with icon stop
open location "https://support.omnigroup.com/omnifocus-reset-database/"
return
end if
tell application "Things3"
--
-- Import T3 hierarchal tags into OF3 in correct order and without duplicates
--
-- Get tags from T3
set tagList to {}
repeat with aTag in every tag
if (parent tag of aTag) is missing value then
copy {null, name of aTag as string} to end of tagList
else
copy {name of parent tag of aTag as string, name of aTag as string} to end of tagList
end if
end repeat
-- Put tags into OF3
tell application "OmniFocus"
tell default document
-- we do this twice, once with first level and then again with second level
-- this is the only way to preserve order
-- first pass - parents only
repeat with aTag in tagList
set theParentTag to first item of aTag
set theChildTag to second item of aTag
if theParentTag is null then
if not (tag theChildTag exists) then ¬
make new tag with properties {name:theChildTag}
end if
end repeat
-- second pass - children only
repeat with aTag in tagList
set theParentTag to first item of aTag
set theChildTag to second item of aTag
if theParentTag is not null then
set parentTag to tag theParentTag
if not (my isTagInList(name of every tag of parentTag, theChildTag)) then
set childTag to make new tag with properties {name:theChildTag}
move childTag to (end of tags of (first tag whose name is theParentTag))
end if
end if
end repeat
end tell
end tell -- End of Tag Import
--
-- Import T3 Areas as Folders into OF3
--
-- Get areas from T3
set folderList to name of every area
log "Processing " & (count of folderList) & " folders"
-- Put areas into OF3
tell application "OmniFocus"
tell default document
repeat with aFolder in folderList
if not (folder aFolder exists) then
make new folder with properties {name:aFolder}
end if
end repeat
end tell
end tell -- End of Folder Import
--
-- Import Projects into OF3
--
set projectList to {{missing value, noProjectTitle, "", "active", "", {}}}
set completedProjectList to {}
set AppleScript's text item delimiters to ","
repeat with aProject in every project
set areaName to null
if area of aProject is missing value then
set areaName to missing value
else
set areaName to name of area of aProject as string
end if
set projectName to name of aProject as string
set projectNotes to notes of aProject as string
-- set tagList to every text item of (tag names of aProject as string)
set tagList to my gatherTagsOf(aProject)
set projectCompletionDate to completion date of aProject
-- status: active/‌on hold/‌done/‌dropped. vs open completed canceled
set projectStatus to status of aProject as string
copy {areaName, projectName, projectNotes, projectStatus, projectCompletionDate, tagList} to end of projectList
end repeat
log "Processing " & (count of projectList) & " projects"
tell application "OmniFocus"
tell default document
repeat with aProject in projectList
set theFolderName to first item in aProject
set theProjectName to second item in aProject
set theProjectNotes to third item in aProject
set theProjectStatus to fourth item in aProject
set theProjectTags to sixth item in aProject
set theProjectCompletionDate to fifth item in aProject
if theProjectStatus is "completed" then
set theProjectStatus to done
else if theProjectStatus is "canceled" then
set theProjectStatus to dropped
else if theProjectStatus is "open" then
set theProjectStatus to active
end if
if theFolderName is missing value then
-- no area
if (project theProjectName exists) then
set theProject to project theProjectName
else
set theProject to make new project with properties {name:theProjectName, note:theProjectNotes}
end if
if theProjectStatus is done then
if not (my isItemInList(completedProjectList, theProject)) then
copy {theProject, theProjectCompletionDate} to end of completedProjectList
end if
end if
else
-- Project inside an area
tell folder theFolderName
if project theProjectName exists then
set theProject to project theProjectName
else
-- add tags
set theProject to make new project with properties {name:theProjectName, note:theProjectNotes}
end if
if theProjectStatus is done then
if not (my isItemInList(completedProjectList, theProject)) then
copy {theProject, theProjectCompletionDate} to end of completedProjectList
end if
end if
end tell
move theProject to (end of sections of (first folder whose name is theFolderName))
end if
-- Write out tags
my writeTagsTo(theProject, theProjectTags)
end repeat
end tell
end tell
--
-- Import T3 To Dos into OF3
--
-- Combine all the folders you want to search here
-- Options are: Inbox, Today, Anytime, Upcoming, Someday, Lonely Projects, Logbook, Trash
set theTodos to to dos of list "Inbox" & to dos of list "Anytime" & to dos of list "Upcoming" & to dos of list "Someday" & to dos of list "Lonely Projects" & to dos of list "Logbook"
-- Go through all the tasks in the combined lists
log "Processing " & (count of theTodos) & " entries"
repeat with aTodo in theTodos
-- Get various attributes of Things task
set theTitle to name of aTodo
set theNote to notes of aTodo
set theDueDate to due date of aTodo
set theStartDate to activation date of aTodo -- aka "Defer Date"
set theFlagStatus to false
set theStatus to status of aTodo as string
set theCompletionDate to completion date of aTodo
set theCreationDate to creation date of aTodo
-- Get project & area names
set theFolderName to missing value
set theProjectNote to ""
set theProjectName to ""
set processItemFlag to true
set isInbox to false
set isProject to false
if class of aTodo is project then
-- Is this task actually a project?
set isProject to true
else if ((area of aTodo is missing value) and (project of aTodo is missing value)) then
-- Is this todo in the Inbox? If so, it needs special treatment
set isInbox to true
else if (project of aTodo) is missing value then
-- Just an orphaned task, no such thing in OF3 so put in a folder
set theProjectName to noProjectTitle
else
-- Regular task inside a project
set theProjectName to (name of project of aTodo)
set theProjectNote to (notes of project of aTodo)
-- With a folder
if area of project of aTodo is missing value then
set theFolderName to missing value
else
set theFolderName to (name of area of project of aTodo)
end if
end if
-- Gather tags
set allTagNames to my gatherTagsOf(aTodo)
-- Create a new task in OmniFocus
tell application "OmniFocus"
tell default document
if not isProject then
-- Create the actual task - does not de-dupe
-- Do it differently if inbox
if isInbox then
set newTask to make new inbox task with properties {name:theTitle, note:theNote, creation date:theCreationDate, due date:theDueDate, defer date:theStartDate, flagged:theFlagStatus}
else
if theFolderName is missing value then
if project theProjectName exists then
tell project theProjectName
set newTask to make new task with properties {name:theTitle, note:theNote, creation date:theCreationDate, due date:theDueDate, defer date:theStartDate, flagged:theFlagStatus}
end tell
end if
else
tell folder theFolderName
tell project theProjectName
set newTask to make new task with properties {name:theTitle, note:theNote, creation date:theCreationDate, due date:theDueDate, defer date:theStartDate, flagged:theFlagStatus}
end tell
end tell
end if
end if
-- handle completed
if theStatus is "completed" then
mark complete newTask
set completion date of newTask to theCompletionDate
else if theStatus is "canceled" then
mark complete newTask
set status of newTask to dropped
set completion date of newTask to theCompletionDate
else if theStatus is "open" then
mark incomplete newTask
set completion date of newTask to missing value
end if
-- Process tags
my writeTagsTo(newTask, allTagNames)
end if -- not a task
end tell -- OF application
end tell -- Document
end repeat -- Things list
-- Mark complete any projects that should be that way
tell application "OmniFocus"
tell default document
repeat with completedProjectInfo in completedProjectList
set completedProject to first item of completedProjectInfo
set completedProjectDate to second item of completedProjectInfo
mark complete completedProject
set completion date of completedProject to completedProjectDate
end repeat
end tell
end tell
end tell -- Things application
-- Clumsy way of seeing if an item is in the Inbox as Things doesn't expose a "list" property
on isItemInList(theList, theItem)
set the matchFlag to false
repeat with anItem from 1 to the count of theList
if theList contains anItem then ¬
set the matchFlag to true
end repeat
return the matchFlag
end isItemInList
-- Another hack for heirarchal tags to say "does tag exist inside this other tag?"
on isTagInList(theList, theItem)
set the matchFlag to false
repeat with anItem from 1 to the count of theList
if item anItem of theList is theItem then ¬
set the matchFlag to true
end repeat
return the matchFlag
end isTagInList
-- less clumsy inbox hack
on isInInbox(anItem)
return ((area of anItem is missing value) and (project of anItem is missing value))
end isInInbox
-- gather tags
on gatherTagsOf(aTodo)
set allTagNames to {}
tell application "Things3"
repeat with aTag in every tag of aTodo
if (parent tag of aTag) is missing value then
copy {null, name of aTag} to end of allTagNames
else
copy {name of parent tag of aTag, name of aTag} to end of allTagNames
end if
end repeat
end tell
return allTagNames
end gatherTagsOf
-- write tags
on writeTagsTo(aTask, tagList)
tell application "OmniFocus"
tell default document
repeat with aTag in tagList
set theParentTag to first item of aTag
set theChildTag to second item of aTag
if theParentTag is null then
-- No Parent tag
add tag theChildTag to tags of aTask
else
-- Has a parent tag
set childTag to (first tag of tag theParentTag whose name is theChildTag)
add childTag to tags of aTask
end if
end repeat -- tags
end tell
end tell
end writeTagsTo
@kscarmardo
Copy link

FYI.... "set status of newTask to dropped" is not supported on tasks...just projects. I changed the line to tag the Task as Cancelled instead. Some additional code to create a CancelledT3 tag for users to rename later would probably be good to add. If I get enough time I'll code it and send along.

@iszw
Copy link

iszw commented Oct 27, 2020

FYI.... "set status of newTask to dropped" is not supported on tasks...just projects. I changed the line to tag the Task as Cancelled instead. Some additional code to create a CancelledT3 tag for users to rename later would probably be good to add. If I get enough time I'll code it and send along.

hello. could you tell me how you deal with this problem? how do I modify this code?
997FE4EA-402F-4843-A452-2CA8836093E7

@matellis
Copy link
Author

matellis commented Oct 27, 2020 via email

@iszw
Copy link

iszw commented Oct 27, 2020

Modify it to do what exactly?

On Oct 26, 2020, 6:35 PM -0700, suvan @.***>, wrote: @iszw commented on this gist. > FYI.... "set status of newTask to dropped" is not supported on tasks...just projects. I changed the line to tag the Task as Cancelled instead. Some additional code to create a CancelledT3 tag for users to rename later would probably be good to add. If I get enough time I'll code it and send along. hello. could you tell me how you deal with this problem? how do I modify this code? — You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub, or unsubscribe.
The script throws an error this line.
image

@matellis
Copy link
Author

I'm unable to help you debug remotely much as I'd like to. There are so many different ways these kind of scripts can go wrong and I've no idea what enhancements Things has added since I updated this script. I suggest you comment this line out and see what happens, just add a double dash -- before the line throwing an error to do this. Good luck.

@iszw
Copy link

iszw commented Oct 27, 2020

Okay. thanks for ur suggestion and the script. 🎉

@lgeorge
Copy link

lgeorge commented Aug 28, 2022

The script helped me move from Things 3 3.15.17 (31517520) to Omnifocus 3.13.2 (v151.28.0) this week.
It took me a few tries; here are a few notes:

  • My first try got an error: "lonely project" wasn't found, and the script exited with a partial transfer. I didn't see "lonely project" in Things so I commented out the "lonely project" test in the script (& commented out "trash"); that got me through the whole set of folders.
  • It took a couple tries to get my "someday" tasks into a form that would be transferred - I ended up dumping them into "Anytime" to reassign later.
  • I hadn't been using task contacts, subtasks, cancellation dates, or tag shortcuts, so those weren't a concern.
  • I do use recurrences, so it took a few days to reestablish recurrences and confirm that everything transferred.
    It was very helpful to have the script, as it established the basic project + task structure with all of my notes, dates, etc.
    Thanks!

@HelloThisIsFlo
Copy link

Hey @matellis ,
Thanks a lot for your script. It helped me a lot... but I wanted to get support for 'someday' items and 'checklists'. I tried to tweak your script, only to realize that the Things 3 Applescript API just doesn't allow access to these items (I did manage to get some 'someday' items, but not all, and no checklists).

So, I wrote my own conversion script that reads directly from the Things3 DB and generated files in the TaskPaper format ready to be copy-pasted in Omnifocus. I migrated 2000+ tasks and 150+ projects this way, and it worked great (with some manual cleanup needed afterwards, see README)

If anyone lands on this page, and is interested to give it a shot, it's here: https://github.com/HelloThisIsFlo/Things3-To-OmniFocus
It comes with no guarantees whatsoever, obviously, but I believe it can be very useful

Thanks again for your work @matellis, it's been very helpful

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