Handle variants of Joseph, Joanna, Joanne.
[hermes] / package / src / hermes.py
index e844113..411ca56 100644 (file)
@@ -8,261 +8,266 @@ from contacts import ContactStore
 import names
 
 class Hermes:
-  """Encapsulate the process of syncing Facebook friends' information with the
-     Evolution contacts' database. This should be used as follows:
-     
-       * Initialise, passing in a callback (methods: need_auth(),
-         block_for_auth(), use_twitter(), use_facebook()).
-       * Call load_friends().
-       * Call sync_contacts().
-       * Retrieve information on changes effected.
+    """Encapsulate the process of syncing Facebook friends' information with the
+       Evolution contacts' database. This should be used as follows:
        
-     This requires two gconf paths to contain Facebook application keys:
-         /apps/maemo/hermes/key_app
-         /apps/maemo/hermes/key_secret
-       
-     Copyright (c) Andrew Flegg <andrew@bleb.org> 2009.
-     Released under the Artistic Licence."""
-
-
-  # -----------------------------------------------------------------------
-  def __init__(self, callback, twitter = None, facebook = False, empty = False):
-    """Constructor. Passed a callback which must implement three informational
-       methods:
-       
-         need_auth() - called to indicate a login is about to occur. The user
-                       should be informed.
+         * Initialise, passing in a callback (methods: need_auth(),
+           block_for_auth(), use_twitter(), use_facebook()).
+         * Call load_friends().
+         * Call sync_contacts().
+         * Retrieve information on changes effected.
+         
+       This requires two gconf paths to contain Facebook application keys:
+           /apps/maemo/hermes/key_app
+           /apps/maemo/hermes/key_secret
+         
+       Copyright (c) Andrew Flegg <andrew@bleb.org> 2009.
+       Released under the Artistic Licence."""
+    
+    
+    # -----------------------------------------------------------------------
+    def __init__(self, callback, twitter = None, facebook = False, empty = False):
+        """Constructor. Passed a callback which must implement three informational
+           methods:
+           
+             need_auth() - called to indicate a login is about to occur. The user
+                           should be informed.
+                           
+             block_for_auth() - prompt the user to take some action once they have
+                                successfully logged in to Facebook.
+                              
+             progress(i, j) - the application is currently processing friend 'i' of
+                              'j'. Should be used to provide the user a progress bar.
+                              
+          Other parameters:
+             twitter - a username/password tuple or None if Twitter should not be
+                       used. Defaults to None.
                        
