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