3 from facebook import Facebook, FacebookError
7 from contacts import ContactStore
11 """Encapsulate the process of syncing Facebook friends' information with the
12 Evolution contacts' database. This should be used as follows:
14 * Initialise, passing in a callback (methods: need_auth(),
15 block_for_auth(), use_twitter(), use_facebook()).
16 * Call load_friends().
17 * Call sync_contacts().
18 * Retrieve information on changes effected.
20 This requires two gconf paths to contain Facebook application keys:
21 /apps/maemo/hermes/key_app
22 /apps/maemo/hermes/key_secret
24 Copyright (c) Andrew Flegg <andrew@bleb.org> 2009.
25 Released under the Artistic Licence."""
28 # -----------------------------------------------------------------------
29 def __init__(self, callback, twitter = None, facebook = False, empty = False):
30 """Constructor. Passed a callback which must implement three informational
33 need_auth() - called to indicate a login is about to occur. The user
36 block_for_auth() - prompt the user to take some action once they have
37 successfully logged in to Facebook.
39 progress(i, j) - the application is currently processing friend 'i' of
40 'j'. Should be used to provide the user a progress bar.
43 twitter - a username/password tuple or None if Twitter should not be
44 used. Defaults to None.
46 facebook - boolean indicating if Facebook should be used. Defaults to
49 empty - boolean indicating if 'empty' contacts consisting of a profile
50 URL and birthday should be created.
53 self.gc = gnome.gconf.client_get_default()
54 self.callback = callback
55 self.twitter = twitter
56 self.facebook = facebook
57 self.create_empty = empty
59 # -- Check the environment is going to work...
61 if (self.gc.get_string('/desktop/gnome/url-handlers/http/command') == 'epiphany %s'):
62 raise Exception('Browser in gconf invalid (see NB#136012). Installation error.')
64 # -- Get private keys for this app...
66 key_app = self.gc.get_string('/apps/maemo/hermes/key_app')
67 key_secret = self.gc.get_string('/apps/maemo/hermes/key_secret')
68 if (key_app is None or key_secret is None):
69 raise Exception('No Facebook application keys found. Installation error.')
71 self.fb = Facebook(key_app, key_secret)
72 self.fb.desktop = True
75 # -----------------------------------------------------------------------
76 def do_fb_login(self):
77 """Perform authentication against Facebook and store the result in gconf
78 for later use. Uses the 'need_auth' and 'block_for_auth' methods on
79 the callback class. The former allows a message to warn the user
80 about what is about to happen to be shown; the second is to wait
81 for the user to confirm they have logged in."""
82 self.fb.session_key = None
86 self.callback.need_auth()
87 self.fb.auth.createToken()
89 self.callback.block_for_auth()
90 session = self.fb.auth.getSession()
92 self.gc.set_string('/apps/maemo/hermes/session_key', session['session_key'])
93 self.gc.set_string('/apps/maemo/hermes/secret_key', session['secret'])
94 self.gc.set_string('/apps/maemo/hermes/uid', session['uid'])
97 # -----------------------------------------------------------------------
98 def load_friends(self):
99 """Load information on the authenticated user's friends. If no user is
100 currently authenticated, prompts for a login."""
103 self.blocked_pictures = []
104 self.callback.progress(0, 0)
105 self.friends_by_url = {}
107 # -- Get a user session and retrieve Facebook friends...
110 print "+++ Opening Facebook..."
111 if self.fb.session_key is None:
112 self.fb.session_key = self.gc.get_string('/apps/maemo/hermes/session_key')
113 self.fb.secret = self.gc.get_string('/apps/maemo/hermes/secret_key')
114 self.fb.uid = self.gc.get_string('/apps/maemo/hermes/uid')
116 # Check the available session is still valid...
119 if self.fb.users.getLoggedInUser() and self.fb.session_key:
121 except FacebookError:
125 # Get the list of friends...
126 attrs = ['uid', 'name', 'pic_big', 'birthday_date', 'profile_url', 'first_name', 'last_name', 'website']
127 for friend in self.fb.users.getInfo(self.fb.friends.get(), attrs):
128 key = unicode(friend['name']).encode('trans')
129 self.friends[key] = friend
130 self.friends_by_url[friend['profile_url']] = friend
131 friend['pic'] = friend[attrs[2]]
132 friend['account'] = 'facebook'
133 if friend['website']:
134 friend['homepage'] = friend['website']
136 if not friend['pic']:
137 self.blocked_pictures.append(friend)
140 # -- Retrieve following information from Twitter...
142 if self.twitter is not None:
143 print "+++ Opening Twitter..."
144 (user, passwd) = self.twitter
145 api = twitter.Api(username=user, password=passwd)
146 users = api.GetFriends()
147 for friend in api.GetFriends():
148 key = unicode(friend.name).encode('trans')
149 url = 'http://twitter.com/%s' % (friend.screen_name)
150 self.friends[key] = {'name': friend.name, 'pic': friend.profile_image_url, 'birthday_date': None, 'twitter_url': url, 'homepage': friend.url, 'account': 'twitter'}
151 self.friends_by_url[url] = self.friends[key]
153 # TODO What if the user has *no* contacts?
156 # -----------------------------------------------------------------------
157 def sync_contacts(self, resync = False):
158 """Synchronise Facebook profiles to contact database. If resync is false,
159 no existing information will be overwritten."""
161 # -- Find addresses...
163 print "+++ Syncing contacts..."
164 self.addresses = evolution.ebook.open_addressbook('default')
165 print "+++ Addressbook opened..."
166 self.store = ContactStore(self.addresses)
167 print "+++ Contact store created..."
171 contacts = self.addresses.get_all_contacts()
172 contacts.sort(key=lambda obj: obj.get_name())
174 maximum = len(contacts)
175 for contact in contacts:
177 self.callback.progress(current, maximum)
181 # Try match on existing URL...
182 for url in self.store.get_urls(contact):
183 if url in self.friends_by_url:
184 updated = self.update_contact(contact, self.friends_by_url[url], resync)
189 # Fallback to names...
191 for name in names.variants(contact.get_name()):
192 if name in self.friends:
193 updated = self.update_contact(contact, self.friends[name], resync)
198 # Keep track of updated stuff...
200 self.updated.append(contact)
201 self.addresses.commit_contact(contact)
202 print "Saved changes to [%s]" % (contact.get_name())
205 self.matched.append(contact)
207 self.unmatched.append(contact)
209 # -- Create 'empty' contacts with birthdays...
211 if self.create_empty:
212 for name in self.friends:
213 friend = self.friends[name]
214 if 'contact' in friend or 'birthday_date' not in friend or not friend['birthday_date']:
217 contact = evolution.ebook.EContact()
218 contact.props.full_name = friend['name']
219 contact.props.given_name = friend['first_name']
220 contact.props.family_name = friend['last_name']
222 self.update_contact(contact, friend)
224 self.addresses.add_contact(contact)
225 self.updated.append(contact)
226 self.addresses.commit_contact(contact)
228 print "Created [%s]" % (contact.get_name())
229 self.matched.append(contact)
234 # -----------------------------------------------------------------------
235 def update_contact(self, contact, friend, resync = False):
236 """Update the given contact with information from the 'friend'
240 friend['contact'] = contact
242 if friend['pic'] and (resync or contact.get_property('photo') is None):
243 updated = self.store.set_photo(contact, friend['pic']) or updated
245 if friend['birthday_date'] and (resync or contact.get_property('birth-date') is None):
246 date_str = friend['birthday_date'].split('/')
248 updated = self.store.set_birthday(contact, int(date_str[1]),
250 int(date_str[2])) or updated
252 if 'profile_url' in friend and friend['profile_url']:
253 updated = self.store.add_url(contact, friend['profile_url'], unique='facebook.com') or updated
255 if 'twitter_url' in friend and friend['twitter_url']:
256 updated = self.store.add_url(contact, friend['twitter_url'], unique='twitter.com') or updated
258 if 'homepage' in friend and friend['homepage']:
259 updated = self.store.add_url(contact, friend['homepage']) or updated