phpmsdnwindows-live

Importing Windows Live Contacts


I've started with importing contacts from live. Now I don't know what MS is thinking, but they seriously do overcomplicate everything they put their hands to.

For my app, it's very important that I get a phone number. So important in fact, that should you not have a phone number, your contact is skipped. With my method I can't see any phone numbers. I assumed that it would be shown if I loop through each contact one by one, but alas, no love.

Here is my method:

$import_id = time();
$client_id = "xxx";
$redirect_uri = 'redirecturi';
$client_secret = "xxx";
$code = $_GET['code'];
$grant_type = "authorization_code";

$post = "client_id=$client_id&redirect_uri=$redirect_uri&client_secret=$client_secret&code=$code&grant_type=$grant_type";
$curl = curl_init();
curl_setopt($curl,CURLOPT_URL,"https://login.live.com/oauth20_token.srf");
curl_setopt($curl,CURLOPT_POST,5);
curl_setopt($curl,CURLOPT_POSTFIELDS,$post);
curl_setopt($curl,CURLOPT_RETURNTRANSFER,true);
curl_setopt($curl,CURLOPT_SSL_VERIFYPEER,false);
$result = curl_exec($curl);
curl_close($curl);
$token = json_decode($result);
$access_token = $token->access_token;
$user_id = $token->user_id;

$url = "https://apis.live.net/v5.0/me/contacts?access_token=$access_token";
$response =  curl_file_get_contents($url);
$response = json_decode($response);
foreach($response->data as $contact) {
    $contact_details = curl_file_get_contents("https://apis.live.net/v5.0/" . $contact->id . "?access_token=$access_token");
    debug($contact_details);
}
die();

However, I'm only getting info back like this (this person I know has a contact number as I can see it when I view him on people.live.com):

{
   "id": "contact.id", 
   "first_name": "Danie", 
   "last_name": "Van den Heever", 
   "name": "Danie Van den Heever", 
   "is_friend": false, 
   "is_favorite": false, 
   "user_id": "userid", 
   "email_hashes": [
      "emailhash"
   ], 
   "updated_time": "2014-09-17T12:11:10+0000"
}

My permission request url (which defines the scopes) looks like this:

https://login.live.com/oauth20_authorize.srf?client_id=clientidkey&scope=wl.basic%20wl.offline_access&response_type=code&redirect_uri=redirecturi

Should I add more scopes to get the contact number? If so, which scopes? Or is this not possible?


Solution

  • The solution is to use an undocumented scope wl.contacts_phone_numbers, there is a risk that it'll become deprecated or just locked down and only Microsoft-approved clients will be able to use it, but in the meantime it works.

    Also you do not need to do an extra request for every contact, the contact object you get from me/contacts already has the phone numbers in a phones object.

    By the way, here's the code I used while testing this, I used a REST client library which avoids copy/pasting the long and repetitive cURL parameters each time and turns the requests into one-liners.

    Code to ask for permission :

    $params = ["client_id" => "...", "scope" => "wl.basic wl.contacts_phone_numbers", "response_type" => "code", "redirect_uri" => "http://sanctuary/contacts_callback.php"];
    
    header("Location: https://login.live.com/oauth20_authorize.srf?".http_build_query($params));
    

    Note the extra wl.contacts_phone_numbers scope in the permission request.

    Code to get access token and retrieve contacts :

    // Composer's autoloader, required to load libraries installed with it
    // in this case it's the REST client
    require "vendor/autoload.php";
    
    // exchange the temporary token for a reusable access token
    $resp = GuzzleHttp\post("https://login.live.com/oauth20_token.srf", ["body" => ["client_id" => "...", "client_secret" => "...", "code" => $_GET["code"], "redirect_uri" => "http://sanctuary/contacts_callback.php", "grant_type" => "authorization_code"]])->json();
    $token = $resp["access_token"];
    
    // REST client object that will send the access token by default
    // avoids writing the absolute URL and the token each time
    $client = new GuzzleHttp\Client(["base_url" => "https://apis.live.net/v5.0/", "defaults" => ["query" => ["access_token" => $token]]]);
    
    // get all the user's contacts
    $contacts = $client->get("me/contacts")->json()["data"];
    
    // iterate over contacts
    foreach ($contacts as $contact) {
        // if that contact has a phone number object
        if (array_key_exists("phones", $contact)) {
            // iterate over each phone number
            foreach ($contact["phones"] as $phone) {
                // if number isn't blank
                if (!empty($phone)) {
                    // do whatever you want with that number
                }
            }
        }
    }
    

    Here's what me/contacts looks like with the extra scope (minus a few line breaks and personal info) :

    Array (
        [data] => Array (
                [0] => Array (
                        [id] => contact...
                        [first_name] => ...
                        [last_name] => ...
                        [name] => ...
                        [is_friend] => 
                        [is_favorite] => 
                        [user_id] => 
                        [email_hashes] => ...
                        [updated_time] => ...
                        [phones] => Array ( // what you asked for
                                [personal] => 
                                [business] => 
                                [mobile] => +337...
                            )
                    )
            )
    )