Slim Kiwi Technology
Sending myself a daily agenda using AppleScript, iCal, Things & Mail.app

I’ve been using Things for a while now and recently read up on some of the wiki posts for getting the most out of the app, primarily those documenting scripting Things using AppleScript to maximize productivity.

When I get into the office in the morning, I tend to jump into work or get distracted by something interesting, and don’t necessarily stick to checking my calendar and To Do list diligently. I do, however, check my email on my iPhone during my walk in. Having an email listing my appointments and tasks for the day would be perfect.

I use Things on my laptop (my primary computer) and my iPhone. I shut my laptop down occasionally, so relying on it for any sort of automation wouldn’t work 100% of the time. Step 1: Synch Things with my office iMac. My appointments in iCal are already in place, I use .mac and Spanning Sync to sync my calendars across computers and with my Google Calendar, and Mail syncs accounts using .mac as well. For this, I’ll be using a neutral email account to send the agenda to my slimkiwi.com address: My .mac account, set as the default account in Mail.

With all that in place, it just comes down to writing AppleScript against the three apps. Fortunately I found some quick examples on the Web to toss together, documented in the following script:

-- This first portion came from:
-- http://www.perceptiveautomation.com/phpBB2/viewtopic.php?p=23652&sid=ba05d6b94df919be25bdf8e0721df1ee#23652
set Now to current date

set TodaysDate to date string of Now
set MidnightToday to date TodaysDate
-- Needs to run in the AM for the +1 to work
set MidnightTomorrow to (date TodaysDate) + (1 * days)

-- These two vars are mine, to build up the string for the email
set Agenda to ""
set ToDoList to ""

tell application "iCal"
	-- Work and Home are my calendars, use your own
	set AllCalendars to {calendar "Work", calendar "Home"}
	
	repeat with EachCalendar in AllCalendars
		set CalendarName to name of EachCalendar
		
		set TodaysEvents to (every event of EachCalendar ¬
			whose (start date is greater than MidnightToday and ¬
			end date is less than MidnightTomorrow) or ¬
			(start date is MidnightToday and allday event is true))
		repeat with EachEvent in TodaysEvents
			if contents of EachEvent is not missing value then
				set TheEvent to contents of EachEvent
				set EventProperties to properties of TheEvent
				set EventName to summary of EventProperties
				set EventLocation to location of EventProperties
				set EventDescription to description of EventProperties
				
				if not (allday event of EventProperties) then
					set EventTime to "at " & time string of start date of EventProperties
					if start date of EventProperties comes before Now then
						set EventStillToCome to false
					else
						set EventStillToCome to true
					end if
				else
					set EventTime to ", All Day"
					set EventStillToCome to true
				end if
				
				if EventStillToCome then
					set EventAttendees to attendees of TheEvent
					set AttendeeNames to ""
					repeat with EachAttendee in EventAttendees
						set AttendeeName to display name of EachAttendee
						set AttendeeNames to AttendeeNames
					end repeat
					
					-- Start building message 
					-- The original code appears below. I shortened it to a 1-line summary
					set EventMsg to "* "
					set EventMsg to EventMsg & CalendarName & ": " & EventTime & ", " & EventName
					
					-- set EventMsg to EventMsg & "From the " & CalendarName & " Calendar, "
					-- set EventMsg to EventMsg & "Today " & EventTime & ", "
					-- set EventMsg to EventMsg & "is the " & EventName
					-- if EventLocation is not in {missing value, ""} then ¬
					-- set EventMsg to EventMsg & ", at " & EventLocation
					-- set EventMsg to EventMsg & ". "
					-- if EventDescription is not in {missing value, ""} then ¬
					-- set EventMsg to EventMsg & "The notes indicate: " & EventDescription & ". "
					-- if EventAttendees is not {} then ¬
					-- set EventMsg to EventMsg & "Expected attendees are " & AttendeeNames & ". "
					-- display dialog EventMsg
					
					-- Build up agenda list:
					set Agenda to Agenda & EventMsg & "
"
				end if
			end if
		end repeat
	end repeat
	-- Pause before quitting the app
	delay 5
	quit
end tell

-- display dialog Agenda & "
--
-- "
-- Found Things code at
-- http://culturedcode.com/things/wiki/index.php/Log_Today_List_%28AppleScript%29
tell application "Things"
	repeat with aToDo in to dos of list "Today"
		
		-- Pretty simple, first hit project to do, then area to do, then individual to do
		if (project of aToDo) is not missing value then
			set ToDoList to ToDoList & "* " & (name of project of aToDo) & ": " & name of aToDo & "
