+import re
import httplib
import gnome.gconf
from oauth import oauth
from xml.dom.minidom import parseString
+from org.maemo.hermes.engine.phonenumber import PhoneNumber
from org.maemo.hermes.engine.friend import Friend
class LinkedInApi():
"""LinkedIn API for Hermes.
Copyright (c) Fredrik Wendt <fredrik@wendt.se> 2010.
+ Copyright (c) Andrew Flegg <andrew@bleb.org> 2010.
Released under the Artistic Licence."""
-
- GCONF_API_KEY = '/apps/maemo/hermes/linkedin_key'
- GCONF_API_SECRET = '/apps/maemo/hermes/linkedin_secret'
- GCONF_ACCESS_TOKEN = '/apps/maemo/hermes/linkedin_access_token'
- LI_SERVER = "api.linkedin.com"
LI_API_URL = "https://api.linkedin.com"
- LI_CONN_API_URL = LI_API_URL + "/v1/people/~/connections"
-
- REQUEST_TOKEN_URL = LI_API_URL + "/uas/oauth/requestToken"
- AUTHORIZE_URL = LI_API_URL + "/uas/oauth/authorize"
- ACCESS_TOKEN_URL = LI_API_URL + "/uas/oauth/accessToken"
-
-
+ LI_CONN_API_URL = LI_API_URL + "/v1/people/~/connections:(id,first-name,last-name,picture-url,site-standard-profile-request:(url),date-of-birth,main-address,location:(country:(code)),phone-numbers,member-url-resources)"
+ LI_PROFILE_API_URL = LI_API_URL + "/v1/people/~"
+
+
# -----------------------------------------------------------------------
- def __init__(self, gconf=None):
- """Initialize the LinkedIn service, finding LinkedIn API keys in gconf and
- having a gui_callback available."""
-
- if gconf: self._gc = gconf
- else: self._gc = gnome.gconf.client_get_default()
-
- api_key = self._gc.get_string(LinkedInApi.GCONF_API_KEY)
- secret_key = self._gc.get_string(LinkedInApi.GCONF_API_SECRET)
- self.api_key = api_key
-
- if api_key is None or secret_key is None:
- raise Exception('No LinkedIn application keys found. Installation error.')
-
- self.access_token = self.get_access_token_from_gconf()
-
- self.consumer = oauth.OAuthConsumer(api_key, secret_key)
- self.sig_method = oauth.OAuthSignatureMethod_HMAC_SHA1()
-
- self._verify_browser_command()
-
+ def __init__(self, consumer, api_request):
+ """Instantiate with an OAuth consumer"""
+ self.consumer = consumer
+ self._make_api_request = api_request
+
# -----------------------------------------------------------------------
- def authenticate(self, need_auth, block_for_auth):
- need_auth()
- token = self._get_request_token()
- url = self._get_authorize_url(token)
- verifier = block_for_auth(True)
- self._verify_verifier(token, verifier)
+ def verify_verifier(self, access_token):
+ try:
+ xml = self._make_api_request(self.LI_PROFILE_API_URL)
+ dom = parseString(xml)
+ friends = self._parse_dom(dom)
+ return friends[0].get_name()
+ except Exception, e:
+ import traceback
+ traceback.print_exc()
+ raise Exception("LinkedIn authorization failed, try again")
+
# -----------------------------------------------------------------------
connection.request(oauth_request.http_method, url, headers=headers)
data = connection.getresponse().read()
return data
-
-
- # -----------------------------------------------------------------------
- def _make_api_request(self, url):
- oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, token=self.access_token, http_url=url)
- oauth_request.sign_request(self.sig_method, self.consumer, self.access_token)
- connection = httplib.HTTPSConnection(self.LI_SERVER)
- try:
- connection.request(oauth_request.http_method, url, headers=oauth_request.to_header())
- xml = connection.getresponse().read()
- return xml
- except:
- raise Exception("Failed to contact LinkedIn at " + url)
# -----------------------------------------------------------------------
def _parse_dom(self, dom):
def get_first_tag(node, tagName):
- tags = node.getElementsByTagName(tagName)
- if tags and len(tags) > 0:
- return tags[0]
+ if node:
+ tags = node.getElementsByTagName(tagName)
+ if tags and len(tags) > 0:
+ return tags[0]
def extract(node, tagName):
tag = get_first_tag(node, tagName)
def extract_public_url(node):
tag = get_first_tag(node, 'site-standard-profile-request')
if tag:
- url = extract(tag, 'url')
- return url.replace("&", "&")
+ url = extract(tag, 'url').replace("&", "&")
+ return re.sub('[?&](auth|trk)\w*=[^&]*', '', url)
+
+ def extract_phone_numbers(node):
+ country = extract(get_first_tag(node, 'country'), 'code')
+ tag = get_first_tag(node, 'phone-numbers')
+ numbers = []
+ if tag:
+ for element in tag.childNodes:
+ if element.nodeName != 'phone-number':
+ continue
+
+ phone_type = extract(element, 'phone-type')
+ device = phone_type == 'mobile' and phone_type or None
+ type = phone_type in set(['home', 'work']) and phone_type or None
+
+ number = PhoneNumber(extract(element, 'phone-number'), device = device, type = type, country = country)
+ numbers.append(number)
+ return numbers
+
+ def extract_urls(node):
+ tag = get_first_tag(node, 'member-url-resources')
+ urls = []
+ if tag:
+ for element in tag.getElementsByTagName('url'):
+ urls.append(element.firstChild.nodeValue.replace("&", "&"))
+ return urls
+
+ def extract_birthday(node):
+ tag = get_first_tag(node, 'date-of-birth')
+ bday = None
+ if tag:
+ month = extract(tag, 'month')
+ day = extract(tag, 'day')
+ year = extract(tag, 'year')
+ if month and day:
+ bday = '%s/%s' % (extract(tag, 'month'), extract(tag, 'day'))
+ if year:
+ bday = '%s/%s' % (bday, year)
+
+ return bday
# look for errors
errors = dom.getElementsByTagName('error')
- if (len(errors) > 0):
+ if (errors and len(errors) > 0):
details = ""
try:
details = " (" + extract(errors[0], "message") + ")"
friends = []
people = dom.getElementsByTagName('person')
for p in people:
- try:
- fn = extract(p, 'first-name')
- ln = extract(p, 'last-name')
- photo_url = extract(p, 'picture-url')
- id = extract(p, 'id')
- public_url = extract_public_url(p)
-
- name = fn + " " + ln
- friend = Friend(name)
- friend.add_url(public_url)
- if photo_url:
- friend.set_photo_url(photo_url)
+ fn = extract(p, 'first-name')
+ ln = extract(p, 'last-name')
+ photo_url = extract(p, 'picture-url')
+ id = extract(p, 'id')
+ public_url = extract_public_url(p)
+ bday = extract_birthday(p)
+
+ name = fn + " " + ln
+ friend = Friend(name)
+ friend.add_url(public_url)
+ if photo_url:
+ friend.set_photo_url(photo_url)
- friends.append(friend)
-
- except:
- pass
+ if bday:
+ friend.set_birthday_date(bday)
+
+ for number in extract_phone_numbers(p):
+ friend.add_phone(number)
+
+ for url in extract_urls(p):
+ friend.add_url(url)
+
+ friends.append(friend)
return friends
-
- # -----------------------------------------------------------------------
- def _get_request_token(self):
- """Get a request token from LinkedIn"""
-
- oauth_consumer_key = self.api_key
- oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, callback="oob", http_url=self.REQUEST_TOKEN_URL)
- oauth_request.sign_request(self.sig_method, self.consumer, None)
-
- connection = httplib.HTTPSConnection(self.LI_SERVER)
- connection.request(oauth_request.http_method, self.REQUEST_TOKEN_URL, headers=oauth_request.to_header())
- response = connection.getresponse().read()
-
- try:
- token = oauth.OAuthToken.from_string(response)
- except Exception, e:
- raise Exception("Authorization failure - failed to get request token")
- return token
-
-
- # -----------------------------------------------------------------------
- def _get_authorize_url(self, token):
- """The URL that the user should browse to, in order to authorize the
- application's request to access data"""
-
- oauth_request = oauth.OAuthRequest.from_token_and_callback(token=token, http_url=self.AUTHORIZE_URL)
- return oauth_request.to_url()
-
-
- # -----------------------------------------------------------------------
- def _get_access_token(self, token, verifier):
- """If the verifier (which was displayed in the browser window) is
- valid, then an access token is returned which should be used to
- access data on the service."""
-
- oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, token=token, verifier=verifier, http_url=self.ACCESS_TOKEN_URL)
- oauth_request.sign_request(self.sig_method, self.consumer, token)
-
- connection = httplib.HTTPSConnection(self.LI_SERVER)
- connection.request(oauth_request.http_method, self.ACCESS_TOKEN_URL, headers=oauth_request.to_header())
- response = connection.getresponse()
- token_str = response.read()
- if "ouath_problem" in token_str:
- raise Exception("Authorization failure - failed to get access token")
- self._store_access_token_in_gconf(token_str)
- return oauth.OAuthToken.from_string(token_str)
-
-
- # -----------------------------------------------------------------------
- def _verify_verifier(self, request_token, verifier):
- try:
- self.access_token = self._get_access_token(request_token, verifier)
- except Exception, e:
- import traceback
- traceback.print_exc()
- raise Exception("LinkedIn authorization failed, try again (" + e + ")")
-
-
- # -----------------------------------------------------------------------
- def _verify_browser_command(self):
- # -- Check the environment is going to work...
- # FIXME: duplication
- if (self._gc.get_string('/desktop/gnome/url-handlers/http/command') == 'epiphany %s'):
- raise Exception('Browser in gconf invalid (see NB#136012). Installation error.')
-
-
- # -----------------------------------------------------------------------
- def _store_access_token_in_gconf(self, token_str):
- self._gc.set_string(LinkedInApi.GCONF_ACCESS_TOKEN, token_str)
-
-
- # -----------------------------------------------------------------------
- def get_access_token_from_gconf(self):
- """Returns an oauth.OAuthToken, or None if the gconf value is empty"""
-
- token_str = self._gc.get_string(LinkedInApi.GCONF_ACCESS_TOKEN)
- if not token_str:
- return None
- if "oauth_problem" in token_str:
- self._store_access_token_in_gconf("")
- raise Exception("Authorization failure - access token reported OAuth problem")
- return oauth.OAuthToken.from_string(token_str)
-
-
- # -----------------------------------------------------------------------
- def remove_access_token_from_gconf(self):
- """Remove the oauth.OAuthToken, if any."""
-
- self._gc.unset(LinkedInAPI.GCONF_ACCESS_TOKEN)