Posts Tagged ‘applescript’
Scheduling to-dos
April 15th, 2010 at 4:01 pm
Geez, it’s been a while since I last posted an original script here, so let’s fix that.
One of the things I miss about OmniFocus is its ability to hide tasks until a given date. The advantage of being able to do this is that you can add the task when it comes to you, but you won’t see it as part of your regular to-do list until the time is ripe. My favorite example is the recurring task of changing my furnace filter, which I do every 3-4 months. I liked how OmniFocus allowed me to enter this task but kept it out of my Next Actions list until a week or so before I was supposed to do it.
TaskPaper, my current to-do-list manager, doesn’t have this feature. TaskPaper’s greatest strength, and the reason I actually use it, is its simplicity, but this is one area where a little complexity would help. Since TaskPaper’s “database” of to-dos is just a simple text file, it’s easy to write scripts that extend its features (like this one), and last week I started planning a script that would automatically add scheduled tasks to my main to-do list. After a bit of thinking, though, I stopped.
Here’s the thing: I’ve never stuck with any particular task managing system, software- or paper-based, for more than a year at a time. As much as I like TaskPaper now, why should I assume I’ll still be using it a year from now? Why should I invest the time in writing a script that will be used for only a small percentage of my to-dos and may never be used after Christmas? I decided to go for a longer-lasting solution.
I’ve been using iCal and Mail continuously for over five years, ever since returning to the Mac from Linux. If I’m at my computer, they’re running. So the most robust system, it seemed, would be one that emails me a reminder when it’s time to add a task to my to-do list. Although this system won’t add the entry automatically, it will work no matter what task manager I use.
First, I created a new iCal calendar called tasks.

Every entry in this calendar will be an item that should go into my to-do list on the specified date. I keep this calendar unchecked, and therefore invisible, except when entering a scheduled task, because these aren’t part of what David Allen calls the “hard landscape,” and so, according to GTD orthodoxy, shouldn’t be in my calendar at all. In fact, I don’t consider the tasks entries as part of my calendar, per se—they’re merely using iCal as a convenient mechanism for running scripts on a schedule.
Here’s my entry for replacing the furnace filter.

It’s a recurring event that appears quarterly on the morning of the first day of the month. The summary, “Change furnace filter,” is the task to be added to my to-do list on that day. When the scheduled time arrives, it runs an AppleScript called Email Scheduled Task:
1: set rightNow to current date
2: set fiveMinutesAgo to rightNow - 5 * minutes
3: set fiveMinutesFromNow to rightNow + 5 * minutes
4:
5: tell application "iCal"
6: set theEvents to every event of calendar "tasks" whose start date comes after fiveMinutesAgo and start date comes before fiveMinutesFromNow
7: set theTask to the summary of item 1 of theEvents
8: end tell
9:
10: tell application "Mail"
11: set theMessage to make new outgoing message with properties {visible:true, subject:"Scheduled Task", content:theTask}
12: tell theMessage
13: make new to recipient at end of to recipients with properties {name:"Dr. Drang", address:"user@example.com"}
14: end tell
15: send theMessage
16: end tell
The script does three things:
- It gets the current time and sets up a ten-minute window around it.
- It looks through the tasks calendar for entries within that window of time and gets the summary of the first one.
- It sends an email to me with that summary as the content and “Scheduled Task” as the subject.
A time window of some sort was needed because the script’s call to current date will almost certainly not be exactly equal to the entry’s start date. A window as wide as ten minutes is probably unnecessary, but I was feeling generous. As long as I don’t create entries with overlapping windows—a very easy restriction to satisfy—the theEvents list returned by Line 6 will have just one item, and therefore Line 7 will grab only the task that triggered the script.
(If you’re wondering why there isn’t an easier way to get the event that triggered the script, join the club. I couldn’t find one.)
I learned how to do the date math in Lines 2 and 3 from the AppleScript Language Guide and how to set up and send an email message from this MacTech article.
On the morning of July 1, the message “Change furnace filter” will be in my inbox when I get to work. I’ll select and copy the content text and paste it into TaskPaper (or whatever I’m using by then). Presumably, I’ll change the filter shortly thereafter and scratch it off my list. Repeat on October 1, January 1, etc.
My script hall of fame
January 27th, 2010 at 1:25 pm
Many of my posts here have been about the writing—or rewriting or rerewriting—of scripts to automate the dull, repetitive, clicky-click tasks so common to computer use. While almost all of these scripts have been worthwhile, a few have proved so useful that I use them on a weekly or even daily basis. These are the member of my personal scripting hall of fame.
Folder labels
I like the project file folders in my office to have crisp laser-printed labels. There are plenty of templates out there for Avery labels and their clones, but they’re usually made for MS Word or some other program I don’t use. Also, there’s a lot of repetitive typing associated with those templates, and I obviously want to avoid that. So I wrote a script called pflabels that takes plain text input in a simple format and generates output for printing on a sheet of 1″×4″ labels (Avery 5261 or the equivalent).

