mostly cleanup, minor adds, pseudo code in gtkui.py
[hermes] / package / src / org / maemo / hermes / engine / linkedin / api.py
1 import httplib
2 import gnome.gconf
3 from oauth import oauth
4 from xml.dom.minidom import parseString
5 from org.maemo.hermes.engine.friend import Friend
6
7 #    httplib.HTTPSConnection.debuglevel = 1
8
9 class LinkedInApi():
10     """LinkedIn API for Hermes.
11                 
12        Copyright (c) Fredrik Wendt <fredrik@wendt.se> 2010.
13        Released under the Artistic Licence."""
14        
15        
16     LI_SERVER = "api.linkedin.com"
17     LI_API_URL = "https://api.linkedin.com"
18     LI_CONN_API_URL = LI_API_URL + "/v1/people/~/connections"
19
20     REQUEST_TOKEN_URL = LI_API_URL + "/uas/oauth/requestToken"
21     AUTHORIZE_URL = LI_API_URL + "/uas/oauth/authorize"
22     ACCESS_TOKEN_URL = LI_API_URL + "/uas/oauth/accessToken"
23
24
25     # -----------------------------------------------------------------------
26     def __init__(self):
27         """Initialize the LinkedIn service, finding LinkedIn API keys in gconf and
28            having a gui_callback available."""
29         
30         self._gc = gnome.gconf.client_get_default()
31         
32         # -- Check the environment is going to work...
33         # FIXME: duplication
34         if (self._gc.get_string('/desktop/gnome/url-handlers/http/command') == 'epiphany %s'):
35             raise Exception('Browser in gconf invalid (see NB#136012). Installation error.')
36
37         api_key = self._gc.get_string('/apps/maemo/hermes/linkedin_api_key')
38         secret_key = self._gc.get_string('/apps/maemo/hermes/linkedin_key_secret')
39
40         # FIXME: move this to gconf and postinst
41         api_key = '1et4G-VtmtqNfY7gF8PHtxMOf0KNWl9ericlTEtdKJeoA4ubk4wEQwf8lSL8AnYE'
42         secret_key = 'uk--OtmWcxER-Yh6Py5p0VeLPNlDJSMaXj1xfHILoFzrK7fM9eepNo5RbwGdkRo_'
43
44         if api_key is None or secret_key is None:
45             raise Exception('No LinkedIn application keys found. Installation error.')
46
47         self.access_token = self._get_access_token_from_gconf()
48
49         self.consumer = oauth.OAuthConsumer(api_key, secret_key)
50         self.sig_method = oauth.OAuthSignatureMethod_HMAC_SHA1()
51         
52         # IMPROVEMENT: verify that the access_token is valid for at least another hour
53         self._verify_access_token()
54         
55
56     def authenticate(self, need_auth, block_for_auth):
57         need_auth()
58         token = self._get_request_token()
59         url = self._get_authorize_url(token)
60         verifier = block_for_auth(url)
61         self._verify_verifier(verifier)
62     
63     
64     # -----------------------------------------------------------------------
65     def get_friends(self):
66         """ Returns a Friend object for each LinkedIn connection."""
67         
68         xml = self._make_api_request(self.LI_CONN_API_URL)
69         dom = parseString(xml)
70         friends = self._parse_dom(dom)
71
72         return friends
73         
74
75     # -----------------------------------------------------------------------
76     def _verify_verifier(self, verifier):
77         try:
78             self.access_token = self._get_access_token(self.request_token, verifier)
79             self._store_access_token_in_gconf()
80         except:
81             raise Exception("authorization failed, try again")
82     
83         
84     # -----------------------------------------------------------------------
85     def _verify_access_token(self):
86         return True
87
88
89     # -----------------------------------------------------------------------
90     def _store_access_token_in_gconf(self, token_str):
91         self._gc.set_string('/apps/maemo/hermes/linkedin_access_token', token_str)
92
93         
94     # -----------------------------------------------------------------------
95     def _get_access_token_from_gconf(self):
96         token_str = self._gc.get_string('/apps/maemo/hermes/linkedin_access_token')
97         return oauth.OAuthToken.from_string(token_str)
98
99     
100     # -----------------------------------------------------------------------
101     def _make_api_request(self, url):
102         print "_make_api_request", url
103         oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, token=self.access_token, http_url=url)
104         oauth_request.sign_request(self.sig_method, self.consumer, self.access_token)
105         connection = httplib.HTTPSConnection(self.LI_SERVER)
106         try:
107             connection.request(oauth_request.http_method, url, headers=oauth_request.to_header())
108             return connection.getresponse().read()
109         except:
110             raise Exception("Failed to contact LinkedIn at " + url)
111
112
113     # -----------------------------------------------------------------------
114     def _parse_dom(self, dom):
115         print "parse_dom", dom
116         def get_first_tag(node, tagName):
117             tags = node.getElementsByTagName(tagName)
118             if tags and len(tags) > 0:
119                 return tags[0]
120         
121         def extract(node, tagName):
122             tag = get_first_tag(node, tagName)
123             if tag:
124                 return tag.firstChild.nodeValue
125             
126         def extract_public_url(node):
127             tag = get_first_tag(node, 'site-standard-profile-request')
128             if tag:
129                 url = extract(tag, 'url')
130                 return url.replace("&amp;", "&")
131         
132         # look for errors
133         errors = dom.getElementsByTagName('error')
134         if (len(errors) > 0):
135             print "Error" # FIXME: handle this better
136             return []
137         
138         friends = []
139         people = dom.getElementsByTagName('person')
140         for p in people:
141             try:
142                 fn = extract(p, 'first-name')
143                 ln = extract(p, 'last-name')
144                 photo_url = extract(p, 'picture-url')
145                 id = extract(p, 'id')
146                 public_url = extract_public_url(p)
147
148                 name = fn + " " + ln
149                 friend = Friend(name)
150                 friend.add_url(public_url)
151                 if photo_url:
152                     friend.set_photo_url(photo_url)
153                 
154                 friends.append(friend)
155
156             except:
157                 pass   
158
159     
160     # -----------------------------------------------------------------------
161     def _get_access_token(self, token, verifier):
162         """If the verifier (which was displayed in the browser window) is valid,
163            then an access token is returned which should be used to access data on the service."""
164         
165         oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, token=token, verifier=verifier, http_url=self.ACCESS_TOKEN_URL)
166         oauth_request.sign_request(self.sig_method, self.consumer, token)
167
168         connection = httplib.HTTPSConnection(self.LI_SERVER)
169         connection.request(oauth_request.http_method, self.ACCESS_TOKEN_URL, headers=oauth_request.to_header()) 
170         response = connection.getresponse()
171         token_str = response.read()
172         self._store_access_token_in_gconf(token_str)
173         return oauth.OAuthToken.from_string(token_str)
174
175
176     # -----------------------------------------------------------------------
177     def _get_authorize_url(self, token):
178         """The URL that the user should browse to, in order to authorize the application to acess data"""
179         
180         oauth_request = oauth.OAuthRequest.from_token_and_callback(token=token, http_url=self.AUTHORIZE_URL)
181         return oauth_request.to_url()
182
183
184     # -----------------------------------------------------------------------
185     def _get_request_token(self):
186         """Get a request token from LinkedIn"""
187         
188         oauth_consumer_key = self.api_key
189         oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, callback="oob", http_url=self.REQUEST_TOKEN_URL)
190         oauth_request.sign_request(self.sig_method, self.consumer, None)
191
192         connection = httplib.HTTPSConnection(self.LI_SERVER)
193         connection.request(oauth_request.http_method, self.REQUEST_TOKEN_URL, headers=oauth_request.to_header())
194         response = self.connection.getresponse().read()
195         
196         token = oauth.OAuthToken.from_string(response)
197         return token
198