#!/usr/bin/python ################################################################################ # gContactsSync.py # # Synopsis: # This script is designed to sync mutt aliases with Google contacts # Description: # # Notes: # Author: Mathias Chauvin # License: # Date: 2009-08-07 # Requires: Google API # Links: # http://blog.mc-thias.org # http://docs.python.org # http://code.google.com/apis/contacts/docs/1.0/developers_guide_python.html # Examples: # # ################################################################################ import sys, getopt, os import atom import re import gdata.contacts import gdata.contacts.service import shutil ################################################################################ # Using a standalone client to sync contacts. ################################################################################ gd_client = gdata.contacts.service.ContactsService() ################################################################################ # Google credentials come here ################################################################################ gd_client.email = 'your_gmail_address_comes_here@gmail.com' gd_client.password = 'your_gmail_password_comes_here' gd_client.source = 'gContactsSync' gd_client.ProgrammaticLogin() ################################################################################ # User defined parameters ################################################################################ max_num_of_contacts = 200 # Get user home directory try: from win32com.shell import shellcon, shell homedir = shell.SHGetFolderPath(0, shellcon.CSIDL_APPDATA, 0, 0) except ImportError: homedir = os.path.expanduser("~") ################################################################################ # Function: ensure_dir # Function that checks if a directory exists and creates it if not # @param d: Directory path ################################################################################ def ensure_dir(d): if not os.path.exists(d): os.makedirs(d) ensure_dir(homedir + "/.gcontactsync") google_aliases_file_path = homedir + '/.gcontactsync/tmpgooglecontacts' local_aliases_file_path = homedir + '/.mail_aliases' ################################################################################ # Function: raw_default # Function that uses raw_input to prompt the user with a default answer # @param prompt: Prompt displayed to the user # @param dflt: Default answer ################################################################################ def raw_default(prompt, dflt=None): if dflt: prompt = "%s [%s]: " % (prompt, dflt) res = raw_input(prompt) if not res and dflt: return dflt return res ################################################################################ # Function: WriteAliasesToFile # Function that gets Google contacts and write a Mutt email aliases # formatted file. # @param feed: Google contacts feed # @param filepath: Path to the file to be written ################################################################################ def WriteAliasesToFile(feed,filepath): address_types_per_contact = {} f = open(filepath,'w') for i, entry in enumerate(feed.entry): # Create a dictionary value to count email addresses of each type # Google contacts allows to categorize email addresses in home, work or other address_type_count = { 'main': 0, 'home': 0, 'work': 0, 'other': 0 } # Parse email addresses for the contact. for email in entry.email: if email.primary and email.primary == 'true': # Write the primary email address for the contact. f.write('alias %s\t%s\t<%s>\n' % (entry.title.text.replace( ' ', '_' ),entry.title.text,email.address)) address_type_count['main'] = address_type_count['main']+1 else: # Get the rel tag and extract whether it is a home, work, or other address m = re.search('(?<=#)\w+', email.rel) address_type = m.group(0) # Write secondary email addresses f.write('alias %s-%s%d\t%s\t<%s>\n' % (entry.title.text.replace( ' ', '_' ),address_type,address_type_count[address_type],entry.title.text,email.address)) address_type_count[address_type] = address_type_count[address_type]+1 address_types_per_contact[entry.title.text] = address_type_count f.close() if (not os.path.exists("%s.old" % (filepath))): shutil.copy(filepath, "%s.old" % (filepath)) # Making a copy of the file to get it for next sync shutil.copy(filepath, "%s.ori" % (filepath)) return address_types_per_contact ################################################################################ # Function: AppendLocalOnlyAliasesToFile # Function that appends aliases defined only in local Mutt aliases file # to the file generated from Google contacts. # This function returns a directory structure with the local only aliases. # This directory should then be used to update Google contacts. # @param local_aliases_file_path: Path to the local Mutt aliases file # @param google_aliases_file_path: Path to the Google aliases generated file # @param prev_google_aliases_file_path: Path to the previously downloaded # Google contacts # @param address_type_count_per_contact: A directory structure to store the # number of email addresses per types for a contact # @param mode: either auto, or manual which will interact with the user ################################################################################ def AppendLocalOnlyAliasesToFile(local_aliases_file_path,google_aliases_file_path,prev_google_aliases_file_path,address_type_count_per_contact,mode): mail_aliases_dict = {} google_contacts_email_arr = [] prev_google_contacts_email_arr = [] only_local_aliases = {} # Read local and store aliases to a dictionary structure f = open(local_aliases_file_path,'r') for line in f.readlines(): m = re.search('(?<=<)[\w\-\.+]+@(\w[\w\-]+\.)+[\w\-]+', line) mail_aliases_dict[m.group(0).lower()] = line #mail_aliases_arr.append(m.group(0).lower()) f.close() f = open(google_aliases_file_path,'r') for line in f.readlines(): m = re.search('(?<=<)[\w\-\.+]+@(\w[\w\-]+\.)+[\w\-]+', line) google_contacts_email_arr.append(m.group(0).lower()) f.close() f = open(prev_google_aliases_file_path,'r') for line in f.readlines(): m = re.search('(?<=<)[\w\-\.+]+@(\w[\w\-]+\.)+[\w\-]+', line) prev_google_contacts_email_arr.append(m.group(0).lower()) f.close() f = open(google_aliases_file_path,'a') for local_email_contact in mail_aliases_dict: if local_email_contact not in google_contacts_email_arr and local_email_contact not in prev_google_contacts_email_arr: # Split the local alias line into a list to create a directory with alias info # This directory will be used to update Google contacts. arr = mail_aliases_dict[local_email_contact].split() arr.pop() only_local_aliases[local_email_contact] = { 'alias': arr[1], 'fullname': ' '.join(arr[2:]) } if mode == 'auto': if only_local_aliases[local_email_contact]['fullname'] in address_type_count_per_contact and address_type_count_per_contact[only_local_aliases[local_email_contact]['fullname']]['main'] > 0: f.write("alias\t%s-other%d\t%s\t<%s>\n" % (only_local_aliases[local_email_contact]['fullname'].replace( ' ', '_' ),address_type_count_per_contact[only_local_aliases[local_email_contact]['fullname']]['other'],only_local_aliases[local_email_contact]['fullname'],local_email_contact)) address_type_count_per_contact[only_local_aliases[local_email_contact]['fullname']]['other'] = address_type_count_per_contact[only_local_aliases[local_email_contact]['fullname']]['other']+1 else: f.write("alias\t%s\t%s\t<%s>\n" % (only_local_aliases[local_email_contact]['fullname'].replace( ' ', '_' ),only_local_aliases[local_email_contact]['fullname'],local_email_contact)) elif mode == 'keep': f.write(mail_aliases_dict[local_email_contact]) elif mode == 'manual': print "\nEnter the value for the contact '%s' with the email '%s'" % (only_local_aliases[local_email_contact]['fullname'], local_email_contact) alias_name = raw_default('\tSelect the alias you will use for this contact', only_local_aliases[local_email_contact]['fullname'].replace( ' ', '_' )) full_name = raw_default('\tSelect the full name for this contact', only_local_aliases[local_email_contact]['fullname']) contact_email = raw_default('\tCheck the email for the contact', local_email_contact) f.write("alias\t%s\t%s\t<%s>\n" % (alias_name,full_name,contact_email)) f.close() return only_local_aliases ################################################################################ # Function: CopyTmpGoogleFileToMuttAliases # Function that copies the generated temp file to the final file # @param local_aliases_file_path # @param google_aliases_file_path ################################################################################ def CopyTmpGoogleFileToMuttAliases(local_aliases_file_path,google_aliases_file_path): shutil.copy(google_aliases_file_path, local_aliases_file_path) ################################################################################ # Function: UpdateGoogleContacts # Function that updates Google contacts according to the aliases defined # locally. # @param gd_client: what it is is what it is # @param feed: Google contacts feed # @param local_aliases_data: Email aliases available locally only # @param mode: auto or manual ################################################################################ def UpdateGoogleContacts(gd_client, feed,local_aliases_data, address_type_count_per_contact, mode): if mode == 'manual': print "\nUpdating Google contacts by adding local email addresses only" print "Please check and/or modify the values that will be sent to Google server if needed" for only_local_email in local_aliases_data: for i, entry in enumerate(feed.entry): if local_aliases_data[only_local_email]['fullname'] == entry.title.text: to_update_entry = entry if address_type_count_per_contact[local_aliases_data[only_local_email]['fullname']]['main'] > 0: to_update_entry.email.append(gdata.contacts.Email(address=only_local_email,primary='false', rel=gdata.contacts.REL_OTHER)) else: to_update_entry.email.append(gdata.contacts.Email(address=only_local_email,primary='true', rel=gdata.contacts.REL_OTHER)) to_create = '' break # Send the updated contact to the server gd_client.UpdateContact(to_update_entry.GetEditLink().href, to_update_entry) else: to_create = local_aliases_data[only_local_email]['fullname'] if to_create != '': if mode == 'auto': full_name = local_aliases_data[only_local_email]['fullname'] contact_email = only_local_email elif mode == 'manual': print "\nEnter the value for the contact '%s' with the email '%s'" % (local_aliases_data[only_local_email]['fullname'], only_local_email) full_name = raw_default('\tSelect the full name for this contact', local_aliases_data[only_local_email]['fullname']) contact_email = raw_default('\tCheck the email for the contact', only_local_email) to_create_entry = gdata.contacts.ContactEntry(title=atom.Title(text=full_name)) to_create_entry.content = atom.Content(text='Created from Mutt aliases') # Create a work email address for the contact and use as primary. to_create_entry.email.append(gdata.contacts.Email(address=contact_email, primary='true', rel=gdata.contacts.REL_OTHER)) # Add extended properties to add data which is used in your application. #to_create_entry.extended_property.append(gdata.ExtendedProperty( # name='favourite flower', value='daisy')) #sports_property = gdata.ExtendedProperty(name='sports') #sports_property.SetXmlBlob('') #to_create_entry.extended_property.append(sports_property) # Send the contact data to the server. contact_entry = gd_client.CreateContact(to_create_entry) ################################################################################ # Function: AddAliasContactFromFile # Function that updates Google contacts according to the aliases defined # locally. # @param gd_client: what it is is what it is # @param feed: Google contacts feed # @param input_file: File containing the '^From: .*' one line from the email to treat # @param local_aliases_file_path: Email aliases available locally only # @param google_aliases_file_path: Path to the Google aliases generated file ################################################################################ def AddAliasContactFromFile(gd_client, feed, input_file, local_aliases_file_path, google_aliases_file_path): f = open(input_file,'r') for line in f.readlines(): from_pattern = re.compile("^From: ") if from_pattern.match(line): arr = line.split() m = re.search('(?<=<)[\w\-\.+]+@(\w[\w\-]+\.)+[\w\-]+', line) # Email Address (ea) if not m: ea = arr.pop() else: ea = m.group(0) arr.pop() # Suggested alias given the email address suggested_alias = (ea.split('@'))[0] final_alias = raw_default('Alias as: ', suggested_alias) final_email = raw_default('Address: ', ea) # Full Name of the contact final_fname = raw_default('Personal name: ', ' '.join(arr[1:])) only_local = { final_email: { 'alias': final_alias, 'fullname': final_fname } } address_type_count_per_contact = WriteAliasesToFile(feed,google_aliases_file_path) UpdateGoogleContacts(gd_client,feed,only_local,address_type_count_per_contact,'auto') la_file = open(local_aliases_file_path, 'a') la_file.write("alias\t%s\t%s\t<%s>\n" % (final_alias,final_fname,final_email)) la_file.close() f.close() ################################################################################ # Function: usage # Function that prints the usage to the std output. ################################################################################ def usage(): print "Usage: %s -h | -s [auto|manual] | -g [auto|manual] | -i " % (sys.argv[0]) print "" print " This scripts allows to sync Google contacts with Mutt aliases." print "" print " -h | --help : displays this help" print " -s [auto|manual] | --sync=[auto|manual] : sync Google contacts with Mutt aliases" print " If auto, then the script will decide the aliases and user name it has to create if needed" print " If manual, the user will be prompted for Contact name/alias." print " -g [auto|manual] | --get-google=[auto|manual] : gets Google contacts and overwrites Mutt aliases" print " -i path/to/file | --from-file=path/to/file : create/update Google contact according to an alias entry stored in a file" print " This option is used almost exclusively from Mutt macro to create an alias from an email" print "" ################################################################################ # Function main # Main function that is run when calling the script. # This function mainly gets the args and process them ################################################################################ def main(): try: opts, args = getopt.getopt(sys.argv[1:], "hs:g:a:i:", ["help", "sync=", "get-google=", "add-alias=", "from-file="]) except getopt.GetoptError, err: # print help information and exit: print str(err) # will print something like "option -a not recognized" usage() sys.exit(2) query = gdata.contacts.service.ContactsQuery() query.max_results = max_num_of_contacts feed = gd_client.GetContactsFeed(query.ToUri()) syncopt = None for o, a in opts: if o in ("-h", "--help"): usage() sys.exit() elif o in ("-s", "--sync"): if a == '': syncopt = 'auto' else: syncopt = a address_type_count_per_contact = WriteAliasesToFile(feed,google_aliases_file_path) only_local = AppendLocalOnlyAliasesToFile(local_aliases_file_path,google_aliases_file_path,"%s.old" % (google_aliases_file_path),address_type_count_per_contact,syncopt) CopyTmpGoogleFileToMuttAliases(local_aliases_file_path,google_aliases_file_path) UpdateGoogleContacts(gd_client,feed,only_local,address_type_count_per_contact,syncopt) shutil.copy("%s.ori" % (google_aliases_file_path), "%s.old" % (google_aliases_file_path)) elif o in ("-g", "--get-google"): if a == '': syncopt = 'auto' else: syncopt = a address_type_count_per_contact = WriteAliasesToFile(feed,google_aliases_file_path) only_local = AppendLocalOnlyAliasesToFile(local_aliases_file_path,google_aliases_file_path,"%s.old" % (google_aliases_file_path),address_type_count_per_contact,syncopt) CopyTmpGoogleFileToMuttAliases(local_aliases_file_path,google_aliases_file_path) shutil.copy("%s.ori" % (google_aliases_file_path), "%s.old" % (google_aliases_file_path)) elif o in ("-a", "--add-alias"): args_to_add = a.split(';') only_local = { args_to_add[1]: { 'alias': 'fake', 'fullname': args_to_add[0] } } address_type_count_per_contact = WriteAliasesToFile(feed,google_aliases_file_path) UpdateGoogleContacts(gd_client,feed,only_local,address_type_count_per_contact,'auto') elif o in ("-i", "--from-file"): if a == '': input_file = '/tmp/mutt-alias-to-create' else: input_file = a AddAliasContactFromFile(gd_client, feed, input_file, local_aliases_file_path, google_aliases_file_path) else: assert False, "unhandled option" # ... if __name__ == "__main__": main()