Skip to content

Instantly share code, notes, and snippets.

@bluegraybox
Created January 23, 2016 11:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bluegraybox/1c44abbfb1b8c8b9ba88 to your computer and use it in GitHub Desktop.
Save bluegraybox/1c44abbfb1b8c8b9ba88 to your computer and use it in GitHub Desktop.
Script to export contact information from OS X Contacts
#!/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