Address Book URLs, revisited

In an earlier post I discussed the Mac’s addressbook URI scheme and how you can open a particular contact in your Address Book with a command like

open addressbook://A1A2AA41-FA30-40AE-9925-FD6DB270B0A5:ABPerson

from the Terminal. Everything after the double slash is the ID of the contact, accessible via the id property in AppleScript. In a similar way, you can create links in HTML documents which, when clicked, will open Address Book to the contact with that ID. (On my work computer, the above ID is for Apple, Inc.; the ID for Apple on your computer will be different.) I’ve been using such links a lot recently—for a serverless wiki-like system I’ll be writing about soon—and needed a utility for quickly extracting the ID of an Address Book contact.

The Address Book opener described in that earlier post used a pair of scripts—one AppleScript and one bash script. I never liked that setup, because the AppleScript was somewhat convoluted and because it used two script to do basically one thing. So even though the ID extraction script would be almost identical to the Address Book opener, I didn’t want to reuse my old code.

The script works like this: entering

abid john smith

at the Terminal prints the ID of the first contact with the names “john” and “smith.” Any number of arguments can be given to the abid command; the name of the contact must have each of the arguments as a substring. The search through the Address Book is case-insensitive. If no match is found, abid prints “No match!” instead of the ID.

Here’s the code. It’s written in Python using the appscript module.

 1:  #!/usr/bin/python
 3:  import sys
 4:  from appscript import *
 6:  # Find the contacts that have all the names in the given list.
 7:  def searchName(nameList):
 8:    names = [ x.lower() for x in nameList ]
 9:    # Get everyone whose last name matches.
10:    matches = [ x for x in app('Address Book').people.get() if names[-1] in ]
11:    # Look for matches with the other names if there are any.
12:    while len(names) > 1:
13:      del names[-1]
14:      matches = [ x for x in matches if names[-1] in ]
15:    # Return the list of matches.
16:    return matches
18:  # Print the ID of the top match. Or print an error message.
19:  try:
20:    print searchName(sys.argv[1:])[0].id.get().replace(':', '%3A')
21:  except IndexError:
22:    print "No matches!"

The searchName function does almost the same thing as the identically-named function in my old AppleScript, but is much shorter and the logic is cleaner. Which is why I wanted to get away from AppleScript. I suppose the really cool, Lispy way to write searchName would be to make it recursive instead of iterative, but the while loop works fine.

The name of a contact is the full name—prefix, first, middle, last, suffix—as a string. If the contact is a company, the name is the company name. The list comprehensions in Lines 10 and 14 are basically substring filters.

You can see in Line 20 that I’ve URL-encoded the colon near the end of the ID, replacing it with %3A. The encoding isn’t necessary to make the URL legal, but eliminating that colon from the string makes the ID easier to use in the serverless wiki I mentioned at the top of this post.

The program works through the argument list from back to front instead of front to back. I expected to give it the names in “first last” order, and I thought that filtering by last name first would be faster because identical last names are rarer than identical first names and the list comprehension in the while loop would therefore be working with smaller lists. This notion could be completely off base; I haven’t done any benchmarking.

There’s no need to pass abid more than one name if one of the contact’s names is unique. I have only one Adolf in my Address Book, so

abid adolf

is all I need to get his ID. Nicknames will work if the nickname is a subset of the name given in Address Book. For example, if someone is listed as Robert Johnson, then

abid rob johnson

will find him, but

abid bob johnson

will not.

This script would likely be unnecessary if Address Book would show us the ID and let us copy it into the clipboard. But I haven’t found any way to get Address Book to show us the ID; if you know of a way, I’d like to hear about it.