+from facebook import Facebook, FacebookError
+import gnome.gconf
import org.maemo.hermes.engine.provider
import org.maemo.hermes.engine.facebook.service
class Provider(org.maemo.hermes.engine.provider.Provider):
"""Facebook provider for Hermes.
+ 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> 2010.
Released under the Artistic Licence."""
"""Open the preferences for this provider as a child of the 'parent' widget."""
print "Err, open preferences. OK. Err, right. Hmm."
+
+
+ # -----------------------------------------------------------------------
+ def prepare_service(self):
+ # -- 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.')
+
+ 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
+
+ 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()
+
# -----------------------------------------------------------------------
def service(self, gui_callback):
- """Return the service backend. This must be a class which implements the
- following methods:
- * get_friends
- * process_contact
- * finalise
+ return org.maemo.hermes.engine.facebook.service.Service(self.fb)
+
+
+ # -----------------------------------------------------------------------
+ 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
- See Service for more details."""
-
- return org.maemo.hermes.engine.facebook.service.Service()
+ if self._gui:
+ self._gui.need_auth()
+
+ self.fb.auth.createToken()
+ self.fb.login()
+
+ if self._gui:
+ self._gui.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']))
-import gnome.gconf
import org.maemo.hermes.engine.service
-from facebook import Facebook
from org.maemo.hermes.engine.names import canonical
class Service(org.maemo.hermes.engine.service.Service):
"""Facebook backend for Hermes.
- 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> 2010.
+ Copyright (c) Fredrik Wendt <fredrik@wendt.se> 2010.
Released under the Artistic Licence."""
+ attrs = ['uid', 'name', 'pic_big', 'birthday_date', 'profile_url', 'first_name', 'last_name', 'website']
# -----------------------------------------------------------------------
- def __init__(self, autocreate = False, gui_callback = None):
+ def __init__(self, facebook, autocreate = False, gui_callback = None):
"""Initialise the Facebook service, finding Facebook API keys in gconf and
having a gui_callback available."""
- self._gc = gnome.gconf.client_get_default()
self._gui = gui_callback
self._autocreate = autocreate
+ self.fb = facebook
- # -- 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.')
-
- 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._friends = None
+ self._friends_by_id = {}
self._friends_by_url = {}
- self._should_create = set()
+ self._friends_by_contact = {}
+ self._contacts_by_friend = {}
+ self._friends_without_contact = set()
+ self._known_urls = set()
# -----------------------------------------------------------------------
- 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 self._gui:
- self._gui.need_auth()
-
- self.fb.auth.createToken()
- self.fb.login()
-
- if self._gui:
- self._gui.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 get_name(self):
+ return "Facebook"
+
+
# -----------------------------------------------------------------------
def get_friends(self):
- """Return a list of friends from this service, or 'None' if manual mapping
- is not supported."""
-
- if self._friends:
- return self._friends.values()
-
- 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')
+ return self._contacts_by_friend.keys()
+
+
+ def get_contacts_with_match(self):
+ return self._friends_by_contact
+
+ def get_unmatched_friends(self):
+ return self._friends_by_id.values()
+
+ # -----------------------------------------------------------------------
+ def pre_process_contact(self, contact):
+ """Registers URLs of all previous mappings, and makes sure that any Friends with those
+ URLs don't get match by name."""
+ for url in contact.get_urls():
+ self._known_urls.add(url)
+
+
+ # -----------------------------------------------------------------------
+ def process_friends(self):
- # 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()
-
- self._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):
- print "#"
- friend['key']= canonical(friend['name'])
- self._friends[friend['key']] = friend
+ def if_defined(data, key, callback):
+ if key in data and data[key]:
+ callback(data[key])
- if 'profile_url' not in friend:
- friend['profile_url'] = "http://www.facebook.com/profile.php?id=" + str(friend ['uid'])
+ friends_data = self._get_friends_data()
+ for data in friends_data:
+ key = canonical(data['name']) # FIXME: deal with name collision
+ friend = self._create_friend(data['name'])
- self._friends_by_url[friend['profile_url']] = friend
- friend['pic'] = friend[attrs[2]]
- friend['account'] = 'Facebook'
+ if 'profile_url' not in data:
+ data['profile_url'] = "http://www.facebook.com/profile.php?id=" + str(data['uid'])
- if 'website' in friend and friend['website']:
- friend['homepage'] = friend['website']
-
- if 'birthday_date' in friend and friend['birthday_date']:
- self._should_create.add(friend['profile_url'])
-
- return self._friends.values()
+ if_defined(data, 'website', friend.add_url)
+ if_defined(data, 'profile_url', friend.add_url)
+ if_defined(data, 'birthday_date', friend.set_birthday_date)
-
- # -----------------------------------------------------------------------
- def process_contact(self, contact, overwrite = False, friend = None):
- """Called for each contact in the address book. Any friends linked to
- from the contact should have their matching updated. The backend should
- enrich the contact with any meta-data it can; and return 'True' if any
- information was updated. If 'friend' is provided, the information should
- be retrieved from friend. This will be one of the entries from
- 'get_friends' when manual mapping is being done."""
-
- if not self._friends:
- self.get_friends()
+ if_defined(data, 'pic_big', friend.set_photo_url)
+ if friend.has_birthday_date(): # FIXME: remove this, either you want to add your contacts or not?
+ self._friends_without_contact.add(friend)
+ url = data['profile_url']
+ self._friends_by_url[url] = friend
+
+ if url not in self._known_urls:
+ self._friends_by_id[key] = friend
+
+ # -----------------------------------------------------------------------
+ def process_contact(self, contact):
+ if self._friends_by_contact.has_key(contact):
+ return
+
matched_friend = None
+ # we might get a hit if the friend has setup a URL with another service,
+ # such as putting the id link to Facebook on the Twitter account's profile
for url in contact.get_urls():
if url in self._friends_by_url:
matched_friend = self._friends_by_url[url]
+ self._register_match(contact, matched_friend)
+ print contact.get_name(), " -> match by url -> ", matched_friend
break
if not matched_friend:
for id in contact.get_identifiers():
- if id in self._friends:
- matched_friend = self._friends[id]
+ if id in self._friends_by_id:
+ matched_friend = self._friends_by_id.pop(id)
+ self._register_match(contact, matched_friend)
+ print contact.get_name(), " -> match by name -> ", matched_friend
break
-
- if matched_friend:
- print contact.get_name(), " -> ", matched_friend['profile_url']
- self._should_create.discard(matched_friend['profile_url'])
- else:
- print contact.get_name(), " no match to Facebook with ", contact.get_identifiers()
-
-
+
# -----------------------------------------------------------------------
- def finalise(self, updated, overwrite = False):
- """Once all contacts have been processed, allows for any tidy-up/additional
- enrichment. If any contacts are updated at this stage, 'updated' should
- be added to."""
+ def _register_match(self, contact, friend):
+ self._friends_without_contact.discard(friend)
+ self._friends_by_contact[contact] = friend
+ self._contacts_by_friend[friend] = contact
- if self._autocreate:
- for url in self._should_create:
- print "Need to autocreate:", self._friends_by_url[url]
+ # -----------------------------------------------------------------------
+ def _get_friends_data(self):
+ """Returns a list of dicts, where each dict represents a friend/contact"""
+
+ return self.fb.users.getInfo(self.fb.friends.get(), Service.attrs)
+from friend import Friend
+
class Service:
"""The notional `Service' for a provider. This is responsible for communicating
with the backend service and enhancing contacts.
Copyright (c) Andrew Flegg <andrew@bleb.org> 2010.
Released under the Artistic Licence."""
+
+ def __init__(self):
+ """Should make initial calls to the service and retrieve a list of friends."""
# -----------------------------------------------------------------------
- def get_friends(self):
- """Return a list of friends from this service, or 'None' if manual mapping
- is not supported."""
+ def get_name(self):
+ """Should return the same name as the provider class - debugging only - REMOVE/FIXME"""
+
+ return None
+
+
+ # -----------------------------------------------------------------------
+ def pre_process_contact(self, contact):
+ """If the contact have an URL (or similar) that proves an existing matching
+ to a friend on this service, then this should be remembered to avoid
+ name collision/mapping the same friend to other contacts with the same
+ name."""
return None
+ def process_friends(self):
+ """Called once to signal that it's time to get all friends' data."""
+
+ pass
+
+
# -----------------------------------------------------------------------
- def process_contact(self, contact, overwrite = False, friend = None):
+ def process_contact(self, contact):
"""Called for each contact in the address book. Any friends linked to
- from the contact should have their matching updated. The backend should
- enrich the contact with any meta-data it can; and return 'True' if any
- information was updated. If 'friend' is provided, the information should
- be retrieved from friend. This will be one of the entries from
- 'get_friends' when manual mapping is being done."""
+ from the contact should have their matching updated. The back end should
+ enrich the friend passed with any meta-data it can."""
pass
# -----------------------------------------------------------------------
- def finalise(self, updated, overwrite = False):
+ def get_unmatched_friends(self):
+ """Return a list of friends not matched to a contact, or 'None' if manual mapping
+ is not supported."""
+
+ return None
+
+
+ # -----------------------------------------------------------------------
+ def finalise(self, updated, overwrite=False):
"""Once all contacts have been processed, allows for any tidy-up/additional
enrichment. If any contacts are updated at this stage, 'updated' should
be added to."""
pass
+
+
+ # -----------------------------------------------------------------------
+ def _create_friend(self, name):
+ """Used to create a Friend object for a contact"""
+
+ return Friend(name, self.get_name())