-         block_for_auth() - prompt the user to take some action once they have
-                            successfully logged in to Facebook.
-                          
-         progress(i, j) - the application is currently processing friend 'i' of
-                          'j'. Should be used to provide the user a progress bar.
-                          
-      Other parameters:
-         twitter - a username/password tuple or None if Twitter should not be
-                   used. Defaults to None.
-                   
-         facebook - boolean indicating if Facebook should be used. Defaults to
-                    False.
-
-         empty - boolean indicating if 'empty' contacts consisting of a profile
-                 URL and birthday should be created.
-                          """
-
-    self.gc       = gnome.gconf.client_get_default()
-    self.callback = callback
-    self.twitter  = twitter
-    self.facebook = facebook
-    self.create_empty = empty
-
-    # -- Check the environment is going to work...
-    #
-    if (self.gc.get_string('/desktop/gnome/url-handlers/http/command') == 'epiphany %s'):
-      raise Exception('Browser in gconf invalid (see NB#136012). Installation error.')
-
-    # -- Get private keys for this app...
-    #
-    key_app    = self.gc.get_string('/apps/maemo/hermes/key_app')
-    key_secret = self.gc.get_string('/apps/maemo/hermes/key_secret')
-    if key_app is None or key_secret is None:
-      raise Exception('No Facebook application keys found. Installation error.')
-
-    self.fb = Facebook(key_app, key_secret)
-    self.fb.desktop = True
-
-
-  # -----------------------------------------------------------------------
-  def do_fb_login(self):
-    """Perform authentication against Facebook and store the result in gconf
-         for later use. Uses the 'need_auth' and 'block_for_auth' methods on
-         the callback class. The former allows a message to warn the user
-         about what is about to happen to be shown; the second is to wait
-         for the user to confirm they have logged in."""
-    self.fb.session_key = None
-    self.fb.secret = None
-    self.fb.uid = None
+             facebook - boolean indicating if Facebook should be used. Defaults to
+                        False.
+        
+             empty - boolean indicating if 'empty' contacts consisting of a profile
+                     URL and birthday should be created.
+                              """
+        
+        self.gc       = gnome.gconf.client_get_default()
+        self.callback = callback
+        self.twitter  = twitter
+        self.facebook = facebook
+        self.create_empty = empty
+        
+        # -- Check the environment is going to work...
+        #
+        if (self.gc.get_string('/desktop/gnome/url-handlers/http/command') == 'epiphany %s'):
+            raise Exception('Browser in gconf invalid (see NB#136012). Installation error.')
+        
+        # -- Get private keys for this app...
+        #
+        key_app    = self.gc.get_string('/apps/maemo/hermes/key_app')
+        key_secret = self.gc.get_string('/apps/maemo/hermes/key_secret')
+        if key_app is None or key_secret is None:
+            raise Exception('No Facebook application keys found. Installation error.')
+        
+        self.fb = Facebook(key_app, key_secret)
+        self.fb.desktop = True
     
-    self.callback.need_auth()
-    self.fb.auth.createToken()
-    self.fb.login()
-    self.callback.block_for_auth()
-    session = self.fb.auth.getSession()
-
-    self.gc.set_string('/apps/maemo/hermes/session_key', session['session_key'])
-    self.gc.set_string('/apps/maemo/hermes/secret_key', session['secret'])
-    self.gc.set_string('/apps/maemo/hermes/uid', str(session['uid']))
-
-
-  # -----------------------------------------------------------------------
-  def load_friends(self):
-    """Load information on the authenticated user's friends. If no user is
-       currently authenticated, prompts for a login."""
-
-    self.friends = {}
-    self.blocked_pictures = []
-    self.callback.progress(0, 0)
-    self.friends_by_url = {}
     