"
		else if (area of aToDo) is not missing value then
			set ToDoList to ToDoList & "* " & (name of area of aToDo) & ": " & name of aToDo & "
"
		else
			set ToDoList to ToDoList & "* " & "" & name of aToDo & "
"
		end if
		
	end repeat
	-- Pause before quitting the app
	delay 5
	quit
end tell

-- Email code is from:
-- http://mac.appstorm.net/how-to/applescript/the-ultimate-beginners-guide-to-applescript/
-- I'll be running this on an iMac I leave running in the office, with my .mac account
-- set up as the default mail account. iCal will sync with .mac and gCal (via Spanning Sync)
-- so it'll be fresh. Things will sync via Dropbox directory.

--Variables
set recipientName to "Mark J. Reeves"
set recipientAddress to "me@slimkiwi.com"
set theSubject to "Today's Agenda"
set theContent to Agenda & "

" & "To Do List:
" & ToDoList

--Mail Tell Block
tell application "Mail"
	
	--Create the message
	set theMessage to make new outgoing message with properties {subject:theSubject, content:theContent, visible:true}
	
	--Set a recipient
	tell theMessage
		make new to recipient with properties {name:recipientName, address:recipientAddress}
		
	end tell
	--Send the Message
	send theMessage
	
	-- Give the message time to send
	delay 30
	quit
end tell

There you have it: An AppleScript you can run or schedule to pull today’s appointments and To Do list together in a summary email that will land in your Inbox, relying on your favorite productivity apps.

Know a good LAMP Sysadmin?

Fresh on the heels of touting my success in setting up satisfactory backup scripts on my server, I’m looking for help with some deeper server admin tasks. The basics of setting up and managing a web server are pretty well documented via Google, but there are some tasks and processes that merit expertise.

If you’re a freelance Sysadmin type, we (and some of our independent colleagues) would love to have a relationship with someone we can call on - and pay - when the chips are down or the task is a bit deeper than we can fathom.

Learn more at http://www.slimkiwi.com/jobs/#sysadmin

Backup scripts on my Media Temple (dv), using Amazon S3, that I'm happy with

My (dv) 3.5 at Media Temple is hosting a few Magento sites. With the configuration customized to support them, I’m wary of messing with an upgrade, and an upgrade to Plesk is needed to fully support scheduled backups posted to another server.

A few quick Google searches last night and a few hours spent got me up and running with a great backup solution that stores everything on Amazon’s S3 hosting service.

  1. Used this guy’s script examples to create archives of vhost directories and mysqldumps of databases on the server. I put everything in /skbackup with a ./backups directory and a ./scripts directory within. 1 script each for vhosts and databases, per client, so they can be staggered when scheduled in crontab.
  2. Used this guy’s script to archive the entire ./backups directory and move it to Amazon S3, which I signed up for last night. His script accommodates a POSTCALLBACK script call, which I use to call a purge_backups script that deletes everything within ./backups once they’ve all been moved to Amazon. Nothing stays on the server.
  3. #2’s script references S3 cmd, which worked great. I just uploaded the app to a directory within my ./scripts directory and used the documented Python installer.

Everything was easy to manually test and came together quite nicely. The S3 backup script sends emails upon error and completion and I built logging into each step. I’m glad to finally have that out of the way (and to continue to switch from Plesk tools to the command line).

October weekday morning in Salem.

October weekday morning in Salem.

Tumblr vs. Posterous

It’s on.

ExpressionEngine: Category subnav with entry counts

To create a category subnav for a weblog in ExpressionEngine, that includes entry counts, place this inside an

{exp:query sql=""}

tag:

Select c.cat_id, c.cat_name, c.cat_url_title, count(cp.entry_id) number
from exp_categories c join exp_category_posts cp
on c.cat_id = cp.cat_id
join exp_weblog_titles wt on cp.entry_id = wt.entry_id
where c.group_id = 5
and wt.status = 'open' and wt.weblog_id = 6
group by c.cat_id, c.cat_name, c.cat_url_title order by c.cat_name asc

You’ll have the category name and category URL title to link to. {number} will give you the entry count.

Replace 5 with your own category group ID and 6 with your own weblog ID. This will only display counts for entries with status = ‘open’.