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