Back to my blog Back to my projects

Scripts

source: gmail-contacts-to-vcf.py @ d473bae

Revision d473bae, 8.3 KB checked in by Aurélien Bompard <aurelien@…>, 17 months ago (diff)

Encoding fixes

  • Property mode set to 100755
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3u"""
4GMail contacts to VCF
5---------------------
6
7Exports Gmail contacts to a vcard (VCF) file. This can be done through the
8Gmail web interface, but this script is more complete (more fields are
9exported) and can be run automatically and periodically, for example as a
10backup system.
11
12
13.. :Authors:
14       Aurélien Bompard <aurelien@bompard.org> <http://aurelien.bompard.org>
15
16.. :License:
17       GNU GPL v3 or later
18
19"""
20
21from __future__ import with_statement # compat python 2.5
22
23import os
24import sys
25import getpass
26import base64
27from urlparse import urlparse
28from cStringIO import StringIO
29from optparse import OptionParser
30
31import atom
32import gdata.contacts
33import gdata.contacts.service
34import gdata.contacts.client
35import vobject
36
37
38
39class Contacts(object):
40
41    def __init__(self, email, password):
42        """
43        Takes an email and password corresponding to a gmail account to
44        connect to the Contacts feed.
45
46        :param email: The e-mail address of the account to use.
47        :param password: The password corresponding to the account specified by
48            the email parameter.
49        """
50        self.gd_client = gdata.contacts.client.ContactsClient()
51        self.gd_client.source = os.path.basename(sys.argv[0])
52        self.gd_client.ClientLogin(email, password, self.gd_client.source)
53        self.groups = {}
54        self.maingroup = None
55
56
57    def dump(self, filename):
58        self.list_groups()
59        query = gdata.contacts.client.ContactsQuery()
60        query.max_results = 999
61        feed = self.gd_client.GetContacts(q=query)
62
63        with open(filename, "w") as vcf_file:
64            for i, entry in enumerate(feed.entry):
65                if entry.title.text is None:
66                    continue # likely to be a collected address
67                all_group_ids = [ g.href for g in entry.group_membership_info ]
68                if self.maingroup not in all_group_ids:
69                    # Don't store this contact, it's a collected address
70                    continue
71                print i+1, entry.title.text.encode("utf8")
72                #print entry
73                contact = self._make_contact(entry)
74                if contact is None:
75                    continue
76                vcf_file.write(contact.serialize())
77                vcf_file.write("\r\n")
78
79
80    def _make_contact(self, entry):
81        """Builds a VCard entry from a Google Atom entry and returns it"""
82
83        # Name
84        contact = vobject.vCard()
85        contact.add("n")
86        contact.n.value = vobject.vcard.Name()
87        if entry.name.given_name:
88            contact.n.value.given = entry.name.given_name.text
89        if entry.name.family_name:
90            contact.n.value.family = entry.name.family_name.text
91        if entry.name.name_prefix:
92            contact.n.value.prefix = entry.name.name_prefix.text
93        if entry.name.additional_name:
94            contact.n.value.additional = entry.name.additional_name.text
95        contact.add("fn")
96        contact.fn.value = entry.name.full_name.text
97        contact.add("name").value = entry.title.text
98
99        # Email addresses
100        for email in entry.email:
101            c_email = contact.add("email")
102            c_email.value = email.address
103            if email.primary and email.primary == 'true':
104                c_email.type_param = "PREF"
105
106        # Note
107        if entry.content:
108            contact.add("note").value = entry.content.text
109
110        # Groups
111        groups = []
112        for group in entry.group_membership_info:
113            if group.href not in self.groups:
114                continue
115            groups.append(self.groups[group.href])
116        if groups:
117            contact.add("categories").value = groups
118
119        # Modification time
120        contact.add("rev")
121        contact.rev.value = entry.updated.text
122
123        # Phone
124        for phone in entry.phone_number:
125            phone_type = urlparse(phone.rel).fragment
126            if phone_type == "mobile":
127                phone_type = "cell"
128            elif phone_type == "work_fax":
129                phone_type = "fax"
130            tel = contact.add("tel")
131            tel.value = phone.text
132            tel.type_param = phone_type.upper()
133
134        # Organization
135        if entry.organization:
136            contact.add("org").value = [entry.organization.name.text]
137
138        # Birthday
139        if entry.birthday:
140            contact.add("bday").value = entry.birthday.when
141
142        # Address
143        for address in entry.structured_postal_address:
144            adr = contact.add("adr")
145            adr.value = vobject.vcard.Address()
146            if address.street:
147                adr.value.street = address.street.text,
148            if address.city:
149                adr.value.city = address.city.text,
150            if address.region:
151                adr.value.region = address.region.text,
152            if address.neighborhood:
153                adr.value.code = address.neighborhood.text,
154            if address.postcode:
155                adr.value.code = address.postcode.text,
156            if address.country:
157                adr.value.country = address.country.text,
158            if address.po_box:
159                adr.value.box = address.po_box.text,
160            adr_type = urlparse(address.rel).fragment
161            adr.type_param = adr_type.upper()
162
163        # Photo
164        for link in entry.link:
165            if link.rel != "http://schemas.google.com/contacts/2008/rel#photo":
166                continue
167            if "{http://schemas.google.com/g/2005}etag" not in link._other_attributes:
168                continue
169            hosted_image_binary = self.gd_client.GetPhoto(entry)
170            if hosted_image_binary:
171                contact.add("photo")
172                contact.photo.value = hosted_image_binary
173                contact.photo.encoding_param = "b"
174                contact.photo.type_param = "image/jpeg"
175            #with open("%s.jpg" % entry.title.text, "w") as img:
176            #    img.write(hosted_image_binary)
177
178        # IM
179        im_addrs = []
180        for im in entry.im:
181            proto = urlparse(im.protocol).fragment
182            im_addrs.append( (proto, im.address) )
183        if im_addrs:
184            c_im = contact.add("x-kaddressbook-x-imaddress")
185            c_im.value = " ".join("(%s)%s" % addr for addr in im_addrs)
186
187        # Website
188        for website in entry.website:
189            contact.add("url").value = website.href
190
191        # Display extended properties.
192        for extended_property in entry.extended_property:
193            if extended_property.value:
194                value = extended_property.value
195            else:
196                value = extended_property.GetXmlBlob()
197            print '    Extended Property - %s: %s' % (extended_property.name, value)
198
199        return contact
200
201
202    def list_groups(self):
203        """
204        Lists all Google contact groups and stores them in self.groups.
205        The main "My Contacts" group is stored in self.maingroup to filter contacts.
206        """
207        query = gdata.service.Query(feed='/m8/feeds/groups/default/full')
208        query.max_results = 2
209        feed = self.gd_client.GetGroups()
210        for entry in feed.entry:
211            if entry.system_group is not None:
212                if entry.system_group.id == "Contacts":
213                    self.maingroup = entry.id.text
214                continue
215            self.groups[entry.id.text] = entry.title.text
216
217
218
219def parse_opts():
220    usage = "%prog [--user email_address] [--password password] [--filename vcard_file]"
221    parser = OptionParser(usage)
222    parser.add_option("-u", "--user", help="full email address")
223    parser.add_option("-p", "--password")
224    parser.add_option("-f", "--filename", help="VCard file to write to")
225    opts, args = parser.parse_args()
226    while not opts.user:
227        opts.user = raw_input("Please enter your username: ")
228    while not opts.password:
229        print "Please enter your password: ",
230        opts.password = getpass.getpass()
231        if not opts.password:
232            print "Password cannot be blank."
233    while not opts.filename:
234        opts.filename = raw_input("Please enter the VCard file: ")
235    return opts
236
237
238def main():
239    opts = parse_opts()
240
241    try:
242        contacts = Contacts(opts.user, opts.password)
243    except gdata.service.BadAuthentication:
244        print 'Invalid user credentials given.'
245        return
246
247    contacts.dump(opts.filename)
248
249
250
251if __name__ == '__main__':
252    main()
Note: See TracBrowser for help on using the repository browser.