The input looks like this:
#Kernighan Building|4215
Drawings
Contract
Correspondence
Photographs and
videotape
#Ossanna Residence|4332
Report
Correspondence
The headings, which have the project name and number separated by the pipe character (|), are denoted with an initial hash symbol (#), and individual labels are separated by blank lines. If I’m going to make several labels for the same project (which is usually the case), I need only enter the heading once. The script takes two options, -r and -c, which tell it which row and column to start on, so it can print on label sheets that have been partially used.
I generally type up my label input in TextMate and then run pflabels on that input via the Text>Filter Through Command… (⌥⌘R) command.
Screenshot uploader
This script, called snapftp:
- Takes a snapshot of some portion of my computer screen.
- Names it.
- (Optionally) resizes it.
- (Optionally) uploads it to my blog.
- Puts the URL of the uploaded image on the clipboard for pasting into a post.
I’ve configured FastScripts to run snapftp with the ⌃⌥⌘4 key combination, a minor variation on the Apple-standard ⌘⇧4 key combo.

There are certainly commercial products that do some or all of these things, but none are so perfectly tuned to my way of working. I can’t imagine going back to writing posts like this without it.
Markdown links in TextMate
I write almost everything in Markdown, mainly because I can forget about the formatting and just type, but also because it’s so easy to read. To keep the visual clutter to an absolute minimum, I use reference-style links, which puts all the URLs at the bottom of the document, out of the flow of the text.

I use three TextMate command/snippet/macros to do this:
- One for inserting links as I type, triggered by ⌃L.
- One for inserting links after I’ve already typed out the text, triggered by ⌃⌥L.
- One for inserting Google’s I Feel Lucky link on text already typed, triggered by ⌃⌥⇧L.
The first two are described here, and the third is described here. They’re a bit complicated to set up but are wonderfully simple to use. I should probably turn them into a Bundle for easier installation.
URL getters for TextExpander
This started out as a set of scripts for getting the URL (or a shortened version of the URL) of the page showing on the visible tab of the frontmost Safari window, and the scripts were triggered by key combinations defined in FastScripts. Then, after seeing this tip by Jeff Gamet, I turned them into a set of TypeIt4Me snippets. When I switched from TypeIt4Me to TextExpander, I moved the snippet over and that’s what I use today.
The two workhorses are:
- The snippet triggered by
;furl, which gets the aforementioned URL and inserts it. - The snippet triggered by
;surl, which gets the URL, shortens it through the Metamark shortening service (run by perl.org), and inserts it.
I tend to use the first when writing posts like this or email and the second when using links on Twitter. The great advantage is that I can add a link to a page I’ve been reading without having to switch back to Safari to get it.
More recently, I added a few more snippets for getting the URLs of the first, second, third, and fourth Safari tabs, regardless of whether they are frontmost.
BBC Radio recording scripts for Audio Hijack Pro
These scripts, written in Python and AppleScript, differ from those described above in that I never actually run these scripts myself. They’re run automatically on a schedule set in Audio Hijack Pro, and they record and tag certain BBC Radio 2 shows and put them into my iTunes library for syncing with my iPod. In effect, they turn shows that aren’t podcasts into podcasts for me.
The scripts are described here and they’re also available in this GitHub repository.
Library loan tracking
This script, like the set of BBC scripts, is run automatically—in this case, by a launchd process. Every morning, it logs in to my local library and gathers information on all the items my family has checked out or on hold. It then sends that information to my wife and me in a nicely formatted email.

Now it’s true that my library emails us a notice shortly before an item is due, but the advantage of this system is that we see everything at once and can gather up all the books that will be due in the next several days before going to the library to make a return. And we don’t have to remember to sign on to the library’s web site; the information is delivered to us every day.
Suspend and sleep screen
This is a pretty recent script, but I’ve come to love it. Without logging me out, it suspends my user session and puts the display to sleep. Seeing the Desktop swing around in that cube animation (which I’ve been a fan of since Andy Hertzfeld used a simplified version of it in Switcher) has become the visual signal that my day at work is over.
One-off recordings with Audio Hijack Pro
December 15th, 2009 at 7:05 pm
I’ve written once or twice about using Audio Hijack Pro to record a few weekly BBC radio shows from their internet streams. I use the BBC’s Listen Again feature to schedule the recordings not when the shows are running, but late at night when I’m not using my office computer. The BBC also runs irregular programs of just one or two episodes that I like to record. In the past, I’ve always just recorded these one-off shows when I see that they’re available; there was no obvious way to schedule one-time recordings of these programs because I wouldn’t know the stream URL in advance.
Since the BBC revamped its web site, the stream URLs for upcoming shows are easy to determine. Just go to the page with the show’s description and copy the eight-character string at the end of the URL

The stream URL will be that eight-character string appended to the end of http://bbc.co.uk/iplayer/console/.
I’ve set up a dummy recording session in Audio Hijack Pro called “BBC” that has all my usual settings. When I want to record a one-time show, I make a copy of it and change the URL, the schedule, and the tags.




An AppleScript called “Add Show to iTunes Radio Playlist” runs after the recording is made. It’s a simplified version of the AppleScripts I use for the weekly programs; it does everything they do but get a track listing from the show’s web page.
1: (* Audio Hijack Script *** (C) Copyright 2003-2007, Rogue Amoeba Software, LLC
2: "set shufflable" and "set bookmarkable" lines added on 20070725
3: "add to Radio shows" line added on 20080904 *)
4:
5: on process(theArgs)
6:
7: --Coerce args to be a list
8: if class of theArgs is not list then
9: set theArgs to {theArgs}
10: end if
11:
12: --Into iTunes ye files shall go
13: tell application "iTunes"
14: repeat with theFile in theArgs
15: set aTrack to (add theFile)
16: delay 60
17: set bookmarkable of aTrack to true
18: set shufflable of aTrack to false
19: add (get location of aTrack) to playlist "Radio shows"
20: end repeat
21: end tell
22:
23: end process
As you can see from the comments at the top, the script is adapted from an example provided by Rogue Amoeba. My additions are Lines 16-19, which set a couple of track properties and put the track in my “Radio shows” playlist. The delay 60 command provides plenty of time for the track to get into iTunes before we try to set its properties. I’ve found that if I run the script without the delay, Lines 17-19 don’t do anything. The script is saved in the ~/Library/Application Support/Audio Hijack Pro/Recording Scripts folder.
One last script. A couple of years ago I wrote a Python script that printed out the schedule of of weekly shows to be recorded. With the addition of a few lines, it now prints out both the weekly and one-off shows:
God's Jukebox: S------ from 12:30 AM to 3:35 AM (185 min)
Monty Python's Wonderful World of Sound: --t---- from 10:30 PM to 11:35 PM (65 min)
Sounds of the 60s: S------ from 12:10 PM to 2:15 PM (125 min)
Sounds of the 70s: -M----- from 8:00 PM to 10:05 PM (125 min)
Trevor Nelson Soul: ---W--- from 8:00 PM to 9:05 PM (65 min)
The weekly shows and one-offs are distinguished by the case of the letter indicating the day of the week. Notice that tonight’s Monty Python show uses a lowercase t instead of uppercase. The script is called ahtimers, and it uses the appscript library.
1: #!/usr/bin/env python
2:
3: from appscript import *
4: import datetime
5:
6: # Get a list of all the sessions.
7: allsessions = app('Audio Hijack Pro').sessions.get()
8:
9: # Make a list with the sessions that have scheduled timers.
10: timersessions = []
11: for s in allsessions:
12: for t in s.timers.get():
13: if t.scheduled():
14: timersessions.append(s)
15: break # go to next session after finding a scheduled timer
16:
17: # Get the length of the longest name of a timersession.
18: longest = max(len(s.name()) for s in timersessions)
19:
20:
21: # Make a 7-character string with the days that the timer runs.
22: def timerdays(t):
23: daylist = ['-'] * 7
24:
25: # Put a lowercase letter in daylist where the next recording is scheduled.
26: dstring = 'smtwtfs'
27: wday = (t.next_run_date.get().weekday() + 1) % 7 # convert from Mon=0 to Sun=0
28: daylist[wday] = dstring[wday]
29:
30: # Put uppercase letters in daylist where regular recordings are scheduled. This
31: # may overwrite the lowercase from the section above.
32: if t.runs_Sunday():
33: daylist[0] = 'S'
34: if t.runs_Monday():
35: daylist[1] = 'M'
36: if t.runs_Tuesday():
37: daylist[2] = 'T'
38: if t.runs_Wednesday():
39: daylist[3] = 'W'
40: if t.runs_Thursday():
41: daylist[4] = 'T'
42: if t.runs_Friday():
43: daylist[5] = 'F'
44: if t.runs_Saturday():
45: daylist[6] = 'S'
46: return ''.join(daylist)
47:
48:
49: # Print the info for all the sessions with enabled timers.
50: for s in timersessions:
51: for t in s.timers.get():
52: if t.scheduled():
53: dur = t.duration()
54: durstr = '(%d min)' % (dur/60)
55: st = t.start_time()
56: et = st + datetime.timedelta(seconds = dur)
57: dow = timerdays(t)
58: ststr = st.strftime("%l:%M %p")
59: etstr = et.strftime("%l:%M %p")
60: fmtstr = "%" + str(longest) + "s: %s from %s to %s %s"
61: print fmtstr % (s.name(), dow, ststr, etstr, durstr.rjust(9))
The additional lines are 25-28. Two things are worth noting:
appscriptuses Python’sdatetimemodule when returning date information, anddatetimetakes Monday to be the beginning of the week. Since I consider Sunday the beginning of the week, Line 27 converts the output ofweekday()to my convention.- Both one-time and weekly timers will return a value for
next_run_date, so thedayliststring will have lowercase letters for both types of program after Line 28. Lines 30-46 go on to overwrite the lowercase letters with uppercase letters for the weekly timers.
When I get to the office tomorrow morning, the Monty Python documentary will waiting for me, ready to be synced to my iPod.










