Add phone number support to friends, and contacts, and populate from
[hermes] / package / src / org / maemo / hermes / engine / linkedin / api.py
index 5dff05e..6f87b2c 100644 (file)
@@ -1,22 +1,27 @@
+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_api_key'
-    GCONF_API_SECRET = '/apps/maemo/hermes/linkedin_key_secret'
+    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'
+    GCONF_USER = '/apps/maemo/hermes/linkedin_user'
     
     LI_SERVER = "api.linkedin.com"
     LI_API_URL = "https://api.linkedin.com"
-    LI_CONN_API_URL = LI_API_URL + "/v1/people/~/connections"
+    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/~"
 
     REQUEST_TOKEN_URL = LI_API_URL + "/uas/oauth/requestToken"
     AUTHORIZE_URL = LI_API_URL + "/uas/oauth/authorize"
@@ -38,12 +43,10 @@ class LinkedInApi():
         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.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()
 
 
     # -----------------------------------------------------------------------
@@ -95,9 +98,10 @@ class LinkedInApi():
     # -----------------------------------------------------------------------
     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)
@@ -107,8 +111,24 @@ class LinkedInApi():
         def extract_public_url(node):
             tag = get_first_tag(node, 'site-standard-profile-request')
             if tag:
-                url = extract(tag, 'url')
-                return url.replace("&amp;", "&")
+                url = extract(tag, 'url').replace("&amp;", "&")
+                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 = []
+            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
         
         # look for errors
         errors = dom.getElementsByTagName('error')
@@ -135,6 +155,9 @@ class LinkedInApi():
                 friend.add_url(public_url)
                 if photo_url:
                     friend.set_photo_url(photo_url)
+                    
+                for number in extract_phone_numbers(p):
+                    friend.add_phone(number)
                 
                 friends.append(friend)
 
@@ -155,7 +178,13 @@ class LinkedInApi():
         connection.request(oauth_request.http_method, self.REQUEST_TOKEN_URL, headers=oauth_request.to_header())
         response = connection.getresponse().read()
         
-        token = oauth.OAuthToken.from_string(response)
+        try:
+            token = oauth.OAuthToken.from_string(response)
+        except Exception, e:
+            import traceback
+            traceback.print_exc()
+            print response
+            raise Exception("Authorization failure - failed to get request token")
         return token
 
     
@@ -181,8 +210,8 @@ class LinkedInApi():
         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")
+        if 'oauth_problem' in token_str:
+            raise Exception("Authorization failure - failed to get access token (" + token_str + ")")
         self._store_access_token_in_gconf(token_str)
         return oauth.OAuthToken.from_string(token_str)
 
@@ -191,35 +220,37 @@ class LinkedInApi():
     def _verify_verifier(self, request_token, verifier):
         try:
             self.access_token = self._get_access_token(request_token, verifier)
+            xml = self._make_api_request(self.LI_PROFILE_API_URL)
+            dom = parseString(xml)
+            friends = self._parse_dom(dom)
+            self._gc.set_string(LinkedInApi.GCONF_USER, friends[0].get_name())
         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.')
+            raise Exception("LinkedIn authorization failed, try again")
 
 
     # -----------------------------------------------------------------------
     def _store_access_token_in_gconf(self, token_str):
+        if "oauth_problem" in token_str:
+            raise Exception("Authorization failure - access token reported OAuth problem")
+        
         self._gc.set_string(LinkedInApi.GCONF_ACCESS_TOKEN, token_str)
 
         
     # -----------------------------------------------------------------------
-    def _get_access_token_from_gconf(self):
+    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)
+        self._gc.unset(LinkedInApi.GCONF_USER)