Gmail 联系人信息与 vCard 格式转换的 Python 脚本

地址簿格式, 个人偏好 vCard 格式. 这个格式比较软件中立, 方便在各种格式间转换, 且被大都软件接受为导入或导出格式之一. 大部分手机也支持此种格式交换联系人信息.

如是, 在本地建立一个版本控制的目录, 全是 vCard 文件, 可以方便地做些简单查找, 合并及批量修改.

整理通讯信息, 自然少不了导入导出. 这个 Online vCard Converter 在转换 Gmail 等的联系人信息时十分方便. 我开始便是用这个转换. 但后来数据多了, 教育网又要找代理, 便自己写了几个脚本. 放此备份, 或有同样需求的.

以下为从 Gmail-CSV 到 vCard 转换的脚本.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
 
import csv
import sys
import re

def load_csv(fn):
    f = open(fn,'rb')
    csvs = csv.reader(f)
    head = csvs.next()
    rows = [row for row in csvs]
    for row in rows:
        print to_vcard(head, row)

def get_n_from_fn(fn):
    fn=fn.decode('utf8')
    if ' ' in fn:
        (given, family) = fn.rsplit(' ', 1)
    if re.match('[a-zA-Z0-9_]', fn[0]):
        (given, family) = (fn, '')
    else:
        (family, given) = (fn[0], fn[1:])
    return ('%s;%s;;;' % (family, given)).encode('utf8')

def tel_types_str(*types):
    itypes=[]
    for type in types:
        type=type.upper()
        if type == 'MOBILE':
            itypes.append('CELL')
            continue
        if type in ['OTHER', 'PHONE'] :
            continue
        itypes.append(type)
    ts = ','.join(itypes)
    if ts: return ';TYPE=%s' % ts
    return ''

def to_vcard(head, row):
    ln = '\n'
    vc = []
    vc.append('BEGIN:VCARD')
    vc.append('Version:3.0')
    vc.append('FN:%s' % row[0])
    vc.append('N:%s' % get_n_from_fn(row[0]))
    vc.append('EMAIL:%s' % row[1])
    vc.append('NOTES:%s' % row[2].replace('\n', '\\n'))
    idx = 2
    desc = ''
    for field in head[3:]:
        idx += 1
        type = field.split(' - ')[1]
        if idx >= len(row):
            break
        if type == 'Description':
            desc = row[idx].upper()
            continue
        if not row[idx]:
            continue
        if type == 'Email':
            emails = row[idx].split(';')
            for email in emails:
                vc.append('EMAIL;TYPE=%s:%s' % (desc, email.strip()))
            continue
        if type == 'IM':
            ims = row[idx].split(';')
            for im in ims:
                if ':' in im:
                    (imt, ima) = im.split(':', 1)
                else:
                    if '@gmail.com' in im:
                        (imt, ima) = ('X-GTALK', im)
                    elif '@hotmail.com' in im:
                        (imt, ima) = ('X-MSN', im)
                    else:
                        (imt, ima) = ('OTHER', im)
                vc.append('EMAIL;TYPE=%s:%s' % (imt.upper(), ima.strip()))
            continue
        if type in ['Mobile', 'Pager', 'Fax', 'Phone']:
            tels = row[idx].split(';')
            for tel in  tels:
                vc.append('TEL%s:%s' % (tel_types_str(desc, type), tel.strip()))
            continue
        if type == 'Title':
            vc.append('TITLE;TYPE=%s:%s' % (desc, row[idx]))
            continue
        if type == 'Other':
            continue
        if type == 'Company':
            vc.append('ORG;TYPE=%s:%s' % (desc, row[idx]))
            continue
        if type == 'Address':
            vc.append('ADDR;TYPE=%s:%s' % (desc, row[idx].replace(';', '\\;')))
            continue
    vc.append('END:VCARD')
    nvc = []
    for line in vc:
        if re.match('^[^:]*:[ \t]*$', line):
            pass
        else:
            nvc.append(line)
    return ln.join(nvc) + ln

load_csv(sys.argv[1])
以下为从 vCard 到 Gmail CSV 转换的脚本.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import csv
import sys
import re
import vobject

def filter_lines(ls, names, types):
    if not isinstance(names, list): names = [names]
    if not isinstance(types, list): types = [types]
    if names:
        ls = [l for l in ls if l.name in names]
    for type in types:
        if type.istitle():
            type = type.upper()
            ls = [l for l in ls if not type in l.params.get('TYPE', [])]
        else:
            ls = [l for l in ls if type in l.params.get('TYPE', [])]
    return ls

def lines_value(ls):
    return [l.value for l in ls]

def filter_lines_value(ls, names, types):
    ls = filter_lines(ls, names, types)
    return lines_value(ls)