-    # -- Get a user session and retrieve Facebook friends...
-    #
-    if self.facebook:
-      print "+++ Opening Facebook..."
-      if self.fb.session_key is None:
-        self.fb.session_key = self.gc.get_string('/apps/maemo/hermes/session_key')
-        self.fb.secret = self.gc.get_string('/apps/maemo/hermes/secret_key')
-        self.fb.uid = self.gc.get_string('/apps/maemo/hermes/uid')
-
-      # Check the available session is still valid...
-      while True:
-        try:
-          if self.fb.users.getLoggedInUser() and self.fb.session_key:
-            break
-        except FacebookError:
-          pass
-        self.do_fb_login()
-
-      # Get the list of friends...
-      attrs = ['uid', 'name', 'pic_big', 'birthday_date', 'profile_url', 'first_name', 'last_name', 'website']
-      for friend in self.fb.users.getInfo(self.fb.friends.get(), attrs):
-        key = unicode(friend['name']).encode('trans')
-        self.friends[key] = friend
-        self.friends_by_url[friend['profile_url']] = friend
-        friend['pic']  = friend[attrs[2]]
-        friend['account'] = 'facebook'
-        if friend['website']:
-          friend['homepage'] = friend['website']
-
-        if not friend['pic']:
-          self.blocked_pictures.append(friend)
-          
-          
-    # -- Retrieve following information from Twitter...
-    #
-    if self.twitter is not None:
-      print "+++ Opening Twitter..."
-      (user, passwd) = self.twitter
-      api = twitter.Api(username=user, password=passwd)
-      users = api.GetFriends()
-      for tweeter in api.GetFriends():
-        key    = unicode(tweeter.name).encode('trans')
-        url    = 'http://twitter.com/%s' % (tweeter.screen_name)
-        friend = {'name':          tweeter.name, 'pic': tweeter.profile_image_url,
-                  'birthday_date': None,         'twitter_url': url,
-                  'homepage':      tweeter.url,  'account': 'twitter'}
-        if friend['pic'].find('/default_profile') > -1:
-          friend['pic'] = None
-          
-        self.friends[key] = friend
-        self.friends_by_url[url] = friend
-  
-    # TODO What if the user has *no* contacts?
-
-  
-  # -----------------------------------------------------------------------
-  def sync_contacts(self, resync = False):
-    """Synchronise Facebook profiles to contact database. If resync is false,
-       no existing information will be overwritten."""
-
-    # -- Find addresses...
-    #
-    print "+++ Syncing contacts..."
-    self.addresses = evolution.ebook.open_addressbook('default')
-    print "+++ Addressbook opened..."
-    self.store = ContactStore(self.addresses)
-    print "+++ Contact store created..."
-    self.updated = []
-    self.unmatched = []
-    self.matched = []
-    contacts = self.addresses.get_all_contacts()
-    contacts.sort(key=lambda obj: obj.get_name())
-    current = 0
-    maximum = len(contacts)
-    for contact in contacts:
-      current += 1
-      self.callback.progress(current, maximum)
-      found = False
-      updated = False
-      
-      # Try match on existing URL...
-      for url in self.store.get_urls(contact):
-        if url in self.friends_by_url:
-          updated = self.update_contact(contact, self.friends_by_url[url], resync)
-          found = True
-          if updated:
-            break
-
-      # Fallback to names...
-      if not found:
-        for name in names.variants(contact.get_name()):
-          if name in self.friends:
-            updated = self.update_contact(contact, self.friends[name], resync)
-            found = True
-            if updated:
-              break
-   
-      # Keep track of updated stuff...
-      if updated:
-        self.updated.append(contact)
-        self.addresses.commit_contact(contact)
-        print "Saved changes to [%s]" % (contact.get_name())
-      
-      if found:
-        self.matched.append(contact)
-      else:
-        self.unmatched.append(contact)
-
-    # -- Create 'empty' contacts with birthdays...
-    #
-    if self.create_empty:
-      for name in self.friends:
-        friend = self.friends[name]
-        if 'contact' in friend or 'birthday_date' not in friend or not friend['birthday_date']:
-          continue
-
-        contact = evolution.ebook.EContact()
-        contact.props.full_name = friend['name']
-        contact.props.given_name = friend['first_name']
-        contact.props.family_name = friend['last_name']
-
-        self.update_contact(contact, friend)
-   
-        self.addresses.add_contact(contact)
-        self.updated.append(contact)
-        self.addresses.commit_contact(contact)
-
-        print "Created [%s]" % (contact.get_name())
-        self.matched.append(contact)
-
-    self.store.close()
-
-
-  # -----------------------------------------------------------------------
-  def update_contact(self, contact, friend, resync = False):
-    """Update the given contact with information from the 'friend'
-       dictionary."""
-
-    updated = False
-    friend['contact'] = contact
-      
-    if friend['pic'] and (resync or contact.get_property('photo') is None):
-      updated = self.store.set_photo(contact, friend['pic']) or updated
+    # -----------------------------------------------------------------------
+    def do_fb_login(self):
+        """Perform authentication against Facebook and store the result in gconf
+             for later use. Uses the 'need_auth' and 'block_for_auth' methods on
+             the callback class. The former allows a message to warn the user
+             about what is about to happen to be shown; the second is to wait
+             for the user to confirm they have logged in."""
+        self.fb.session_key = None
+        self.fb.secret = None
+        self.fb.uid = None
         
