| 1 | #!/usr/bin/python |
|---|
| 2 | # License: MIT or GPL2+, your choice. |
|---|
| 3 | |
|---|
| 4 | import sqlite3 |
|---|
| 5 | import sys |
|---|
| 6 | |
|---|
| 7 | def load(filename): |
|---|
| 8 | connection = sqlite3.connect(filename) |
|---|
| 9 | cursor = connection.cursor() |
|---|
| 10 | data = [] |
|---|
| 11 | people = list(cursor.execute("SELECT _id, name, notes, times_contacted, last_time_contacted, starred, primary_phone, primary_organization, primary_email, photo_version, custom_ringtone, send_to_voicemail, phonetic_name FROM people")) |
|---|
| 12 | for people_id, name, notes, times_contacted, last_time_contacted, starred, primary_phone_id, primary_organization_id, primary_email_id, photo_version, custom_ringtone, send_to_voicemail, phonetic_name \ |
|---|
| 13 | in people: |
|---|
| 14 | person = { |
|---|
| 15 | 'name': name, |
|---|
| 16 | 'notes': notes, |
|---|
| 17 | 'times_contacted': times_contacted, |
|---|
| 18 | 'last_time_contacted': last_time_contacted, |
|---|
| 19 | 'starred': starred, |
|---|
| 20 | 'photo_version': photo_version, |
|---|
| 21 | 'custom_ringtone': custom_ringtone, |
|---|
| 22 | 'send_to_voicemail': send_to_voicemail, |
|---|
| 23 | 'phonetic_name': phonetic_name, |
|---|
| 24 | } |
|---|
| 25 | # primary_phone_id |
|---|
| 26 | # primary_organization_id |
|---|
| 27 | # primary_email_id |
|---|
| 28 | phones = list(cursor.execute("SELECT type, number, label from phones where person=?", (people_id,))) |
|---|
| 29 | #print phones |
|---|
| 30 | person['phones'] = [] |
|---|
| 31 | for phone_type, phone_number, label in phones: |
|---|
| 32 | if not label: |
|---|
| 33 | label = { |
|---|
| 34 | 0: 'Home', |
|---|
| 35 | 1: 'Mobile', |
|---|
| 36 | 2: 'Work', |
|---|
| 37 | 3: 'Work-Fax', |
|---|
| 38 | 4: 'Home-Fax', |
|---|
| 39 | 5: 'Pager', |
|---|
| 40 | 6: 'Other', |
|---|
| 41 | }[phone_type] |
|---|
| 42 | person['phones'].append({ |
|---|
| 43 | 'number': phone_number, |
|---|
| 44 | 'label': label, |
|---|
| 45 | }) |
|---|
| 46 | data.append(person) |
|---|
| 47 | return data |
|---|
| 48 | |
|---|
| 49 | |
|---|
| 50 | def norm_phone_number(phone_number): |
|---|
| 51 | phone_number = phone_number.replace('-', '') |
|---|
| 52 | if not phone_number.startswith('+'): |
|---|
| 53 | phone_number = phone_number[-10:] |
|---|
| 54 | phone_number = '-'.join(part for part in ( |
|---|
| 55 | phone_number[:-10], |
|---|
| 56 | phone_number[-10:-7], |
|---|
| 57 | phone_number[-7:-4], |
|---|
| 58 | phone_number[-4:], |
|---|
| 59 | ) if part) |
|---|
| 60 | return phone_number |
|---|
| 61 | |
|---|
| 62 | |
|---|
| 63 | def clean_data(data): |
|---|
| 64 | """Cleans phone data *in-place*""" |
|---|
| 65 | # Coallesce "John Doe" and "John Doe1" into one contact |
|---|
| 66 | names = [c['name'] for c in data] |
|---|
| 67 | assert len(names) == len(set(names)) |
|---|
| 68 | by_name = dict((c['name'], c) for c in data) |
|---|
| 69 | bad_names = [n for n in names if n.endswith('1')] |
|---|
| 70 | for bad_name in bad_names: |
|---|
| 71 | name = bad_name.rstrip('1') |
|---|
| 72 | bad_contact = by_name[bad_name] |
|---|
| 73 | if name in by_name: |
|---|
| 74 | contact = by_name[name] |
|---|
| 75 | data.remove(bad_contact) |
|---|
| 76 | contact['phones'].extend(bad_contact['phones']) |
|---|
| 77 | if bad_contact['notes']: |
|---|
| 78 | if contact['notes']: |
|---|
| 79 | contact['notes'] += '\n' |
|---|
| 80 | contact['notes'] += bad_contact['notes'] |
|---|
| 81 | else: |
|---|
| 82 | bad_contact['name'] = name |
|---|
| 83 | |
|---|
| 84 | # Clean up duplicate phone numbers and labels |
|---|
| 85 | for contact in data: |
|---|
| 86 | phones = contact['phones'] |
|---|
| 87 | new_phones = [] |
|---|
| 88 | new_numbers = set() |
|---|
| 89 | new_labels = set() |
|---|
| 90 | for phone in contact['phones']: |
|---|
| 91 | phone_number = norm_phone_number(phone['number']) |
|---|
| 92 | if phone_number in new_numbers: |
|---|
| 93 | continue # skip duplicates |
|---|
| 94 | proposed_label = phone['label'] |
|---|
| 95 | n=2 |
|---|
| 96 | while proposed_label in new_labels: |
|---|
| 97 | proposed_label = "%s%s" % (phone['label'], n) |
|---|
| 98 | n += 1 |
|---|
| 99 | new_numbers.add(phone_number) |
|---|
| 100 | new_labels.add(proposed_label) |
|---|
| 101 | new_phones.append({'label': proposed_label, 'number': phone_number}) |
|---|
| 102 | contact['phones'] = new_phones |
|---|
| 103 | |
|---|
| 104 | |
|---|
| 105 | def as_vcard(contact): |
|---|
| 106 | if not contact['phones']: |
|---|
| 107 | sys.stderr.write("no phone: %s\n" % contact['name']) |
|---|
| 108 | name_parts = contact['name'].split() |
|---|
| 109 | if len(name_parts) == 2: |
|---|
| 110 | first_name = name_parts[0] |
|---|
| 111 | last_name = name_parts[1] |
|---|
| 112 | else: |
|---|
| 113 | first_name = contact['name'] |
|---|
| 114 | last_name = '' |
|---|
| 115 | card_lines = [ |
|---|
| 116 | "BEGIN:VCARD", |
|---|
| 117 | "VERSION:2.1", |
|---|
| 118 | "N:%s;%s;;;" % (last_name, first_name), |
|---|
| 119 | "FN:%s" % contact['name'], |
|---|
| 120 | ] |
|---|
| 121 | for phone in contact['phones']: |
|---|
| 122 | card_lines.append("TEL;X-%s:%s" % (phone['label'], phone['number'])) |
|---|
| 123 | if contact['notes']: |
|---|
| 124 | notes = contact['notes'] |
|---|
| 125 | if '\n' in notes: |
|---|
| 126 | sys.stderr.write("multiline note is: %r\n" % notes) |
|---|
| 127 | notes = notes.replace("\n", ". ").strip() |
|---|
| 128 | card_lines.append("NOTE:%s" % notes) |
|---|
| 129 | card_lines.append("END:VCARD") |
|---|
| 130 | return "\n".join(card_lines) |
|---|
| 131 | |
|---|
| 132 | |
|---|
| 133 | def as_vcards(contacts): |
|---|
| 134 | return "\n".join(as_vcard(c) for c in contacts if c['phones']) |
|---|
| 135 | |
|---|
| 136 | |
|---|
| 137 | def main(argv): |
|---|
| 138 | filename = argv[1] |
|---|
| 139 | data = load(filename) |
|---|
| 140 | clean_data(data) |
|---|
| 141 | print as_vcards(data) |
|---|
| 142 | |
|---|
| 143 | |
|---|
| 144 | if __name__ == '__main__': |
|---|
| 145 | sys.exit(main(sys.argv)) |
|---|