def vcard_to_csv(fp, cw):
    for vcf in vobject.readComponents(fp):
        vcf_to_csv(vcf, cw)

def vcf_to_csv(vcf, cw):
    email = vcf.getChildValue('email') or ''
    fn = vcf.getChildValue('fn') or ''
    note = vcf.getChildValue('note') or ''
    fields = [fn, email, note]
    sections = ['WORK', 'HOME', 'OTHER', 'PERSONAL']
    f_sections=[['WORK'], ['HOME'], ['OTHER'], ['Work', 'Home', 'Other']]
    lines = [l for l in vcf.lines()]
    for idx in range(len(sections)):
        section = sections[idx]
        f_section = f_sections[idx]
        s_email=[v for v in filter_lines_value(lines, 'EMAIL', f_section) if v != email]
        s_im = [l.name.replace('X-', '')+': ' + l.value
                for l in filter_lines(lines, ['X-GTALK', 'X-MSN', 'X-ICQ', 'X-JABBER', 'X-AIM'], f_section)]
        s_mobile = filter_lines_value(lines, 'TEL', f_section + ['CELL'])
        s_pager = filter_lines_value(lines, 'TEL', f_section + ['PAGER'])
        s_fax = filter_lines_value(lines, 'TEL', f_section + ['FAX'])
        s_phone = filter_lines_value(lines, 'TEL', f_section +['Cell', 'Pager', 'Fax'])
        s_label = filter_lines_value(lines, 'LABEL', f_section)
        if section == 'WORK':
            s_org = ' '.join(vcf.getChildValue('org') or [])
            s_title = vcf.getChildValue('title') or ''
        else:
            s_org = ''
            s_title = ''
        if s_email or s_im or s_phone or s_mobile or s_pager or s_fax or s_org or s_title or s_label:
            pass
        else:
            continue
        fields.append(section.title())
        fields.append(' ::: '.join(s_email))
        fields.append(' ::: '.join(s_im))
        fields.append(' ::: '.join(s_phone))
        fields.append(' ::: '.join(s_mobile))
        fields.append(' ::: '.join(s_pager))
        fields.append(' ::: '.join(s_fax))
        fields.append(s_org)# company
        fields.append(s_title)# title
        fields.append('')# other
        fields.append('\n'.join(s_label))
    cw.writerow([field.encode('utf8') for field in fields])

if __name__== '__main__':
    csv_prev_headers = ['Name', 'E-mail', 'Notes']
    csv_per_headers = ['Description', 'Email', 'IM', 'Phone', 'Mobile', 'Pager', 'Fax',
                       'Company','Title', 'Other', 'Address']
    csv_headers = csv_prev_headers
    for i in [1,2,3,4]:
        csv_headers.extend(['Section %s - %s' % (i, h) for h in csv_per_headers])
    cw = csv.writer(sys.stdout)
    cw.writerow(csv_headers)
    if len(sys.argv) < 2:
        vcard_to_csv(sys.stdin, cw)
    else:
        for f in sys.argv[1:]:
            vcard_to_csv(open(f, 'rb'), cw)
以下为将 vCard 中的生日信息转换为 iCalendar 文件的脚本.

#!/usr/bin/env python
# coding: utf-8

import csv
import sys
import re
import vobject
import datetime
import dateutil

tzlocal = dateutil.tz.tzlocal()

def vcard_bday_to_ical(fp, ic):
    for vcf in vobject.readComponents(fp):
        bday = vcf.getChildValue('bday')
        if not bday: continue
        bday = bday.replace('-', '').replace(' ', '')
        if len(bday) == '4':
            bday = '1980'+bday
        if len(bday) != 8:
            continue
        print >>sys.stderr, 'parsing ', vcf.fn.value, vcf.bday.value
        date = datetime.date(int(bday[0:4]), int(bday[4:6]), int(bday[6:8]))
        v = ic.add('vevent')
        v.add('dtstart').value=date
        v.add('dtend').value=date+datetime.timedelta(1)
        rrule = dateutil.rrule.rruleset()
        rrule.rrule(dateutil.rrule.rrule(dateutil.rrule.YEARLY, dtstart=datetime.datetime(2009,12,12,tzinfo=tzlocal)))
        v.rruleset=rrule
        v.add('uid').value=vcf.uid.value + '-jiangzuoyan@gmail.com'
        v.add('summary').value=' '.join(
            [i for i in
             [vcf.fn.value,
              vcf.getChildValue('email') or '',
              vcf.getChildValue('tel') or '',
              vcf.getChildValue('bday') or ''
              ]
             if i])


if __name__ == '__main__':
    ic = vobject.iCalendar()
    for file in sys.argv[1:]:
        if file == '-':
            fp = sys.stdin
        else:
            fp = open(file, 'rb')
        vcard_bday_to_ical(fp, ic)
    print ic.serialize()

No comments: