3 from oauth import oauth
4 from xml.dom.minidom import parseString
6 import org.maemo.hermes.engine.service
7 from org.maemo.hermes.engine.friend import Friend
9 from org.maemo.hermes.engine.names import canonical
11 # httplib.HTTPSConnection.debuglevel = 1
13 class Service(org.maemo.hermes.engine.service.Service):
14 """LinkedIn backend for Hermes.
16 This sets up two gconf paths to contain LinkedIn OAuth keys:
17 /apps/maemo/hermes/linkedin_oauth
18 /apps/maemo/hermes/linkedin_verifier
20 Copyright (c) Fredrik Wendt <fredrik@wendt.se> 2010.
21 Released under the Artistic Licence."""
24 LI_SERVER = "api.linkedin.com"
25 LI_API_URL = "https://api.linkedin.com"
26 LI_CONN_API_URL = LI_API_URL + "/v1/people/~/connections"
28 REQUEST_TOKEN_URL = LI_API_URL + "/uas/oauth/requestToken"
29 AUTHORIZE_URL = LI_API_URL + "/uas/oauth/authorize"
30 ACCESS_TOKEN_URL = LI_API_URL + "/uas/oauth/accessToken"
33 # -----------------------------------------------------------------------
34 def __init__(self, autocreate=False, gui_callback=None):
35 """Initialize the LinkedIn service, finding LinkedIn API keys in gconf and
36 having a gui_callback available."""
38 self._gc = gnome.gconf.client_get_default()
39 self._gui = gui_callback
40 self._autocreate = autocreate
42 # -- Check the environment is going to work...
44 if (self._gc.get_string('/desktop/gnome/url-handlers/http/command') == 'epiphany %s'):
45 raise Exception('Browser in gconf invalid (see NB#136012). Installation error.')
47 api_key = self._gc.get_string('/apps/maemo/hermes/linkedin_api_key')
48 secret_key = self._gc.get_string('/apps/maemo/hermes/linkedin_key_secret')
51 api_key = '1et4G-VtmtqNfY7gF8PHtxMOf0KNWl9ericlTEtdKJeoA4ubk4wEQwf8lSL8AnYE'
52 secret_key = 'uk--OtmWcxER-Yh6Py5p0VeLPNlDJSMaXj1xfHILoFzrK7fM9eepNo5RbwGdkRo_'
54 if api_key is None or secret_key is None:
55 raise Exception('No LinkedIn application keys found. Installation error.')
57 self.api_key = api_key
58 self.secret_key = secret_key
61 token_str = "oauth_token_secret=60f817af-6437-4015-962f-cc3aefee0264&oauth_token=f89c2b7b-1c12-4f83-a469-838e78901716"
62 self.access_token = oauth.OAuthToken.from_string(token_str)
64 self.consumer = oauth.OAuthConsumer(api_key, secret_key)
65 self.sig_method = oauth.OAuthSignatureMethod_HMAC_SHA1()
68 self._friends_by_url = {}
69 self._friends_by_contact = {}
74 # -----------------------------------------------------------------------
79 # -----------------------------------------------------------------------
80 def get_friends(self):
81 """Return a list of friends from this service, or 'None' if manual mapping
85 return self._friends.values()
87 # FIXME: Check the available OAuth session is still valid...
92 xml = self._make_api_request(self.LI_CONN_API_URL)
93 dom = parseString(xml)
97 print "get_friends found", len(self._friends)
98 return self._friends.values()
102 # -----------------------------------------------------------------------
103 def pre_process_contact(self, contact):
104 """Makes sure that if the contact has been mapped to a friend before,
105 remove the friend from "new friends" list."""
107 for url in contact.get_urls():
108 if url in self._friends_by_url:
109 matched_friend = self._friends_by_url[url]
110 print "Contact is known as a friend on this service (by URL) %s - %s" % (url, matched_friend)
111 self._friends_by_contact[contact] = matched_friend
114 # -----------------------------------------------------------------------
115 def process_contact(self, contact, friend):
116 """Updates friend if the contact can be mapped to a friend on LinkedIn,
117 either by previous mapping or by identifiers."""
119 if self._friends_by_contact.has_key(contact):
120 friend.update(self._friends_by_contact[contact])
122 self._match_contact_by_identifiers(contact, friend)
125 # -----------------------------------------------------------------------
126 def finalise(self, updated):
127 if self._autocreate or True:
128 for f in self._friends.values():
129 if f not in self._friends_by_contact.values():
130 # FIXME: create friends as contact
131 print "Could/should create contact here for %s" % f
134 # -----------------------------------------------------------------------
135 def _get_access_token(self, token, verifier):
136 """user provides the verifier, which was displayed in the browser window"""
138 oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, token=token, verifier=verifier, http_url=self.ACCESS_TOKEN_URL)
139 oauth_request.sign_request(self.sig_method, self.consumer, token)
141 connection = httplib.HTTPSConnection(self.LI_SERVER)
142 connection.request(oauth_request.http_method, self.ACCESS_TOKEN_URL, headers=oauth_request.to_header())
143 response = connection.getresponse()
144 return oauth.OAuthToken.from_string(response.read())
147 # -----------------------------------------------------------------------
148 def _get_authorize_url(self, token):
149 """The URL that the user should browse to, in order to authorize the application to acess data"""
151 oauth_request = oauth.OAuthRequest.from_token_and_callback(token=token, http_url=self.AUTHORIZE_URL)
152 return oauth_request.to_url()
155 # -----------------------------------------------------------------------
156 def _get_request_token(self, callback):
157 """Get a request token from LinkedIn"""
159 oauth_consumer_key = self.api_key
160 oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, callback=callback, http_url=self.REQUEST_TOKEN_URL)
161 oauth_request.sign_request(self.sig_method, self.consumer, None)
163 connection = httplib.HTTPSConnection(self.LI_SERVER)
164 connection.request(oauth_request.http_method, self.REQUEST_TOKEN_URL, headers=oauth_request.to_header())
165 response = self.connection.getresponse().read()
167 token = oauth.OAuthToken.from_string(response)
171 # -----------------------------------------------------------------------
172 def _make_api_request(self, url):
173 print "_make_api_request", url
174 oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, token=self.access_token, http_url=url)
175 oauth_request.sign_request(self.sig_method, self.consumer, self.access_token)
176 connection = httplib.HTTPSConnection(self.LI_SERVER)
178 connection.request(oauth_request.http_method, url, headers=oauth_request.to_header())
179 return connection.getresponse().read()
181 raise Exception("Failed to contact LinkedIn at " + url)
184 # -----------------------------------------------------------------------
185 def _do_li_login(self):
186 """Perform authentication against LinkedIn and store the result in gconf
187 for later use. Uses the 'need_auth' and 'block_for_auth' methods on
188 the callback class. The former allows a message to warn the user
189 about what is about to happen to be shown; the second is to wait
190 for the user to confirm they have logged in."""
194 # -----------------------------------------------------------------------
195 def _match_contact_by_identifiers(self, contact, friend):
196 for id in contact.get_identifiers():
197 if id in self._friends:
198 matched_friend = self._friends[id]
199 if matched_friend in self._friends_by_contact.values():
200 print "Avoiding assigning same friend to two contacts: %s " % matched_friend
202 self._friends_by_contact[contact] = matched_friend
203 friend.update(matched_friend)
206 # -----------------------------------------------------------------------
207 def _parse_dom(self, dom):
208 print "parse_dom", dom
209 def get_first_tag(node, tagName):
210 tags = node.getElementsByTagName(tagName)
211 if tags and len(tags) > 0:
214 def extract(node, tagName):
215 tag = get_first_tag(node, tagName)
217 return tag.firstChild.nodeValue
219 def extract_public_url(node):
220 tag = get_first_tag(node, 'site-standard-profile-request')
222 url = extract(tag, 'url')
223 return url.replace("&", "&")
225 # FIXME: look for <error>
226 people = dom.getElementsByTagName('person')
230 fn = extract(p, 'first-name')
231 ln = extract(p, 'last-name')
232 photo_url = extract(p, 'picture-url')
233 id = extract(p, 'id')
234 public_url = extract_public_url(p)
237 friend = Friend(name)
238 friend.add_url(public_url)
239 if photo_url: friend.set_photo_url(photo_url)
241 key = canonical(name)
242 self._friends[key] = friend
243 self._friends_by_url[public_url] = friend