Join up GUI, by adding 'Hermes' as a wrapper around Syncjob. This will
[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         api_key = self._gc.get_string('/apps/maemo/hermes/linkedin_key')
33         secret_key = self._gc.get_string('/apps/maemo/hermes/linkedin_secret')
34
35         if api_key is None or secret_key is None:
36             raise Exception('No LinkedIn application keys found. Installation error.')
37
38         self.access_token = self._get_access_token_from_gconf()
39
40         self.consumer = oauth.OAuthConsumer(api_key, secret_key)
41         self.sig_method = oauth.OAuthSignatureMethod_HMAC_SHA1()
42         
43         # IMPROVEMENT: verify that the access_token is valid for at least another hour
44         self._verify_access_token()
45         
46
47     # -----------------------------------------------------------------------
48     def authenticate(self, need_auth, block_for_auth):
49         need_auth()
50         token = self._get_request_token()
51         url = self._get_authorize_url(token)
52         verifier = block_for_auth(True)
53         self._verify_verifier(verifier)
54     
55     
56     # -----------------------------------------------------------------------
57     def get_friends(self):
58         """ Returns a Friend object for each LinkedIn connection."""
59         
60         xml = self._make_api_request(self.LI_CONN_API_URL)
61         dom = parseString(xml)
62         friends = self._parse_dom(dom)
63
64         return friends
65         
66
67     # -----------------------------------------------------------------------
68     def _verify_verifier(self, verifier):
69         try:
70             self.access_token = self._get_access_token(self.request_token, verifier)
71             self._store_access_token_in_gconf()
72         except:
73             raise Exception("authorization failed, try again")
74     
75         
76     # -----------------------------------------------------------------------
77     def _verify_access_token(self):
78         return True
79
80
81     # -----------------------------------------------------------------------
82     def _store_access_token_in_gconf(self, token_str):
83         self._gc.set_string('/apps/maemo/hermes/linkedin_access_token', token_str)
84
85         
86     # -----------------------------------------------------------------------
87     def _get_access_token_from_gconf(self):
88         token_str = self._gc.get_string('/apps/maemo/hermes/linkedin_access_token')
89         return oauth.OAuthToken.from_string(token_str)
90
91     
92     # -----------------------------------------------------------------------
93     def _make_api_request(self, url):
94         print "_make_api_request", url
95         oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, token=self.access_token, http_url=url)
96         oauth_request.sign_request(self.sig_method, self.consumer, self.access_token)
97         connection = httplib.HTTPSConnection(self.LI_SERVER)
98         try:
99             connection.request(oauth_request.http_method, url, headers=oauth_request.to_header())
100             return connection.getresponse().read()
101         except:
102             raise Exception("Failed to contact LinkedIn at " + url)
103
104
105     # -----------------------------------------------------------------------
106     def _parse_dom(self, dom):
107         print "parse_dom", dom
108         def get_first_tag(node, tagName):
109             tags = node.getElementsByTagName(tagName)
110             if tags and len(tags) > 0:
111                 return tags[0]
112         
113         def extract(node, tagName):
114             tag = get_first_tag(node, tagName)
115             if tag:
116                 return tag.firstChild.nodeValue
117             
118         def extract_public_url(node):
119             tag = get_first_tag(node, 'site-standard-profile-request')
120             if tag:
121                 url = extract(tag, 'url')
122                 return url.replace("&amp;", "&")
123         
124         # look for errors
125         errors = dom.getElementsByTagName('error')
126         if (len(errors) > 0):
127             print "Error" # FIXME: handle this better
128             return []
129         
130         friends = []
131         people = dom.getElementsByTagName('person')
132         for p in people:
133             try:
134                 fn = extract(p, 'first-name')
135                 ln = extract(p, 'last-name')
136                 photo_url = extract(p, 'picture-url')
137                 id = extract(p, 'id')
138                 public_url = extract_public_url(p)
139
140                 name = fn + " " + ln
141                 friend = Friend(name)
142                 friend.add_url(public_url)
143                 if photo_url:
144                     friend.set_photo_url(photo_url)
145                 
146                 friends.append(friend)
147
148             except:
149                 pass   
150
151     
152     # -----------------------------------------------------------------------
153     def _get_access_token(self, token, verifier):
154         """If the verifier (which was displayed in the browser window) is valid,
155            then an access token is returned which should be used to access data on the service."""
156         
157         oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, token=token, verifier=verifier, http_url=self.ACCESS_TOKEN_URL)
158         oauth_request.sign_request(self.sig_method, self.consumer, token)
159
160         connection = httplib.HTTPSConnection(self.LI_SERVER)
161         connection.request(oauth_request.http_method, self.ACCESS_TOKEN_URL, headers=oauth_request.to_header()) 
162         response = connection.getresponse()
163         token_str = response.read()
164         self._store_access_token_in_gconf(token_str)
165         return oauth.OAuthToken.from_string(token_str)
166
167
168     # -----------------------------------------------------------------------
169     def _get_authorize_url(self, token):
170         """The URL that the user should browse to, in order to authorize the application to acess data"""
171         
172         oauth_request = oauth.OAuthRequest.from_token_and_callback(token=token, http_url=self.AUTHORIZE_URL)
173         return oauth_request.to_url()
174
175
176     # -----------------------------------------------------------------------
177     def _get_request_token(self):
178         """Get a request token from LinkedIn"""
179         
180         oauth_consumer_key = self.api_key
181         oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, callback="oob", http_url=self.REQUEST_TOKEN_URL)
182         oauth_request.sign_request(self.sig_method, self.consumer, None)
183
184         connection = httplib.HTTPSConnection(self.LI_SERVER)
185         connection.request(oauth_request.http_method, self.REQUEST_TOKEN_URL, headers=oauth_request.to_header())
186         response = self.connection.getresponse().read()
187         
188         token = oauth.OAuthToken.from_string(response)
189         return token
190