-    if friend['birthday_date'] and (resync or contact.get_property('birth-date') is None):
-      date_str = friend['birthday_date'].split('/')
-      date_str.append('0')
-      updated = self.store.set_birthday(contact, int(date_str[1]),
-                                                 int(date_str[0]),
-                                                 int(date_str[2])) or updated
-
-    if 'profile_url' in friend and friend['profile_url']:
-      updated = self.store.add_url(contact, friend['profile_url'], unique='facebook.com') or updated
+        self.callback.need_auth()
+        self.fb.auth.createToken()
+        self.fb.login()
+        self.callback.block_for_auth()
+        session = self.fb.auth.getSession()
+        
+        self.gc.set_string('/apps/maemo/hermes/session_key', session['session_key'])
+        self.gc.set_string('/apps/maemo/hermes/secret_key', session['secret'])
+        self.gc.set_string('/apps/maemo/hermes/uid', str(session['uid']))
+    
+    
+    # -----------------------------------------------------------------------
+    def load_friends(self):
+        """Load information on the authenticated user's friends. If no user is
+           currently authenticated, prompts for a login."""
+        
+        self.friends = {}
+        self.blocked_pictures = []
+        self.callback.progress(0, 0)
+        self.friends_by_url = {}
+        
+        # -- Get a user session and retrieve Facebook friends...
+        #
+        if self.facebook:
+            print "+++ Opening Facebook..."
+            if self.fb.session_key is None:
+                self.fb.session_key = self.gc.get_string('/apps/maemo/hermes/session_key')
+                self.fb.secret = self.gc.get_string('/apps/maemo/hermes/secret_key')
+                self.fb.uid = self.gc.get_string('/apps/maemo/hermes/uid')
             
-    if 'twitter_url' in friend and friend['twitter_url']:
-      updated = self.store.add_url(contact, friend['twitter_url'], unique='twitter.com') or updated
+            # Check the available session is still valid...
+            while True:
+                try:
+                    if self.fb.users.getLoggedInUser() and self.fb.session_key:
+                        break
+                except FacebookError:
+                    pass
+                self.do_fb_login()
             
