Skip to content

Instantly share code, notes, and snippets.

@samr7
Created August 10, 2018 22:08
Show Gist options
  • Save samr7/7541db77ba36742f269f672ff33c80e8 to your computer and use it in GitHub Desktop.
Save samr7/7541db77ba36742f269f672ff33c80e8 to your computer and use it in GitHub Desktop.
iOS backup contact list extractor and VCF converter
#!/usr/bin/python3
import sys
import sqlite3
import datetime
import base64
def convert_ab_time(tmx):
epoch = datetime.datetime(2001, 1, 1)
return epoch + datetime.timedelta(seconds=float(tmx))
def extract_contacts(ab, abi):
cursor = ab.cursor()
xcursor = ab.cursor()
xqcursor = ab.cursor()
icursor = abi.cursor()
xcursor.execute('SELECT ROWID, value from ABMultiValueLabel')
mvlabelids = dict()
for row in xcursor:
value = row[1]
if value.startswith('_$!<') and value.endswith('>!$_'):
value = value[4:-4]
mvlabelids[row[0]] = value
xcursor.execute('SELECT ROWID, value from ABMultiValueEntryKey')
mvelabelids = dict()
for row in xcursor:
value = row[1]
mvelabelids[row[0]] = value
cursor.execute('SELECT ROWID, First, Middle, Last, Prefix, Suffix, Organization, Note, Birthday FROM ABPerson')
for row in cursor:
print('BEGIN:VCARD')
print('VERSION:3.0')
empty = lambda v: '' if (v is None) else str(v)
first = empty(row[1])
middle = empty(row[2])
last = empty(row[3])
prefix = empty(row[4])
suffix = empty(row[5])
org = empty(row[6])
nonempty = lambda v: (v + ' ') if (len(v) > 0) else ''
if last or first:
print('FN:{0}{1}{2}{3}'.format(nonempty(prefix),nonempty(first),nonempty(middle),nonempty(last),nonempty(suffix)))
print('N:{0};{1};{2};{3};{4}'.format(last,first,middle,prefix,suffix))
elif org:
print('FN:{0}'.format(org))
if org:
print('ORG:{0}'.format(row[6]))
if row[7]:
print('NOTE:{0}'.format(row[7]))
if row[8]:
bday = convert_ab_time(row[8])
print('BDAY:{0}'.format(bday.strftime('%Y%m%d')))
xcursor.execute('SELECT ROWID, property, label, value FROM ABMultiValue WHERE record_id = ?', (row[0],))
for xrow in xcursor:
ttype = ''
if xrow[2] is not None:
ttype = ';TYPE={0}'.format(mvlabelids[xrow[2]])
if xrow[1] == 3:
print('TEL{0}:{1}'.format(ttype, xrow[3]))
elif xrow[1] == 4:
print('EMAIL{0}:{1}'.format(ttype, xrow[3]))
elif xrow[1] == 5:
blank = ''
addr = dict()
xqcursor.execute('SELECT key, value FROM ABMultiValueEntry WHERE parent_id = ?', (xrow[0],))
for xqrow in xqcursor:
addr[mvelabelids[xqrow[0]]] = xqrow[1]
acomp = lambda key: addr[key] if key in addr else ''
print('ADR{0}:{1};{2};{3};{4};{5};{6};{7}'.format(ttype,blank,blank,acomp('Street'),acomp('City'),acomp('State'),acomp('ZIP'),acomp('Country')))
elif xrow[1] == 12:
anniv = convert_ab_time(row[8])
print('X-ANNIVERSARY:{0}'.format(anniv.strftime('%Y%m%d')))
elif xrow[1] == 13:
addr = dict()
xqcursor.execute('SELECT key, value FROM ABMultiValueEntry WHERE parent_id = ?', (xrow[0],))
for xqrow in xqcursor:
addr[mvelabelids[xqrow[0]]] = xqrow[1]
acomp = lambda key: addr[key] if key in addr else ''
if 'username' in addr:
print('IMPP:{0}:{1}'.format(acomp('service'), acomp('username')))
elif xrow[1] == 22:
print('URL{0}:{1}'.format(ttype, xrow[3]))
elif xrow[1] == 23:
print('RELATED{0}:{1}'.format(ttype, xrow[3]))
else:
xqcursor.execute('SELECT key, value FROM ABMultiValueEntry WHERE parent_id = ?', (xrow[0],))
va = ''
for xqrow in xqcursor:
va = va + ';{0}={1}'.format(mvelabelids[xqrow[0]], xqrow[1])
print('UNK{0}:prop={1};val={2}{3}'.format(ttype,xrow[1],xrow[3],va))
icursor.execute('SELECT format, data FROM ABThumbnailImage WHERE record_id = ?', (row[0],))
for xrow in icursor:
if xrow[1] is None: continue
if xrow[0] != 0: continue
encoded = base64.b64encode(xrow[1])
print('PHOTO;ENCODING=BASE64;TYPE=JPEG:' + bytes.decode(encoded))
print('END:VCARD')
print('')
def extract_backup(path):
# AddressBook.sqlitedb
xab = sqlite3.connect('file:' + path + '/31/31bb7ba8914766d4ba40d6dfb6113c8b614be442?immutable=1', uri=True)
# AddressBookImages.sqlitedb
xabi = sqlite3.connect('file:' + path + '/cd/cd6702cea29fe89cf280a76794405adb17f9a0ee?immutable=1', uri=True)
extract_contacts(xab, xabi)
if len(sys.argv) == 1:
print('Usage: {0} <path-to-backup>'.format(sys.argv[0]))
sys.exit(1)
extract_backup(sys.argv[1])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment