Add v0.0.6 of Hermes from source tarball
[hermes] / package / src / hermes.py
1 import os.path
2 import evolution
3 from facebook import Facebook, FacebookError
4 import twitter
5 import gnome.gconf
6 from contacts import ContactStore
7 import names
8
9 class Hermes:
10   """Encapsulate the process of syncing Facebook friends' information with the
11      Evolution contacts' database. This should be used as follows:
12      
13        * Initialise, passing in a callback (methods: need_auth(),
14          block_for_auth(), use_twitter(), use_facebook()).
15        * Call load_friends().
16        * Call sync_contacts().
17        * Retrieve information on changes effected.
18        
19      This requires two gconf paths to contain Facebook application keys:
20          /apps/maemo/hermes/key_app
21          /apps/maemo/hermes/key_secret
22        
23      Copyright (c) Andrew Flegg <andrew@bleb.org> 2009.
24      Released under the Artistic Licence."""
25
26
27   # -----------------------------------------------------------------------
28   def __init__(self, callback, twitter = None, facebook = False):
29     """Constructor. Passed a callback which must implement three informational
30        methods:
31        
32          need_auth() - called to indicate a login is about to occur. The user
33                        should be informed.
34                        
35          block_for_auth() - prompt the user to take some action once they have
36                             successfully logged in to Facebook.
37                           
38          progress(i, j) - the application is currently processing friend 'i' of
39                           'j'. Should be used to provide the user a progress bar.
40                           
41       Other parameters:
42          twitter - a username/password tuple or None if Twitter should not be
43                    used. Defaults to None.
44                    
45          facebook - boolean indicating if Facebook should be used. Defaults to
46                     False.
47                           """
48
49     self.gc       = gnome.gconf.client_get_default()
50     self.callback = callback
51     self.twitter  = twitter
52     self.facebook = facebook
53
54     # -- Check the environment is going to work...
55     #
56     if (self.gc.get_string('/desktop/gnome/url-handlers/http/command') == 'epiphany %s'):
57       raise Exception('Browser in gconf invalid (see NB#136012). Installation error.')
58
59     # -- Get private keys for this app...
60     #
61     key_app    = self.gc.get_string('/apps/maemo/hermes/key_app')
62     key_secret = self.gc.get_string('/apps/maemo/hermes/key_secret')
63     if (key_app is None or key_secret is None):
64       raise Exception('No Facebook application keys found. Installation error.')
65
66     self.fb = Facebook(key_app, key_secret)
67     self.fb.desktop = True
68
69
70   # -----------------------------------------------------------------------
71   def do_fb_login(self):
72     """Perform authentication against Facebook and store the result in gconf
73          for later use. Uses the 'need_auth' and 'block_for_auth' methods on
74          the callback class. The former allows a message to warn the user
75          about what is about to happen to be shown; the second is to wait
76          for the user to confirm they have logged in."""
77     self.fb.session_key = None
78     self.fb.secret = None
79     self.fb.uid = None
80     
81     self.callback.need_auth()
82     self.fb.auth.createToken()
83     self.fb.login()
84     self.callback.block_for_auth()
85     session = self.fb.auth.getSession()
86
87     self.gc.set_string('/apps/maemo/hermes/session_key', session['session_key'])
88     self.gc.set_string('/apps/maemo/hermes/secret_key', session['secret'])
89     self.gc.set_int('/apps/maemo/hermes/uid', session['uid'])
90
91
92   # -----------------------------------------------------------------------
93   def load_friends(self):
94     """Load information on the authenticated user's friends. If no user is
95        currently authenticated, prompts for a login."""
96
97     self.friends = {}
98     self.blocked_pictures = []
99     self.callback.progress(0, 0)
100     
101     # -- Get a user session and retrieve Facebook friends...
102     #
103     if self.facebook:
104       if self.fb.session_key is None:
105         self.fb.session_key = self.gc.get_string('/apps/maemo/hermes/session_key')
106         self.fb.secret = self.gc.get_string('/apps/maemo/hermes/secret_key')
107         self.fb.uid = self.gc.get_int('/apps/maemo/hermes/uid')
108
109       # Check the available session is still valid...
110       while True:
111         try:
112           if self.fb.users.getLoggedInUser() and self.fb.session_key:
113             break
114         except FacebookError:
115           pass
116         self.do_fb_login()
117
118       # Get the list of friends...
119       attrs = ['uid', 'name', 'pic_big', 'birthday_date', 'profile_url']
120       for friend in self.fb.users.getInfo(self.fb.friends.get(), attrs):
121         friend['pic'] = friend[attrs[2]]
122         self.friends[friend['name']] = friend
123         if not friend['pic']:
124           self.blocked_pictures.append(friend)
125           
126     # -- Retrieve following information from Twitter...
127     #
128     if self.twitter is not None:
129       (user, passwd) = self.twitter
130       api = twitter.Api(username=user, password=passwd)
131       users = api.GetFriends()
132       for friend in api.GetFriends():
133         self.friends[friend.name] = {'name': friend.name, 'pic': friend.profile_image_url, 'birthday_date': None, 'twitter_url': 'http://twitter.com/%s' % (friend.screen_name), 'homepage' : friend.url}
134   
135     # TODO What if the user has *no* contacts?
136
137   
138   # -----------------------------------------------------------------------
139   def sync_contacts(self, resync = False):
140     """Synchronise Facebook profiles to contact database. If resync is false,
141        no existing information will be overwritten."""
142
143     # -- Find addresses...
144     #
145     print "+++ Syncing contacts..."
146     addresses = evolution.ebook.open_addressbook('default')
147     print "+++ Addressbook opened..."
148     store = ContactStore(addresses)
149     print "+++ Contact store created..."
150     self.updated = []
151     self.unmatched = []
152     self.matched = []
153     contacts = addresses.get_all_contacts()
154     contacts.sort(key=lambda obj: obj.get_name())
155     current = 0
156     maximum = len(contacts)
157     for contact in contacts:
158       current += 1
159       self.callback.progress(current, maximum)
160       found = False
161       for name in names.variants(contact.get_name()):
162         if name in self.friends:
163           friend = self.friends[name]
164           found = True
165           updated = False
166       
167           if friend['pic'] and (resync or contact.get_property('photo') is None):
168             updated = store.set_photo(contact, friend['pic']) or updated
169         
170           if friend['birthday_date'] and (resync or contact.get_property('birth-date') is None):
171             date_str = friend['birthday_date'].split('/')
172             date_str.append('0')
173             updated = store.set_birthday(contact, int(date_str[1]),
174                                                   int(date_str[0]),
175                                                   int(date_str[2])) or updated
176
177           if 'profile_url' in friend and friend['profile_url']:
178             updated = store.add_url(contact, friend['profile_url'], unique='facebook.com') or updated
179             
180           if 'twitter_url' in friend and friend['twitter_url']:
181             updated = store.add_url(contact, friend['twitter_url'], unique='twitter.com') or updated
182             
183           if 'homepage' in friend and friend['homepage']:
184             updated = store.add_url(contact, friend['homepage']) or updated
185     
186           if updated:
187             self.updated.append(contact)
188             addresses.commit_contact(contact)
189             print "Saved changes to [%s]" % (contact.get_name())
190             
191           break
192       
193       if found:
194         self.matched.append(contact)
195       else:
196         self.unmatched.append(contact)
197
198     store.close()
199