2

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?

Bird87 ZA
  • 2,313
  • 8
  • 36
  • 69
  • First off, ditch cURL and use a REST client library such as Guzzle, it'll transform all your cURL code (authentication, etc) into an one-liner and make your code more readable. –  Oct 07 '14 at 10:49
  • Well, if I can get a definite answer on this then I'll be happy. Even if it means that I can't import phone numbers. Thanks for the feedback though. – Bird87 ZA Oct 07 '14 at 13:45

2 Answers2

4

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...
                        )
                )
        )
)
0

From reading the documentation, phone numbers are part of the User object.

To fetch their phone numbers, you would;

  • Grab a list of their contacts (which you've done)
  • Iterate through the result set
  • Grab the user id from the contact response. (Key id)
  • Make a request to the User collection (with the wl.phone_numbers scope)
  • See if the phone numbers are null or not
    • If they are NULL, skip the iteration

An example phones object (in the User response);

"phones": {
   "personal": "(555) 555-1212", 
   "business": "(555) 111-1212", 
   "mobile": null
} 

So;

$arrUser = json_decode($strResponse, true);

if( is_null($arrUser['phones']['personal']) 
     AND is_null($arrUser['phones']['business']
      AND is_null($arrUser['phones']['mobile']) ) {
   //No phone numbers
   //Assuming you're in a loop, fetching a user object for each contact - skip the iteration & move onto the next contact.
   continue;
}
ʰᵈˑ
  • 11,279
  • 3
  • 26
  • 49
  • I just tried that and it doesn't return anything else besides the info already present on `me/contacts`. –  Oct 07 '14 at 12:49
  • @AndréDaniel Did you add the scope `wl.phone_numbers`? – ʰᵈˑ Oct 07 '14 at 13:01
  • @AndréDaniel Ah, sorry! I'll make a few experiments after work tonight, and update my answer later. – ʰᵈˑ Oct 07 '14 at 13:02
  • Will also play around with this tonight. Haven't had time as of yet. I added the bounty to this question and immediately broke my website haha. I love programming. – Bird87 ZA Oct 07 '14 at 13:45
  • Tried this as well, however I got back what @AndréDaniel got back. – Bird87 ZA Oct 08 '14 at 06:43