-
-
Save mlgill/3813222 to your computer and use it in GitHub Desktop.
--Script for setting Reminders for LaunchBar and Alfred | |
--For Alfred, Applescript must NOT be set to run in Background otherwise date parsing does not work | |
--For LaunchBar, place the script in ~/Library/Scripts/LaunchBar | |
--by Michelle L. Gill, 10/07/2012 | |
--Inspired by https://gist.github.com/3187630 | |
--A related Alfred version 2 workflow can be found here: https://github.com/mlgill/alfred-workflow-create-reminder | |
--Changes | |
--02/01/2013 * Fixed an issue with setting the time when the hour is 12 and AM/PM (12-hour clock) is used | |
-- * Removed the ability to set seconds for the time since Reminders doesn't recognize them | |
--02/02/2013 * Fixed a regression in setting the date when only the hour is present | |
-- * Added the ability to return information from the created reminder as a string for error checking. | |
-- * This returned string can be sent to Growl (if installed) to post a notification. For Alfred, the | |
-- option to post the returned string via Growl has been commented out by default | |
-- since the forthcoming version of Alfred will use this script via workflows and | |
-- they have their own method of notification posting. If you're using alfred version 1, | |
-- uncomment the appropriate line below. Note that posting notifications via Mountain Lion's | |
-- Notification Center is not possible without incorporating additional tools (an application). | |
-- I **DO NOT** plan to add notification center support to this script for this reason. (However, | |
-- notification support can be used within the workflows in Alfred 2. | |
--Notes on valid reminders | |
--Order of the text must be "title #note $list @date time" but note | |
--List is optional if a default is set within the script | |
--Time must be included, but date can be omitted and is assumed to be today | |
--Time can be in 12- or 24-hour format, and 24-hour format is assumed if am/pm are absent | |
--Time can be either just the hour or colon separated (hour:minute) | |
--Numerical dates are listed as month/day/year and year isn't required | |
--Relative dates such as "Today" and "Tomorrow" can be used, as well as days of the week | |
--Relative dates are case instensitive and can be abbreviated, i.e. "mon" for "Monday" | |
--If the day of the week is entered as "Sunday" and today is Sunday, 7 days from today will be returned | |
--"Next" can be added to relative dates return an additional seven days from the appropriate day of the week */ | |
--Examples of valid reminders: | |
--Buy Milk #note about milk $Personal @10/7/2012 5:00 PM | |
--Buy Milk #note about Milk @10/7/2012 5:00 PM | |
--Buy Milk $Personal @10/7/2012 5:00PM | |
--Buy Milk @10/7/2012 5:00 PM | |
--Buy Milk @10/7/12 5:00pm | |
--Buy Milk @10/7/12 5pm | |
--Buy Milk @10/7 5pm | |
--Buy Milk @Tomorrow 5pm | |
--Buy Milk @tomorrow 5pm | |
--Buy Milk @tom 5pm | |
--Buy Milk @Sunday 5pm this returns 10/14/2012 if today is Sunday, 10/7/2012 | |
--Buy Milk @sunday 5pm this returns 10/14/2012 if today is Sunday, 10/7/2012 | |
--Buy Milk @sun 5pm this returns 10/14/2012 if today is Sunday, 10/7/2012 | |
--Buy Milk @Next Sunday 5pm this returns 10/21/2012 if today is Sunday, 10/7/2012 | |
--Buy Milk @next sun 5pm this returns 10/21/2012 if today is Sunday, 10/7/2012 | |
--Buy Milk @today 17 this assumes time is in 24-hour format (i.e. 5pm) | |
--Buy Milk @today 5 this also assumes time is in 24-hour format (i.e. 5am) | |
--Buy Milk @5pm this also assumes time is in 12-hour format | |
--Buy Milk @5 this also assumes time is in 24-hour format (i.e. 5am) | |
--set this to the name of the default Reminders list | |
property myDefaultList : "Personal" | |
-- for Alfred | |
on alfred_script(str) | |
set theResultString to my parse_reminder_string(str) | |
-- uncomment the next line to post a growl notification for Alfred version 1 | |
-- post_growl_notification(theResultString) | |
end alfred_script | |
-- for LaunchBar | |
on handle_string(str) | |
set theResultString to my parse_reminder_string(str) | |
-- post growl notification if available | |
post_growl_notification(theResultString) | |
end handle_string | |
-- subroutines | |
on parse_reminder_string(str) | |
--separate the time string and get the value | |
set arrayWithDate to my theSplit(str, "@") | |
if arrayWithDate's length > 1 then | |
set theDate to my parseDate(getArrayValue(arrayWithDate, 2)) | |
end if | |
--separate the Reminders list name if present | |
set arrayWithListName to my theSplit(getArrayValue(arrayWithDate, 1), "$") | |
if arrayWithListName's length > 1 then | |
set listName to my getArrayValue(arrayWithListName, 2) as string | |
else | |
set listName to myDefaultList | |
end if | |
--separate the note for the reminder if present | |
set arrayWithBody to my theSplit(getArrayValue(arrayWithListName, 1), "#") | |
if arrayWithBody's length > 1 then | |
set reminderBody to my getArrayValue(arrayWithBody, 2) | |
else | |
set reminderBody to "" | |
end if | |
set reminderName to getArrayValue(arrayWithBody, 1) | |
tell application "Reminders" | |
try | |
set listName to list listName | |
set newReminder to make new reminder with properties {name:reminderName, container:listName, body:reminderBody, remind me date:theDate} | |
set theReminderIDString to id of newReminder as string | |
set theResultString to my get_reminder_data(theReminderIDString) | |
on error | |
--set newReminder to make new reminder with properties {name:reminderName, container:listName, body:reminderBody} | |
set theResultString to "There was an error creating the reminder." | |
end try | |
end tell | |
return theResultString | |
end parse_reminder_string | |
on theSplit(theString, theDelim) | |
set oldDelimiters to AppleScript's text item delimiters | |
set AppleScript's text item delimiters to theDelim | |
set theArray to every text item of theString | |
set theTrimmedArray to {} | |
repeat with currentSplitString in theArray | |
if currentSplitString as string is not equal to "" then | |
set currentTrimmedString to trim(currentSplitString) | |
copy currentTrimmedString to the end of theTrimmedArray | |
end if | |
end repeat | |
set AppleScript's text item delimiters to oldDelimiters | |
return theTrimmedArray | |
end theSplit | |
on getArrayValue(array, location) | |
return item location in array | |
end getArrayValue | |
on parseDate(theDateStr) | |
-- todo: parsing pseudo-natural language date and time with applescript | |
-- is a pain and rife with special cases (bugs). should convert this | |
-- subroutine to python and use a real natural language parser. | |
set theDate to current date | |
set time of theDate to 0 | |
-- parse the time -- | |
-- applescript's text parsing seems very promiscuous | |
-- e.g. if letters end up part of the date, they are simply ignored rather than | |
-- producing an error. this is a lot of work to check for everything, so | |
-- it hasn't been implemented yet | |
-- first check for am/pm | |
set theDateStrOrig to theDateStr | |
set theDateStr to getArrayValue(theSplit(theDateStr, "am"), 1) as string | |
set theDateStr to getArrayValue(theSplit(theDateStr, "AM"), 1) as string | |
if theDateStrOrig is not in theDateStr then | |
set usesAM to true | |
else | |
set usesAM to false | |
end if | |
set theDateStrOrig to theDateStr | |
set theDateStr to getArrayValue(theSplit(theDateStr, "pm"), 1) as string | |
set theDateStr to getArrayValue(theSplit(theDateStr, "PM"), 1) as string | |
if theDateStrOrig is not in theDateStr then | |
set usesPM to true | |
else | |
set usesPM to false | |
end if | |
-- todo: should throw an error if usesPM and usesAM end up being true | |
-- split the string into date and time | |
set theDateTimeArray to theSplit(theDateStr, " ") | |
-- get the time, which is the last element of theDateTimeArray | |
set theTimeStr to getArrayValue(theDateTimeArray, -1) as string | |
set theTimeArray to theSplit(theTimeStr, ":") | |
-- get the time variables | |
set theHours to item 1 of theTimeArray | |
if theTimeArray's length > 1 then | |
set theMinutes to item 2 of theTimeArray | |
else | |
set theMinutes to 0 | |
end if | |
-- determine how to adjust the hours for am/pm | |
if (theHours as integer = 12) and usesAM then | |
set hoursAdjust to -12 | |
else if (theHours as integer ≠ 12) and usesPM then | |
set hoursAdjust to 12 | |
else | |
set hoursAdjust to 0 | |
end if | |
-- calculate the time | |
set time of theDate to (time of theDate) + (theHours + hoursAdjust) * 3600 | |
set time of theDate to (time of theDate) + theMinutes * 60 | |
-- now parse the date if present -- | |
if theDateTimeArray's length > 1 then | |
set theDateArray to items 1 thru -2 of theDateTimeArray | |
set theDateArrayLen to the count of items in theDateArray | |
-- first see if this is a relative date involving "next" | |
if theDateArrayLen = 2 then | |
set theRelativeDateStr to getArrayValue(theDateArray, 1) as string | |
set theWeekdayStr to getArrayValue(theDateArray, 2) as string | |
if "next" is in theRelativeDateStr then | |
-- setting forward 8 days which prevents one week from today being returned for "next sunday" if today is sunday | |
set time of theDate to (time of theDate) + (8 * 24 * 3600) | |
end if | |
set theDate to getWeekday(theDate, theWeekdayStr) | |
else | |
-- look for backslashes to decide if this date is relative or numerical | |
set theDateArraySplit to theSplit(getArrayValue(theDateArray, 1), "/") | |
set theDateArraySplitLen to the count of items in theDateArraySplit | |
if theDateArraySplitLen = 1 then | |
-- relative date without "next", i.e. today, tomorrow, monday, etc. | |
set theRelativeDayStr to getArrayValue(theDateArraySplit, 1) as string | |
if "tod" is in theRelativeDayStr then | |
set theDate to theDate | |
else if "tom" is in theRelativeDayStr then | |
set time of theDate to (time of theDate) + (24 * 3600) | |
else | |
-- this prevents today's date from being returned if current day of week is entered | |
set time of theDate to (time of theDate) + (24 * 3600) | |
set theDate to getWeekday(theDate, theRelativeDayStr) | |
end if | |
else | |
-- numerical date | |
set month of theDate to (item 1 of theDateArraySplit) as integer | |
set day of theDate to (item 2 of theDateArraySplit) as integer | |
try | |
set theNewYear to (item 3 of theDateArraySplit) as integer | |
if theNewYear < 100 then | |
set theCurrentYear to (year of theDate) as integer | |
set theNewYear to theNewYear + ((theCurrentYear / 100) as integer) * 100 | |
end if | |
set year of theDate to theNewYear | |
end try | |
end if | |
end if | |
end if | |
return theDate | |
end parseDate | |
on get_reminder_data(reminderIDString) | |
tell application "Reminders" | |
-- get the reminder reference | |
set theReminder to reminder id reminderIDString | |
-- get the reminder properties | |
set theRemProps to properties of theReminder | |
-- get the list properties | |
set theListProps to properties of container of theReminder | |
-- title, due date, note from reminder properties | |
set theTitle to name of theRemProps | |
set theDueDate to due date of theRemProps | |
set theNote to body of theRemProps | |
-- list name from list properties | |
set theListName to name of theListProps | |
end tell | |
-- create a string with info | |
set theString to "Created Reminder '" & theTitle & "' on list '" & theListName & "' due on " & theDueDate | |
if theNote is not "" then | |
set theString to theString & " with note '" & theNote & "'" | |
end if | |
set theString to theString & "." | |
return theString | |
end get_reminder_data | |
on post_growl_notification(theString) | |
-- crap that Growl needs before it will post a reminder | |
set allNotificationsList to {"Create Reminder Result"} | |
set enabledNotificationsList to {"Create Reminder Result"} | |
try | |
tell application "Growl" | |
register as application ¬ | |
"Create Reminder" all notifications allNotificationsList ¬ | |
default notifications enabledNotificationsList ¬ | |
icon of application "Reminders" | |
notify with name ¬ | |
"Create Reminder Result" title ¬ | |
"Create Reminder Result" description ¬ | |
theString application name "Create Reminder" | |
end tell | |
end try | |
end post_growl_notification | |
on getWeekday(theDate, theWeekdayStr) | |
repeat until theWeekdayStr is in ((weekday of theDate) as string) | |
set time of theDate to (time of theDate) + (24 * 3600) | |
end repeat | |
return theDate | |
end getWeekday | |
on trim(someText) | |
-- trimming subroutine from: http://macscripter.net/viewtopic.php?id=18519 | |
repeat until someText does not start with " " | |
set someText to text 2 thru -1 of someText | |
end repeat | |
repeat until someText does not end with " " | |
set someText to text 1 thru -2 of someText | |
end repeat | |
return someText | |
end trim |
Fixed a regression in setting the date when only the hour is present
Added the ability to return information from the created reminder as a string for error checking. This returned string can be sent to Growl (if installed) to post a notification.
For Alfred, the option to post the returned string via Growl has been commented out by default since the forthcoming version of Alfred will use this script via workflows and they have their own method of notification posting.
Note that posting notifications via Mountain Lion's Notification Center is not possible without incorporating additional tools (an application). I DO NOT plan to add notification center support to this script for this reason. (However, notification support can still be used within the workflows in Alfred 2.)
Hi
This sounds awesome, but I have some problems getting it to work. I installed it in Alfred 2 but it won't run when I enter e.g.: Køb mælk $Personal @Tomorrow 4pm
What am I doing wrong?
I forked and edited this to prevent an endless loop I was hitting when the date string returned by google calendar's IFTTT channel which I was trying to parse, did not have a day of the week in the string. Not sure if my edit is the most comprehensive, but it solved my problem:
https://gist.github.com/hepcat72/c34c2b96eba23a1aa424757d066aa07d
Updated the script to (hopefully) correct issues with handling times that have 12 for the hour and use the am/pm designation (12-hour clock). Also removed the parsing for seconds in the time as Reminders.app doesn't use seconds (oops).