-    if 'homepage' in friend and friend['homepage']:
-      updated = self.store.add_url(contact, friend['homepage']) or updated
-
-    return updated 
+            # Get the list of friends...
+            attrs = ['uid', 'name', 'pic_big', 'birthday_date', 'profile_url', 'first_name', 'last_name', 'website']
+            for friend in self.fb.users.getInfo(self.fb.friends.get(), attrs):
+                key = unicode(friend['name']).encode('trans')
+                self.friends[key] = friend
+            
+                if 'profile_url' not in friend:
+                    friend['profile_url'] = "http://www.facebook.com/profile.php?id=" + str(friend ['uid'])
+            
+                self.friends_by_url[friend['profile_url']] = friend
+                friend['pic']  = friend[attrs[2]]
+                friend['account'] = 'facebook'
+            
+                if 'website' in friend and friend['website']:
+                    friend['homepage'] = friend['website']
+            
+                if 'pic' not in friend or not friend['pic']:
+                    self.blocked_pictures.append(friend)
+              
+              
+        # -- Retrieve following information from Twitter...
+        #
+        if self.twitter is not None:
+            print "+++ Opening Twitter..."
+            (user, passwd) = self.twitter
+            api = twitter.Api(username=user, password=passwd)
+            users = api.GetFriends()
+            for tweeter in api.GetFriends():
+                key    = unicode(tweeter.name).encode('trans')
+                url    = 'http://twitter.com/%s' % (tweeter.screen_name)
+                friend = {'name':          tweeter.name, 'pic': tweeter.profile_image_url,
+                          'birthday_date': None,         'twitter_url': url,
+                          'homepage':      tweeter.url,  'account': 'twitter'}
+                if friend['pic'].find('/default_profile') > -1:
+                    friend['pic'] = None
+              
+                self.friends[key] = friend
+                self.friends_by_url[url] = friend
+        
+        # TODO What if the user has *no* contacts?
+    
+    
+    # -----------------------------------------------------------------------
+    def sync_contacts(self, resync = False):
+        """Synchronise Facebook profiles to contact database. If resync is false,
+           no existing information will be overwritten."""
+        
+        # -- Find addresses...
+        #
+        print "+++ Syncing contacts..."
+        self.addresses = evolution.ebook.open_addressbook('default')
+        print "+++ Addressbook opened..."
+        self.store = ContactStore(self.addresses)
+        print "+++ Contact store created..."
+        self.updated = []
+        self.unmatched = []
+        self.matched = []
+        contacts = self.addresses.get_all_contacts()
+        contacts.sort(key=lambda obj: obj.get_name())
+        current = 0
+        maximum = len(contacts)
+        for contact in contacts:
+            current += 1
+            self.callback.progress(current, maximum)
+            found = False
+            updated = False
+            
+            # Try match on existing URL...
+            for url in self.store.get_urls(contact):
+                if url in self.friends_by_url:
+                    updated = self.update_contact(contact, self.friends_by_url[url], resync)
+                    found = True
+                    if updated:
+                        break
+            
+            # Fallback to names...
+            if not found:
+                for name in names.variants(contact.get_name()):
+                    if name in self.friends:
+                        updated = self.update_contact(contact, self.friends[name], resync)
+                        found = True
+                        if updated:
+                            break
+            
+            # Keep track of updated stuff...
+            if updated:
+                self.updated.append(contact)
+                self.addresses.commit_contact(contact)
+                print "Saved changes to [%s]" % (contact.get_name())
+            
+            if found:
+                self.matched.append(contact)
+            else:
+                self.unmatched.append(contact)
+        
+        # -- Create 'empty' contacts with birthdays...
+        #
+        if self.create_empty:
+            for name in self.friends:
+                friend = self.friends[name]
+                if 'contact' in friend or 'birthday_date' not in friend or not friend['birthday_date']:
+                    continue
+                
+                contact = evolution.ebook.EContact()
+                contact.props.full_name = friend['name']
+                contact.props.given_name = friend['first_name']
+                contact.props.family_name = friend['last_name']
+                
+                self.update_contact(contact, friend)
+                
+                self.addresses.add_contact(contact)
+                self.updated.append(contact)
+                self.addresses.commit_contact(contact)
+                
+                print "Created [%s]" % (contact.get_name())
+                self.matched.append(contact)
+        
+        self.store.close()
+    
+    
+    # -----------------------------------------------------------------------
+    def update_contact(self, contact, friend, resync = False):
+        """Update the given contact with information from the 'friend'
+           dictionary."""
+        
+        updated = False
+        friend['contact'] = contact
+          
+        if 'pic' in friend and friend['pic'] and (resync or contact.get_property('photo') is None):
+            updated = self.store.set_photo(contact, friend['pic']) or updated
+            
+        if 'birthday_date' in friend and friend['birthday_date'] and (resync or contact.get_property('birth-date') is None):
+            date_str = friend['birthday_date'].split('/')
+            date_str.append('0')
+            updated = self.store.set_birthday(contact, int(date_str[1]),
+                                                       int(date_str[0]),
+                                                       int(date_str[2])) or updated
+        
+        if 'profile_url' in friend and friend['profile_url']:
+            updated = self.store.add_url(contact, friend['profile_url'], unique='facebook.com') or updated
+                
+        if 'twitter_url' in friend and friend['twitter_url']:
+            updated = self.store.add_url(contact, friend['twitter_url'], unique='twitter.com') or updated
+                
+        if 'homepage' in friend and friend['homepage']:
+            updated = self.store.add_url(contact, friend['homepage']) or updated
+        
+        return updated