Skip to content

Instantly share code, notes, and snippets.

@afragen
Last active August 15, 2020 12:12
Show Gist options
  • Save afragen/5177811 to your computer and use it in GitHub Desktop.
Save afragen/5177811 to your computer and use it in GitHub Desktop.
Script for automatically extracting calendar items from meeting requests in Mail.app and adding them into iCal. It will always extract into the first local calendar.
(*
Script for automatically extracting calendar items from meeting requests
and adding them into iCal. It will always extract into the first local calendar.
written by Andy Fragen <andy@thefragens.com>
modified to run from AppleScript menu
http://www.macosxhints.com/article.php?story=20060821073102694
http://bbs.macscripter.net/viewtopic.php?id=17655
Special thanks to Martin Herrera Soler for loads of exceptions. ;-)
Special thanks to Michael Bartz showing me replies.
Abstracted the grep and sed to work better with other Exchange invites.
05 Aug 2008 - Fixed to run as rule
07 Aug 2008 - Fixed for invites as attachments only
11 Aug 2008 - Cleaned up code and fixed for METHOD:PUBLISH
12 Aug 2008 - Fixed code to work with METHOD:CANCEL
14 Aug 2008 - Cleaned up code, user will still need to manually delete cancelled events
05 Sept 2008 - Fixed sed substitution to remove quotes from DTSTART;TZID and DTEND;TZID, more CalDAV compliant
29 Sept 2008 - Fixed for problem when attachment name identical then latest .ics attachment not saved, move /tmp/*.ics to Trash
03 Dec 2008 - Fixed to remove modal dialog from METHOD:PUBLISH invites by changing to METHOD:REQUEST, fixed a few things
28 Dec 2008 - Added ability to allow for multiple exchange servers with only one script
13 Jan 2009 - Removed need for defining calendar for cancelling events
08 Sept 2009 - Fixed for goofy Snow Leopard problem openning .ics file
29 Sept 2009 - Added ability to fix event replies, Simplified parts of the script and cleaned up code
30 Sept 2009 - Cleaned up code some more
*)
--The following 2 properties allow for use with multiple Exchange Servers. You must have the exchange_fragment in the same list position as its ical_tzid
-- Add or change these values to include a unique fragment of the Exchange Server's TZID
property exchange_fragment : {"Central", "GMT -0800", "Montevideo", "Perth"}
--Change these values to what your iCal time zone expects. This is *not* your local timezone. It is the timezone of the Exchange server in proper iCal format. No spaces allowed; but they will be fixed.
property ical_tzid : {"US/Central", "US/Pacific", "America/Buenos Aires", "Australia/Perth"}
using terms from application "Mail"
on perform mail action with messages theMessages for rule theRule
set thepath to ((path to startup disk) as string) & "tmp:temp_invite.ics"
my make_iCal_Event(thepath, theMessages)
end perform mail action with messages
end using terms from
on run {}
set thepath to ((path to startup disk) as string) & "tmp:temp_invite.ics"
tell application "Mail" to set theMessages to selected messages of message viewer 1
my make_iCal_Event(thepath, theMessages)
end run
on make_iCal_Event(thepath, theMessages)
repeat with theMessage in theMessages
set flRequest to false
set flPublish to false
set flCancel to false
set flReply to false
set theInvite to ""
tell application "Mail" to set theSource to the source of theMessage
tell application "Mail" to set theSubject to the subject of theMessage
(* Get the ics data *)
if (the offset of "content-class: urn:content-classes:calendarmessage" in theSource) is not equal to 0 then
(* Find the range of the message source that is an ics message
Note: this works both on messages that detect the .ics attachement,
and on crappy Exchange invites that show up as an owa url. *)
set vcalBegin to the offset of "BEGIN:VCALENDAR" in theSource
set vcalEnd to (the offset of "END:VCALENDAR" in theSource) + (the length of "END:VCALENDAR")
set theInvite to the text vcalBegin thru vcalEnd of theSource
else
set theInvite to my icsAttachment(theMessage)
end if
(* Only deal with requests *)
if (the offset of "METHOD:REQUEST" in theInvite) is equal to 0 then
(* do nothing *)
else
set flRequest to true
end if
(* Only deal with publishes *)
if (the offset of "METHOD:PUBLISH" in theInvite) is equal to 0 then
(* do nothing *)
else
set flPublish to true
end if
(* Only deal with cancellations *)
if (the offset of "METHOD:CANCEL" in theInvite) is equal to 0 then
(* do nothing *)
else
set flCancel to true
end if
(* Only deal with replies *)
if (the offset of "METHOD:REPLY" in theInvite) is equal to 0 then
(* do nothing *)
else
set flReply to true
end if
set thepath to my writeEvent(thepath, theInvite)
my process_event(thepath, flRequest, flPublish, flCancel, flReply, theSubject)
end repeat
tell application "Mail" to set theSource to ""
tell application "Mail" to set theInvite to ""
(* delete the file *)
do shell script "mv /tmp/*.ics ~/.Trash/"
end make_iCal_Event
on writeEvent(thepath, theInvite)
(* write to a temp file *)
set fh to open for access thepath with write permission
set eof fh to 0
write theInvite to fh as string
close access fh
return thepath
end writeEvent
on icsAttachment(theMessage)
tell application "Mail"
if theMessage's mail attachments is not {} then
repeat with theAttachment in theMessage's mail attachments
set attachmentName to the name of theAttachment
if attachmentName ends with ".ics" then
set attachmentName to do shell script "echo \"" & attachmentName & "\" | sed 's/ //g'"
set theFileName to ((path to startup disk) as string) & "tmp:" & attachmentName
try
save theAttachment in theFileName
on error errnum
end try
else
return
end if
end repeat
end if
end tell
return read file theFileName
end icsAttachment
on process_event(thepath, flRequest, flPublish, flCancel, flReply, theSubject)
set tPath to POSIX path of thepath
if flRequest then
my fix_TZID(thepath)
else if flPublish then
do shell script "sed 's|METHOD:PUBLISH|METHOD:REQUEST|g' " & tPath & " > " & tPath & "2"
do shell script "mv " & tPath & "2 " & tPath
else if flCancel then
my fix_TZID(thepath)
else if flReply then
if not my fix_TZID(thepath) then
ignoring case
if theSubject contains "accept" then
set theStatus to "ACCEPTED"
else if theSubject contains "declined" then
set theStatus to "DECLINED"
else
set theStatus to "TENTATIVE"
end if
end ignoring
do shell script "sed 's|ORGANIZER|ATTENDEE|g;s/:MAILTO/;PARTSTAT=" & theStatus & ":MAILTO/g' " & tPath & " > " & tPath & "2"
do shell script "mv " & tPath & "2 " & tPath
end if
end if
(* open in iCal *)
tell application "Calendar" to activate
try
tell application "Calendar" to open alias thepath
on error
--Snow Leopard kludge
set tPath to POSIX path of thepath
do shell script "open " & tPath
end try
end process_event
on fix_TZID(thepath) --fix for Outlook TZID weirdness
set tPath to POSIX path of thepath
repeat with i from 1 to the length of ical_tzid
--this should automatically grab the TZID that Exchange gives
set exchange_tzid to do shell script "grep -o ^TZID.*$ " & tPath & " | sed -e 's|TZID:\\(.*\\)|\\1|g'"
if exchange_tzid contains item i of exchange_fragment then
--make sure user input doesn't have spaces :-)
set item i of ical_tzid to do shell script "echo \"" & item i of ical_tzid & "\"| sed 's/ /_/g'"
--possible fix if TZID is on 2 lines (unable to test)
--set exchange_tzid to do shell script "sed -n '/TZID:/{N;p;}' " & tPath & " | sed 's/TZID://g' | tr -d '\\n'"
set quoted_ical_tzid to "\"" & item i of ical_tzid & "\""
if exchange_tzid is not "" then
do shell script "sed 's|" & exchange_tzid & "|" & item i of ical_tzid & "|g;s|" & quoted_ical_tzid & "|" & item i of ical_tzid & "|g' " & tPath & " > " & tPath & "2"
do shell script "mv " & tPath & "2 " & tPath
end if
end if
end repeat
if exchange_tzid is "" then return false
return true
end fix_TZID
@cerniuk
Copy link

cerniuk commented Aug 15, 2020

That's an impressive amount of work. Kudos to you!! (and shame on Apple for making it necessary.)

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