Blog: Migrating contacts from Android 1.x to Android 2.x: contacts.py

File contacts.py, 4.9 KB (added by retracile, 6 years ago)

minimalist export utility

Line 
1#!/usr/bin/python
2# License: MIT or GPL2+, your choice.
3
4import sqlite3
5import sys
6
7def 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
50def 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
63def 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
105def 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
133def as_vcards(contacts):
134    return "\n".join(as_vcard(c) for c in contacts if c['phones'])
135
136
137def main(argv):
138    filename = argv[1]
139    data = load(filename)
140    clean_data(data)
141    print as_vcards(data)
142
143
144if __name__ == '__main__':
145    sys.exit(main(sys.argv))