Created
January 23, 2016 11:12
-
-
Save bluegraybox/1c44abbfb1b8c8b9ba88 to your computer and use it in GitHub Desktop.
Script to export contact information from OS X Contacts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/local/bin/python | |
from collections import defaultdict | |
import pprint | |
import sqlite3 | |
import sys | |
ADDRESS_DB_FILE = 'Contacts-2016-01-23.abbu/AddressBook-v22.abcddb' | |
def main(): | |
dbfile = sys.argv[1] if len(sys.argv) > 1 else None | |
con = sqlite3.connect(dbfile or ADDRESS_DB_FILE) | |
con.row_factory = sqlite3.Row | |
with con: | |
cur = con.cursor() | |
contact_index = {} | |
cur.execute(recordSql) | |
rows = cur.fetchall() | |
for row in rows: | |
# print dict(row) | |
cid = row['Z_PK'] | |
d = dict(row) | |
# del d['Z_PK'] | |
d = filterNoneValues(d) | |
if d: | |
contact_index[cid] = filterNoneValues(d) | |
addElements(contact_index, emailSql, cur, 'ZOWNER', 'email') | |
addElements(contact_index, mailSql, cur, 'ZOWNER', 'mail') | |
addElements(contact_index, phoneSql, cur, 'ZOWNER', 'phone') | |
addElements(contact_index, imSql, cur, 'ZOWNER', 'im') | |
addElements(contact_index, noteSql, cur, 'ZCONTACT', 'note') | |
contacts = contact_index.values() | |
contacts.sort(cmpContacts) | |
# pprint.pprint(contacts, None, 4) | |
people = [] | |
ddl = lambda: defaultdict(list) | |
for c in contacts: | |
person = {'locations': defaultdict(ddl)} | |
nickname = c.get('ZNICKNAME') | |
if nickname: | |
nickname = '"%s"' % nickname | |
maidenname = c.get('MAIDENNAME') | |
if maidenname: | |
maidenname = '"%s"' % maidenname | |
name = [c.get('ZTITLE'), c.get('ZFIRSTNAME'), nickname, c.get('ZMIDDLENAME'), c.get('ZLASTNAME'), maidenname, c.get('ZSUFFIX')] | |
name = filter(lambda x: x, name) | |
if not name: | |
continue | |
person['name'] = ' '.join(name) | |
people.append(person) | |
email = c.get('email') | |
if email: | |
for e in email: | |
if e.get('ZADDRESS'): | |
person['locations'][e.get('label')]['email'].append(e.get('ZADDRESS')) | |
mail = c.get('mail') | |
if mail: | |
for m in mail: | |
addr = [m.get('street'), m.get('ZCITY'), m.get('ZSTATE'), m.get('ZZIPCODE'), m.get('ZCOUNTRYNAME'), m.get('ZREGION'), m.get('ZSAMA')] | |
addr = filter(None, addr) | |
# addr = filter(lambda x: x, addr) | |
person['locations'][m.get('label')]['mail'].append(', '.join(addr)) | |
phone = c.get('phone') | |
if phone: | |
for p in phone: | |
num = [p.get('ZAREACODE'), p.get('ZCOUNTRYCODE'), p.get('ZEXTENSION'), p.get('ZLOCALNUMBER'), p.get('fullnumber')] | |
num = filter(None, num) | |
person['locations'][p.get('label')]['phone'].append(', '.join(num)) | |
im = c.get('im') | |
if im: | |
for e in im: | |
if e.get('ZADDRESS'): | |
if not person.get('IM'): | |
person['IM'] = [] | |
person['IM'].append('%s (%s)' % (e.get('ZADDRESS'), e.get('ZSERVICENAME'))) | |
notes = c.get('note') | |
if notes: | |
for e in notes: | |
if e.get('text'): | |
if not person.get('notes'): | |
person['notes'] = [] | |
person['notes'].append(e.get('text')) | |
for p in people: | |
print p['name'] | |
locs = p['locations'].items() | |
if len(locs) > 1: | |
for k, v in p['locations'].items(): | |
print ' %s' % k | |
for x in v['mail']: | |
print ' %s' % x | |
for x in v['phone']: | |
print ' %s' % x | |
for x in v['email']: | |
print ' %s' % x | |
else: | |
_, v = locs[0] | |
for x in v['mail']: | |
print ' %s' % x | |
for x in v['phone']: | |
print ' %s' % x | |
for x in v['email']: | |
print ' %s' % x | |
im = p.get('IM') | |
if im: | |
print ' IM' | |
for x in im: | |
print ' %s' % x | |
notes = p.get('notes') | |
if notes: | |
print ' notes' | |
for x in notes: | |
print ' %s' % x | |
def filterNoneValues(d): | |
return dict((k,v) for k,v in d.iteritems() if v is not None) | |
def addElements(contact_index, sql, cur, id_field, name): | |
cur.execute(sql) | |
rows = cur.fetchall() | |
for row in rows: | |
# print dict(row) | |
cid = row[id_field] | |
if not contact_index[cid].get(name): | |
contact_index[cid][name] = [] | |
d = filterNoneValues(dict(row)) | |
del d[id_field] | |
if d: | |
contact_index[cid][name].append(d) | |
def cmpContacts(a, b): | |
a_key_1 = a.get('ZFIRSTNAME') or a.get('ZLASTNAME') or a.get('ZORGANIZATION') | |
a_key_2 = a.get('ZLASTNAME') or a_key_1 | |
b_key_1 = b.get('ZFIRSTNAME') or b.get('ZLASTNAME') or b.get('ZORGANIZATION') | |
b_key_2 = b.get('ZLASTNAME') or b_key_1 | |
return cmp(a_key_1, b_key_1) or cmp(a_key_2, b_key_2) | |
recordSql = ''' | |
select | |
r.Z_PK, | |
date(r.ZBIRTHDAY, 'unixepoch', '+31 years') as birthday, /* Mac dates relative to 2000? */ | |
/* printf('%s %s "%s" %s %s %s (%s)', r.ZTITLE, r.ZFIRSTNAME, r.ZNICKNAME, r.ZMIDDLENAME, r.ZLASTNAME, r.ZSUFFIX, r.ZMAIDENNAME), */ | |
r.ZTITLE, r.ZFIRSTNAME, r.ZNICKNAME, r.ZMIDDLENAME, r.ZLASTNAME, r.ZSUFFIX, r.ZMAIDENNAME, | |
r.ZORGANIZATION, r.ZJOBTITLE, r.ZTMPHOMEPAGE | |
from ZABCDRECORD r; | |
''' | |
emailSql = ''' | |
select | |
e.ZOWNER, lower(replace(replace(e.ZLABEL, '_$!<', ''), '>!$_', '')) as label, e.ZADDRESS | |
from ZABCDEMAILADDRESS e; | |
''' | |
mailSql = ''' | |
select | |
a.ZOWNER, lower(replace(replace(a.ZLABEL, '_$!<', ''), '>!$_', '')) as label, trim(replace(a.ZSTREET, char(10), ', ')) as street, a.ZCITY, a.ZSTATE, a.ZZIPCODE, a.ZCOUNTRYCODE, a.ZCOUNTRYNAME, a.ZREGION, a.ZSAMA | |
from ZABCDPOSTALADDRESS a; | |
''' | |
phoneSql = ''' | |
select | |
p.ZOWNER, lower(replace(replace(p.ZLABEL, '_$!<', ''), '>!$_', '')) as label, p.ZAREACODE, p.ZCOUNTRYCODE, p.ZEXTENSION, p.ZLOCALNUMBER, trim(replace(p.ZFULLNUMBER, char(10), ', ')) as fullnumber | |
from ZABCDPHONENUMBER p; | |
''' | |
imSql = ''' | |
select | |
/* m.ZOWNER, s.ZSERVICENAME, lower(replace(replace(m.ZLABEL, '_$!<', ''), '>!$_', '')) as label, m.ZADDRESS */ | |
m.ZOWNER, s.ZSERVICENAME, m.ZADDRESS | |
from ZABCDMESSAGINGADDRESS m | |
left outer join ZABCDSERVICE s on m.ZSERVICE = s.Z_PK; | |
''' | |
noteSql = ''' | |
select | |
n.ZCONTACT, trim(replace(n.ZTEXT, char(10), ', ')) as text | |
from ZABCDNOTE n; | |
''' | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment