├── XboxLiveClient.php ├── XboxLiveCommunication.php ├── auth.php ├── composer.json ├── getGameDVR.php ├── getScreenshots.php ├── online.php ├── sendMessage.php └── status.php /XboxLiveClient.php: -------------------------------------------------------------------------------- 1 | xuid = $xuid; 15 | $instance->authorization_header = $authorization_header; 16 | 17 | $instance->sha1 = sha1(sprintf("%s%s", $instance->xuid, $instance->authorization_header)); 18 | $instance->setCookieJar($instance->sha1); 19 | return $instance; 20 | } 21 | 22 | /** 23 | * 24 | * @param type $username 25 | * @param type $password 26 | * @return \XboxLiveClient 27 | */ 28 | public static function withUsernameAndPassword($username, $password, $authentication_data = null) { 29 | // for the initial connection generate some temp cookie file 30 | 31 | $instance = new self(); 32 | $instance->username = $username; 33 | $instance->password = $password; 34 | $instance->authorize($authentication_data); 35 | 36 | $instance->sha1 = sha1(sprintf("%s%s", $instance->xuid, $instance->authorization_header)); 37 | $instance->setCookieJar($instance->sha1); 38 | return $instance; 39 | } 40 | 41 | /** 42 | * 43 | * @param array array of xuids 44 | * @return string decodeable json string 45 | */ 46 | public function fetchUserDetails($user_list = array()) { 47 | if (count($user_list) == 0) { 48 | $user_list[] = $this->xuid; 49 | } 50 | 51 | return $this->batchFetchUserDetailsWithXuids($user_list); 52 | } 53 | 54 | /** 55 | * Get the xuid for a given gamertag 56 | * @param string $gamertag 57 | * @return string xuid of the given gamertag, null if not found 58 | */ 59 | public function fetchXuidForGamertag($gamertag) { 60 | $url = sprintf('https://profile.xboxlive.com/users/gt(%s)/profile/settings', urlencode($gamertag)); 61 | 62 | $user_data = json_decode($this->fetchData($url)); 63 | 64 | return ($user_data) ? $user_data->profileUsers[0]->id:null; 65 | } 66 | 67 | public function fetchGamertagForXuid($xuid = null) { 68 | if (!$xuid) 69 | $xuid = $this->xuid; 70 | 71 | $user_data = json_decode($this->fetchUserDetails(array($xuid))); 72 | return ($user_data) ? $user_data->profileUsers[0]->settings[0]->value:null; 73 | } 74 | 75 | /** 76 | * 77 | * @param array $params 78 | * @return string decodeable json string 79 | */ 80 | public function fetchUserAchievements(&$params = array(), $xuid) { 81 | if (count($params) == 0) { 82 | $params = array( 83 | 'orderBy' => 'unlockTime', 84 | 'maxItems' => '600', 85 | ); 86 | } 87 | $param_string = $this->buildParameterString($params); 88 | 89 | // summary - https://achievements.xboxlive.com/users/xuid(2535455857670853)/history/titles?skipItems=0&maxItems=25&orderBy=unlockTime 90 | // summary for specific titleids - https://achievements.xboxlive.com/users/xuid(2535455857670853)/history/titles?skipItems=0&maxItems=25&titleId=1579532320,1169551644,1649675719,1537894068,858615632,1979014977,301917535&orderBy=unlockTime 91 | // via activity - https://avty.xboxlive.com/users/xuid(2535455857670853)/activity/People/People/Feed?activityTypes=Achievement&numItems=5&platform=XboxOne&includeSelf=false 92 | $url = sprintf("https://achievements.xboxlive.com/users/xuid(%s)/achievements?%s", ($xuid) ? $xuid:$this->xuid, $param_string); 93 | return $this->fetchData($url); 94 | } 95 | 96 | /** 97 | * Returns users followed by the user defined in the xuid property 98 | * @param array $params 99 | * @return string decodeable json string 100 | */ 101 | public function fetchFollowedUsers(&$params = array(), $xuid = null) { 102 | if (count($params) == 0) { 103 | $params = array( 104 | 'maxItems' => '100', 105 | ); 106 | } 107 | $param_string = $this->buildParameterString($params); 108 | 109 | $url = sprintf("https://social.xboxlive.com/users/xuid(%s)/people?%s", ($xuid) ? $xuid:$this->xuid, $param_string); 110 | return $this->fetchData($url); 111 | } 112 | 113 | /** 114 | * Returns GameDVR clip information for the user defined in the xuid property 115 | * @param array $params 116 | * @return string decodeable json string 117 | */ 118 | public function fetchGameDVRClips(&$params, $xuid = null) { 119 | if (count($params) == 0) { 120 | $params = array( 121 | 'maxItems' => '24', 122 | ); 123 | } 124 | $param_string = $this->buildParameterString($params); 125 | // qualifier? https://gameclipsmetadata.xboxlive.com/public/titles/1512517621/clips?maxItems=50&qualifier=created 126 | $url = sprintf('https://gameclipsmetadata.xboxlive.com/users/xuid(%s)/clips?%s', ($xuid) ? $xuid:$this->xuid, $param_string); 127 | return $this->fetchData($url); 128 | } 129 | 130 | /** 131 | * Returns GameDVR clip information for the user defined in the xuid property 132 | * @param array $params 133 | * @return string decodeable json string 134 | */ 135 | public function fetchGameDVRClip($scid, $clipId, $xuid = null) { 136 | $url = sprintf("https://gameclipsmetadata.xboxlive.com/users/xuid(%s)/scids/%s/clips/%s", ($xuid) ? $xuid:$this->xuid, $scid, $clipId); 137 | return $this->fetchData($url); 138 | } 139 | 140 | public function fetchUserGameDVRClipsForGame(&$params, $titleid, $xuid = null) { 141 | if (count($params) == 0) { 142 | $params = array( 143 | 'maxItems' => '24', 144 | ); 145 | } 146 | $param_string = $this->buildParameterString($params); 147 | // qualifier? https://gameclipsmetadata.xboxlive.com/public/titles/1512517621/clips?maxItems=50&qualifier=created 148 | $url = sprintf('https://gameclipsmetadata.xboxlive.com/users/xuid(%s)/titles/%s/clips?%s', ($xuid) ? $xuid:$this->xuid, $titleid, $param_string); 149 | return $this->fetchData($url); 150 | } 151 | 152 | /** 153 | * 154 | * @param type $users Array of user xuids to get presence info for 155 | * @returns string of json that can be decoded 156 | */ 157 | public function fetchUserPresence($users) { 158 | $url = "https://userpresence.xboxlive.com/users/batch"; 159 | 160 | $json_payload = json_encode(array( 161 | 'level' => 'all', 162 | 'users' => $users, 163 | )); 164 | 165 | $this->setHeader('x-xbl-contract-version', '3'); 166 | 167 | return $this->fetchData($url, $json_payload); 168 | } 169 | 170 | public function fetchUserScreenshots(&$params = array(), $xuid = null) { 171 | if (count($params) == 0) { 172 | $params = array( 173 | 'maxItems' => '24', 174 | ); 175 | } 176 | 177 | $param_string = $this->buildParameterString($params); 178 | 179 | $url = sprintf('https://screenshotsmetadata.xboxlive.com/users/xuid(%s)/screenshots?%s', ($xuid) ? $xuid:$this->xuid, $param_string); 180 | return $this->fetchData($url); 181 | } 182 | 183 | private function batchFetchUserDetailsWithXuids($user_list) { 184 | $url = 'https://profile.xboxlive.com/users/batch/profile/settings'; 185 | 186 | $json_payload = json_encode(array( 187 | 'settings' => array( 188 | "Gamertag", 189 | "RealName", 190 | "Bio", 191 | "Location", 192 | "Gamerscore", 193 | "GameDisplayPicRaw", 194 | "AccountTier", 195 | "XboxOneRep", 196 | "PreferredColor", 197 | ), 198 | 'userIds' => $user_list, 199 | )); 200 | 201 | return $this->fetchData($url, $json_payload); 202 | } 203 | 204 | public function sendMessage($xuids, $message) { 205 | $url = sprintf("https://msg.xboxlive.com/users/xuid(%s)/outbox", $this->xuid); 206 | 207 | foreach($xuids AS $xuid) { 208 | $recipients[]['xuid'] = $xuid; 209 | } 210 | $json_payload = json_encode(array( 211 | 'header' => array( 212 | 'recipients' => $recipients, 213 | ), 214 | 'messageText' => $message, 215 | )); 216 | 217 | $this->setHeader('x-xbl-contract-version', '3'); 218 | $this->sendData($url, $json_payload); 219 | } 220 | 221 | public function fetchActivity() { 222 | // possible contentTypes include 223 | // * Game 224 | // * App 225 | $url = sprintf("https://avty.xboxlive.com/users/xuid(%s)/activity/People/People/Feed?excludeTypes=Played&numItems=50", $this->xuid); 226 | 227 | return $this->fetchData($url); 228 | } 229 | 230 | 231 | public function fetchActivityForUser(&$params = array(), $xuid) { 232 | // possible contentTypes include 233 | // * Game 234 | // * App 235 | if (count($params) == 0) { 236 | $params = array( 237 | 'excludeTypes' => 'Played', 238 | 'numItems' => '5', 239 | ); 240 | } 241 | $param_string = $this->buildParameterString($params); 242 | 243 | $url = sprintf("https://avty.xboxlive.com/users/xuid(%s)/activity/History?%s", $xuid, $param_string); 244 | 245 | return $this->fetchData($url); 246 | } 247 | public function fetchOwnedGamesAndApps() { 248 | $url = sprintf("https://eplists.xboxlive.com/users/xuid(%s)/lists/RECN/MultipleLists?listNames=GamesRecents,AppsRecents&filterDeviceType=XboxOne", $this->xuid); 249 | 250 | return $this->fetchData($url); 251 | } 252 | 253 | public function fetchPlayedGamesAndApps() { 254 | $url = sprintf("https://avty.xboxlive.com/users/xuid(%s)/activity/History?contentTypes=Game&activityTypes=Played&numItems=10&platform=XboxOne", $this->xuid); 255 | 256 | return $this->fetchData($url); 257 | } 258 | 259 | public function postClipView($xuid, $scid, $clipId) { 260 | $url = sprintf("https://gameclipsmetadata.xboxlive.com/users/xuid(%s)/scids/%s/clips/%s/views", $xuid, $scid, $clipId); 261 | 262 | return $this->fetchData($url, true); 263 | } 264 | public function test() { 265 | //$url = sprintf("https://avty.xboxlive.com/users/xuid(%s)/activity/People/People/Summary/Title?contentTypes=Games&activityTypes=Screenshots&numItems=50&platform=XboxOne&includeSelf=false&startDate=2015-03-126+17-03-02", $this->xuid); 266 | //$url = "https://achievements.xboxlive.com/users/xuid(2535455857670853)/history/titles?skipItems=0&maxItems=15&orderBy=unlockTime"; 267 | //$url = "https://achievements.xboxlive.com/users/xuid(2535455857670853)/history/titles?skipItems=0&maxItems=25&titleId=247546985&orderBy=unlockTime"; 268 | //$url = sprintf("https://screenshotsmetadata.xboxlive.com/users/xuid(%s)/scids/37770100-f9ae-4b80-9dad-7c1d0ec14469/screenshots/b6be00ec-a7d1-4ed1-a880-954db7ad97ea/view", $this->xuid); 269 | $url = "https://gameclipsmetadata.xboxlive.com/users/xuid(2533274812719746)/scids/1b180100-2e72-4297-a9e6-b79d5a9771a4/clips/1e9b3dae-5332-4dbf-80f2-bd4a6bd7b9af/views"; 270 | $url = "https://gameclipsmetadata.xboxlive.com/titles/247546985/clips?maxItems=4"; 271 | 272 | if (count($params) == 0) { 273 | $params = array( 274 | 'maxItems' => '24', 275 | ); 276 | } 277 | $param_string = $this->buildParameterString($params); 278 | 279 | $url = sprintf('https://gameclipsmetadata.xboxlive.com/users/xuids/2533274976649935,2535468563302026/titles/247546985/clips?%s', $param_string); 280 | $url = 'https://avty.xboxlive.com/users/xuid(2535455857670853)/activity/People/People/Summary/User?contentTypes=Game&activityTypes=Played&platform=XboxOne&startDate=2015-04-24%2000%3A00%3A00&titleIds=1512517621'; 281 | 282 | return $this->fetchData($url); 283 | } 284 | 285 | static public function convertTime($datetime, $timezone = 'America/Chicago') { 286 | $parsed = date_parse($datetime); 287 | 288 | $datetime = new DateTime(); 289 | $utczone = new DateTimeZone('UTC'); 290 | 291 | $datetime->setTimezone($utczone); 292 | $datetime->setDate($parsed['year'], $parsed['month'], $parsed['day']); 293 | $datetime->setTime($parsed['hour'], $parsed['minute'], $parsed['second']); 294 | $newzone = new DateTimeZone($timezone); 295 | $datetime->setTimezone($newzone); 296 | 297 | return $datetime->format('U'); 298 | } 299 | 300 | public function fetchRecentPlayers() { 301 | $url = sprintf("https://social.xboxlive.com/users/xuid(%s)/recentplayers", $this->xuid); 302 | 303 | return $this->fetchData($url); 304 | } 305 | 306 | public function fetchStoreInformationForGame($bingId) { 307 | $url = sprintf("https://eds.xboxlive.com/media/en-US/details?ids=%s&idType=Canonical&mediaItemType=DGame&mediaGroup=GameType&desired=TitleId.ReleaseDate.Description.Images.DeveloperName.PublisherName.ZuneId.AllTimeAverageRating.AllTimeRatingCount.RatingId.UserRatingCount.RatingDescriptors.SlideShows.Genres.Capabilities.HasCompanion.ParentalRatings.IsBundle.BundlePrimaryItemId.IsPartOfAnyBundle&targetDevices=XboxOne&domain=Modern", $bingId); 308 | $this->setHeader('x-xbl-contract-version', '3.2'); 309 | $this->setHeader('x-xbl-client-type', 'Companion'); 310 | $this->setHeader('Accept-Language', 'en-us'); 311 | $this->setHeader('x-xbl-device-type', 'iPhone'); 312 | //$this->setHeader('Accept-Encoding', 'gzip, deflate'); 313 | //$this->setHeader('User-Agent', 'SmartGlass/2.103.0302 CFNetwork/711.3.18 Darwin/14.0.0'); 314 | $this->setHeader('x-xbl-client-version', '1.0'); 315 | $this->setHeader('Content-Type', ''); 316 | //$this->setHeader('Connection','keep-alive'); 317 | //$this->setHeader('Proxy-Connection', 'keep-alive'); 318 | return $this->fetchData($url); 319 | } 320 | 321 | public function searchStoreForGameTitle($game_title) { 322 | $url = sprintf("https://eds.xboxlive.com/media/en-US/crossMediaGroupSearch?maxItems=10&q=%s&DesiredMediaItemTypes=DGame.DGameDemo.DApp.DActivity.DConsumable.DDurable.DNativeApp.MusicArtistType.Album.Track.MovieType.TvType&desired=Images.ReleaseDate.Providers.HasCompanion.ParentItems.IsBundle.BundlePrimaryItemId.IsPartOfAnyBundle.AllTimeAverageRating.AllTimeRatingCount&targetDevices=XboxOne&domain=Modern", urlencode($game_title)); 323 | 324 | $this->setHeader('x-xbl-contract-version', '3.2'); 325 | $this->setHeader('x-xbl-client-type', 'Companion'); 326 | $this->setHeader('Accept-Language', 'en-us'); 327 | $this->setHeader('x-xbl-device-type', 'iPhone'); 328 | //$this->setHeader('Accept-Encoding', 'gzip, deflate'); 329 | //$this->setHeader('User-Agent', 'SmartGlass/2.103.0302 CFNetwork/711.3.18 Darwin/14.0.0'); 330 | $this->setHeader('x-xbl-client-version', '0.0'); 331 | //$this->setHeader('Connection','keep-alive'); 332 | //$this->setHeader('Proxy-Connection', 'keep-alive'); 333 | return $this->fetchData($url); 334 | } 335 | 336 | public function fetchUserGames($xuid = null) { 337 | if (!$xuid) 338 | $xuid = $this->xuid; 339 | 340 | $url = sprintf("https://eplists.xboxlive.com/users/xuid(%s)/lists/RECN/MultipleLists?listNames=GamesRecents,AppsRecents&filterDeviceType=XboxOne", $xuid); 341 | 342 | return $this->fetchData($url); 343 | } 344 | 345 | /* 346 | * @param string of period separated item ids from fetchUserGames() 347 | */ 348 | public function fetchGameDetails($itemIds) { 349 | $url = sprintf("https://eds.xboxlive.com/media/en-US/details?ids=%s&idType=Canonical&fields=all&desiredMediaItemTypes=DGame.DApp.DApp.DApp.DApp.DApp.DApp.DApp.DGame.DApp&targetDevices=XboxOne&domain=Modern", $itemIds); 350 | 351 | return $this->fetchData($url); 352 | } 353 | 354 | public function fetchTrendingGameDVR($params = array()) { 355 | if (count($params) == 0) { 356 | $params = array( 357 | 'qualifier' => 'created', 358 | 'maxItems' => '5', 359 | ); 360 | } 361 | 362 | $param_string = $this->buildParameterString($params); 363 | $url = sprintf("https://gameclipsmetadata.xboxlive.com/public/trending/clips?%s", $param_string); 364 | 365 | return $this->fetchData($url); 366 | } 367 | 368 | public function fetchTrendingScreenshots($params = array()) { 369 | if (count($params) == 0) { 370 | $params = array( 371 | 'qualifier' => 'created', 372 | 'maxItems' => '5', 373 | ); 374 | } 375 | 376 | $param_string = $this->buildParameterString($params); 377 | $url = sprintf("https://screenshotsmetadata.xboxlive.com/public/trending/screenshot?%s", $param_string); 378 | 379 | return $this->fetchData($url); 380 | } 381 | 382 | public function fetchRecentGameDVRForTitle(&$params, $titleId) { 383 | if (count($params) == 0) { 384 | $params = array( 385 | 'qualifier' => 'created', 386 | 'maxItems' => '5', 387 | ); 388 | } 389 | 390 | $param_string = $this->buildParameterString($params); 391 | $url = sprintf("https://gameclipsmetadata.xboxlive.com/public/titles/%s/clips?%s", $titleId, $param_string); 392 | 393 | return $this->fetchData($url); 394 | } 395 | 396 | public function fetchRecentScreenshotsForTitle(&$params, $titleId) { 397 | if (count($params) == 0) { 398 | $params = array( 399 | 'qualifier' => 'created', 400 | 'maxItems' => '5', 401 | ); 402 | } 403 | 404 | $param_string = $this->buildParameterString($params); 405 | $url = sprintf("https://screenshotsmetadata.xboxlive.com/public/titles/%s/screenshots/?%s", $titleId, $param_string); 406 | 407 | return $this->fetchData($url); 408 | } 409 | 410 | public function fetchRecentActivity(&$params = array(), $xuid) { 411 | // possible contentTypes include 412 | // * Game 413 | // * App 414 | if (count($params) == 0) { 415 | $params = array( 416 | 'excludeTypes' => 'Played', 417 | 'numItems' => '5', 418 | ); 419 | } 420 | $param_string = $this->buildParameterString($params); 421 | 422 | $url = sprintf("https://avty.xboxlive.com/public/activity/People/People/Feed?%s", $param_string); 423 | 424 | return $this->fetchData($url); 425 | } 426 | 427 | 428 | /* 429 | * Convert settings array to associative array 430 | * 431 | * $user_detail_lookup = array(); 432 | foreach($raw_user_details->profileUsers AS $current_user) { 433 | foreach($current_user->settings AS $value) { 434 | $user_detail_lookup[$current_user->id][$value->id] = $value->value; 435 | } 436 | } 437 | */ 438 | } -------------------------------------------------------------------------------- /XboxLiveCommunication.php: -------------------------------------------------------------------------------- 1 | clearHeaders(); 31 | $authentication_data = array(); 32 | 33 | $this->logger = new Logger(); 34 | $this->logger->level = Logger::error; 35 | 36 | $this->cookiejar = new CookieJar(); 37 | 38 | // setCookieJar will create the curl handle 39 | $this->setCookieJar(); 40 | 41 | } 42 | 43 | /** 44 | * 45 | * @param type $xuid 46 | * @param type $authorization_header 47 | * @return \XboxLiveCommunication 48 | */ 49 | public static function withCachedCredentials($xuid, $authorization_header) { 50 | $instance = new self(); 51 | $instance->xuid = $xuid; 52 | $instance->authorization_header = $authorization_header; 53 | 54 | $instance->sha1 = sha1(sprintf("%s%s", $instance->xuid, $instance->authorization_header)); 55 | $instance->setCookieJar($instance->sha1); 56 | return $instance; 57 | } 58 | 59 | /** 60 | * 61 | * @param type $username 62 | * @param type $password 63 | * @return \XboxLiveCommunication 64 | */ 65 | public static function withUsernameAndPassword($username, $password, $authentication_data = null) { 66 | // for the initial connection generate some temp cookie file 67 | 68 | $instance = new self(); 69 | $instance->username = $username; 70 | $instance->password = $password; 71 | $instance->authorize($authentication_data); 72 | 73 | $instance->sha1 = sha1(sprintf("%s%s", $instance->xuid, $instance->authorization_header)); 74 | $instance->setCookieJar($instance->sha1); 75 | return $instance; 76 | } 77 | 78 | protected function setCookieJar($xuid = null) { 79 | // writes out the existing cookiejar file 80 | // if there is an existing curl handle 81 | if ($this->ch) { 82 | curl_close($this->ch); 83 | $this->ch = null; 84 | } 85 | 86 | $this->cookiejar->setCookieJar($xuid); 87 | 88 | $this->logger->log(sprintf("Setting cookiejar to %s", $this->cookiejar->cookiejar), Logger::debug); 89 | 90 | 91 | } 92 | 93 | 94 | 95 | public function request($url, $post_data = null, $use_header = 0) { 96 | if ($this->ch) 97 | $this->logger->log("Curl handle exists and it's getting overwritten", Logger::debug); 98 | 99 | 100 | $this->ch = curl_init(); 101 | 102 | if (!$this->no_cookie_jar) { 103 | curl_setopt($this->ch, CURLOPT_COOKIEFILE, $this->cookiejar->cookiejar); 104 | curl_setopt($this->ch, CURLOPT_COOKIEJAR, $this->cookiejar->cookiejar); 105 | } 106 | curl_setopt($this->ch, CURLOPT_URL, $url); 107 | curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true); 108 | curl_setopt($this->ch, CURLOPT_TIMEOUT, 400); 109 | $headers = array(); 110 | if (count($this->headers) > 0) { 111 | foreach($this->headers AS $header_name => $header_value) { 112 | $this->logger->log(sprintf("Adding header: %s", $header_name), Logger::debug_with_headers); 113 | $headers[] = sprintf("%s: %s", $header_name, $header_value); 114 | } 115 | curl_setopt($this->ch, CURLOPT_HTTPHEADER, $headers); 116 | } 117 | else { 118 | $this->logger->log("No headers to set", Logger::debug_with_headers); 119 | } 120 | 121 | if ($post_data) { 122 | curl_setopt($this->ch, CURLOPT_POSTFIELDS, $post_data); 123 | 124 | if (is_array($post_data)) { 125 | curl_setopt($this->ch, CURLOPT_POST, count($post_data)); 126 | } 127 | } 128 | 129 | curl_setopt($this->ch, CURLOPT_VERBOSE, 0); 130 | curl_setopt($this->ch, CURLOPT_HEADER, $use_header); 131 | 132 | $this->logger->log(sprintf("Accessing %s", $url), Logger::debug); 133 | if ($this->logger->level == Logger::debug_with_headers) { 134 | foreach($headers AS $header) { 135 | $this->logger->log(sprintf(" %s", $header), Logger::debug_with_headers); 136 | } 137 | $this->logger->log(sprintf(" %s", $post_data)); 138 | $this->logger->log(sprintf(" \n\n"), Logger::debug_with_headers); 139 | } 140 | $time = microtime(); 141 | $time = explode(' ', $time); 142 | $time = $time[1] + $time[0]; 143 | $start = $time;$time = microtime(); 144 | $time = explode(' ', $time); 145 | $time = $time[1] + $time[0]; 146 | $start = $time; 147 | 148 | if ($this->batch) { 149 | // add to a batch list 150 | $request = new stdClass(); 151 | $request->xlcObject = clone $this; 152 | $request->url = $url; 153 | $request->post_data = $post_data; 154 | $this->batch_items[] = clone $request; 155 | unset($request); 156 | $this->url = $url; 157 | return; 158 | } 159 | else { 160 | // perform the request now 161 | $results = curl_exec($this->ch); 162 | } 163 | 164 | 165 | $time = microtime(); 166 | $time = explode(' ', $time); 167 | $time = $time[1] + $time[0]; 168 | $finish = $time; 169 | $total_time = round(($finish - $start), 3); 170 | 171 | $request = array( 172 | 'request_string' => $url, 173 | 'time_taken' => $total_time, 174 | ); 175 | XboxLiveCommunication::$requests[] = $request; 176 | syslog(LOG_ERR, sprintf("Accessing: '%s' took %ss", $url, $total_time)); 177 | //dpm(sprintf("Accessing: '%s' took %ss", $url, $total_time)); 178 | 179 | if ($this->logger->level == Logger::debug_with_headers) { 180 | curl_setopt($this->ch, CURLOPT_HEADER, 1); 181 | printf("%s\n\n", curl_exec($this->ch)); 182 | } 183 | $this->clearHeaders(); 184 | 185 | if (curl_errno($this->ch) > 0) { 186 | throw new Exception("Unknown error while communicating with Xbox Live. Xbox Live is taking too long to repond or is currently down"); 187 | } 188 | //curl_close($this->ch); 189 | return $results; 190 | } 191 | 192 | public function performBatch() { 193 | $client = new GuzzleHttp\Client(array(), array( 194 | 'curl.options' => array( 195 | 'CURLOPT_COOKIEJAR' => $this->cookiejar->cookiejar, 196 | 'CURLOPT_COOKIEFILE' => $this->cookiejar->cookiejar, 197 | ), 198 | 199 | )); 200 | $time = microtime(); 201 | $time = explode(' ', $time); 202 | $time = $time[1] + $time[0]; 203 | $start = $time;$time = microtime(); 204 | $time = explode(' ', $time); 205 | $time = $time[1] + $time[0]; 206 | $start = $time; 207 | // does the batch operations 208 | $responses = array(); 209 | $requests = array(); 210 | syslog(LOG_ERR, sprintf("Accessing: starting batch")); 211 | foreach($this->batch_items AS $xlc) { 212 | $headers = $xlc->xlcObject->headers; 213 | $url = $xlc->url; 214 | $post_data = $xlc->post_data; 215 | 216 | 217 | 218 | if ($post_data) { 219 | syslog(LOG_ERR, sprintf("Accessing: adding post '%s' to batch", $url)); 220 | 221 | $req = $client->createRequest('POST', $url, 222 | array( 223 | 'future' => false, 224 | 'debug' => false, 225 | 'body' => $post_data, 226 | ) 227 | ); 228 | 229 | 230 | } 231 | else { 232 | syslog(LOG_ERR, sprintf("Accessing: adding get '%s' to batch", $url)); 233 | $req = $client->createRequest('GET', $url, 234 | array( 235 | 'future' => false, 236 | 'debug' => false, 237 | )); 238 | 239 | } 240 | 241 | foreach($headers AS $header => $value) { 242 | $req->setHeader($header, $value); 243 | } 244 | 245 | 246 | $requests[] = $req; 247 | 248 | } 249 | $time = microtime(); 250 | $time = explode(' ', $time); 251 | $time = $time[1] + $time[0]; 252 | $start = $time;$time = microtime(); 253 | $time = explode(' ', $time); 254 | $time = $time[1] + $time[0]; 255 | $start = $time; 256 | 257 | $results = \GuzzleHttp\Pool::batch($client, $requests); 258 | 259 | 260 | $time = microtime(); 261 | $time = explode(' ', $time); 262 | $time = $time[1] + $time[0]; 263 | $finish = $time; 264 | $total_time = round(($finish - $start), 3); 265 | syslog(LOG_ERR, sprintf("Accessing (batch of %s) took %ss", count($requests), $total_time)); 266 | //dpm(sprintf("Accessing (batch) took %ss", $total_time)); 267 | foreach ($results->getSuccessful() as $response) { 268 | 269 | $responses[] = array( 270 | 'body' => $response->getBody(), 271 | 'request' => $response->getEffectiveUrl(), 272 | ); 273 | } 274 | 275 | foreach($results->getFailures() AS $failures) { 276 | } 277 | unset($this->batch_items); 278 | return $responses; 279 | } 280 | 281 | public function setHeader($header_name, $header_value) { 282 | $this->headers[$header_name] = $header_value; 283 | } 284 | 285 | private function clearHeaders() { 286 | $this->headers = array(); 287 | } 288 | 289 | private function fetchPreAuthData() { 290 | // remove the old cookie file 291 | unlink($this->cookiejar->cookiejar); 292 | $post_vals = http_build_query(array( 293 | // don't change this client_id unless you know what you're doing 294 | 'client_id' => '0000000048093EE3', 295 | 'redirect_uri' => 'https://login.live.com/oauth20_desktop.srf', 296 | 'response_type' => 'token', 297 | 'display' => 'touch', 298 | 'scope' => 'service::user.auth.xboxlive.com::MBI_SSL', 299 | 'locale' => 'en', 300 | )); 301 | $post_vals = urldecode($post_vals); 302 | 303 | $preAuthData = $this->request(sprintf("https://login.live.com/oauth20_authorize.srf?%s", $post_vals), null, 1); 304 | 305 | $this->logger->log($preAuthData, Logger::debug_with_headers); 306 | $match = array(); 307 | preg_match("/urlPost:'([A-Za-z0-9:\?_\-\.&\/=]+)/", $preAuthData, $match); 308 | 309 | if (!array_key_exists(1, $match)) { 310 | $this->logger->log("Unable to fetch pre auth data because urlPost wasn't found", Logger::debug); 311 | throw new Exception("Unable to fetch pre auth data, urlPost not found"); 312 | } 313 | $urlPost = $match[1]; 314 | $ppft_re = preg_match("/sFTTag:'.*value=\"(.*)\"\/>'/", $preAuthData, $match); 315 | 316 | if (!array_key_exists(1, $match)) { 317 | $this->logger->log("Unable to fetch pre auth data because sFFTag wasn't found", Logger::debug); 318 | throw new Exception("Unable to fetch pre auth data, sFFTag not found"); 319 | } 320 | $ppft_re = $match[1]; 321 | 322 | $this->authentication_data['urlPost'] = $urlPost; 323 | $this->authentication_data['ppft_re'] = $ppft_re; 324 | } 325 | 326 | private function fetchInitialAccessToken() { 327 | $this->fetchPreAuthData(); 328 | 329 | $post_vals = http_build_query(array( 330 | 'login' => $this->username, 331 | 'passwd' => $this->password, 332 | 'PPFT' => $this->authentication_data['ppft_re'], 333 | 'PPSX' => 'Passpor', 334 | 'SI' => "Sign In", 335 | 'type' => '11', 336 | 'NewUser' => '1', 337 | 'LoginOptions' => '1', 338 | 'i3' => '36728', 339 | 'm1' => '768', 340 | 'm2' => '1184', 341 | 'm3' => '0', 342 | 'i12' => '1', 343 | 'i17' => '0', 344 | 'i18' => '__Login_Host|1', 345 | )); 346 | $access_token_results = $this->request($this->authentication_data['urlPost'], $post_vals, 1); 347 | $this->logger->log($access_token_results, Logger::debug_with_headers); 348 | preg_match('/Location: (.*)/', $access_token_results, $match); 349 | if (!array_key_exists(1, $match)) { 350 | $this->logger->log("Unable to fetch initial token because Location wasn't found", Logger::debug); 351 | throw new Exception("Unable to fetch initial token, Location not found"); 352 | } 353 | 354 | $location_parsed = parse_url($match[1]); 355 | preg_match('/access_token=(.+?)&/', $location_parsed['fragment'], $match); 356 | 357 | if (!array_key_exists(1, $match)) { 358 | $this->logger->log("Unable to fetch initial token because access_token wasn't found", Logger::debug); 359 | throw new Exception("Unable to fetch initial token, access token not found"); 360 | } 361 | 362 | $access_token = $match[1]; 363 | 364 | $this->authentication_data['access_token'] = $access_token; 365 | } 366 | 367 | public function authenticate($access_token = null) { 368 | 369 | if (!$access_token) { 370 | $this->fetchInitialAccessToken(); 371 | } 372 | else { 373 | $this->authentication_data['access_token'] = $access_token; 374 | } 375 | 376 | $url = 'https://user.auth.xboxlive.com/user/authenticate'; 377 | 378 | $payload = array( 379 | 'RelyingParty' => 'http://auth.xboxlive.com', 380 | 'TokenType' => 'JWT', 381 | 'Properties' => array( 382 | 'AuthMethod' => 'RPS', 383 | 'SiteName' => 'user.auth.xboxlive.com', 384 | 'RpsTicket' => $this->authentication_data['access_token'], 385 | ) 386 | ); 387 | $json_payload = json_encode($payload); 388 | 389 | $this->setHeader('Content-Type', 'application/json'); 390 | $this->setHeader('Content-Length', strlen($json_payload)); 391 | 392 | if ($this->logger->level == Logger::debug_with_headers) { 393 | $authentication_results = $this->request($url, $json_payload, 1); 394 | 395 | $this->logger->log($authentication_results, Logger::debug); 396 | $this->setHeader('Content-Type', 'application/json'); 397 | $this->setHeader('Content-Length', strlen($json_payload)); 398 | } 399 | 400 | 401 | $authentication_results = $this->request($url, $json_payload); 402 | 403 | if (empty($authentication_results)) { 404 | $this->logger->log("Unable to authenticate, no data returned", Logger::debug); 405 | throw new Exception("Unable to authenticate, no data returned"); 406 | } 407 | 408 | $user_data = json_decode($authentication_results); 409 | 410 | $this->authentication_data['token'] = $user_data->Token; 411 | $this->authentication_token = $user_data->Token; 412 | $this->authentication_expires = $user_data->NotAfter; 413 | $this->authentication_data['uhs'] = $user_data->DisplayClaims->xui[0]->uhs; 414 | } 415 | 416 | public function authorize($authentication_data = null) { 417 | if ($authentication_data) { 418 | $this->authentication_data = $authentication_data; 419 | } 420 | else { 421 | $this->authenticate(); 422 | } 423 | 424 | $url = 'https://xsts.auth.xboxlive.com/xsts/authorize'; 425 | 426 | $payload = array( 427 | 'RelyingParty' => 'http://xboxlive.com', 428 | 'TokenType' => 'JWT', 429 | 'Properties' => array( 430 | 'UserTokens' => array($this->authentication_data['token']), 431 | 'SandboxId' => 'RETAIL', 432 | ) 433 | ); 434 | $json_payload = json_encode($payload); 435 | 436 | $this->setHeader('Content-Type', 'application/json'); 437 | $this->setHeader('Content-Length', strlen($json_payload)); 438 | 439 | if ($this->logger->level == Logger::debug_with_headers) { 440 | $authentication_results = $this->request($url, $json_payload, 1); 441 | $this->logger->log($authentication_results, Logger::debug); 442 | $this->setHeader('Content-Type', 'application/json'); 443 | $this->setHeader('Content-Length', strlen($json_payload)); 444 | } 445 | 446 | $authorization_data = json_decode($this->request($url, $json_payload)); 447 | 448 | if (empty($authorization_data)) { 449 | $this->logger->log("Unable to authorize, no data returned", Logger::debug); 450 | throw new Exception("Unable to authorize, no data returned"); 451 | } 452 | 453 | $this->xuid = $authorization_data->DisplayClaims->xui[0]->xid; 454 | $this->authorization_header = sprintf('XBL3.0 x=%s;%s', $this->authentication_data['uhs'], $authorization_data->Token); 455 | $this->authorization_expires = $authorization_data->NotAfter; 456 | } 457 | 458 | public function fetchData($url, $json = null) { 459 | if (empty($this->authorization_header)) { 460 | throw new Exception("Not authorized"); 461 | } 462 | $this->setHeader('Authorization', $this->authorization_header); 463 | 464 | if ($json) 465 | $this->setHeader('Content-Type', 'application/json'); 466 | 467 | $this->setHeader('Accept', 'application/json'); 468 | 469 | if (!array_key_exists('x-xbl-contract-version', $this->headers)) 470 | $this->setHeader('x-xbl-contract-version', '2'); 471 | 472 | if (!array_key_exists('User-Agent', $this->headers)) 473 | $this->setHeader('User-Agent', 'XboxRecord.Us Like SmartGlass/2.105.0415 CFNetwork/711.3.18 Darwin/14.0.0'); 474 | 475 | if ($json) { 476 | $this->setHeader('Content-Length', strlen($json)); 477 | } 478 | 479 | return $this->request($url, $json); 480 | } 481 | 482 | public function buildParameterString($params = array()) { 483 | 484 | $output = ""; 485 | foreach($params AS $key => $value) { 486 | $output .= sprintf("%s=%s&", $key, $value); 487 | } 488 | 489 | return substr($output, 0, strlen($output) - 1); 490 | } 491 | 492 | public function sendData($url, $json) { 493 | print_r($this->fetchData($url, $json)); 494 | } 495 | 496 | public function requestLog() { 497 | return $this->requests; 498 | } 499 | 500 | public function batch($is_batch = 0) { 501 | $this->batch = $is_batch; 502 | } 503 | } 504 | 505 | 506 | 507 | class CookieJar { 508 | public $cookiejar; 509 | 510 | public function setCookieJar($id = null) { 511 | if (!$id) { 512 | $this->cookiejar = CookieJar::generateCookieJarName(); 513 | } 514 | else { 515 | $cookiejar = CookieJar::generateCookieJarName($id); 516 | 517 | // if cookiejar is already defined and the new file doesn't exist 518 | // then we're tranisitioning from authorization to doing work 519 | if ($this->cookiejar && !file_exists($cookiejar)) 520 | rename($this->cookiejar, $cookiejar); 521 | else 522 | unlink($this->cookiejar); 523 | 524 | $this->cookiejar = $cookiejar; 525 | } 526 | 527 | } 528 | 529 | private static function generateCookieJarName($id = null) { 530 | if ($id) 531 | return sprintf("%s/xbox_live_cookies_%s", realpath('/tmp'), $id); 532 | else 533 | return tempnam(realpath('/tmp'), 'xbox_live_cookies_'); 534 | } 535 | } 536 | 537 | class Logger { 538 | var $level; 539 | 540 | const none = 0; 541 | const error = 1; 542 | const debug = 2; 543 | const debug_with_headers = 3; 544 | 545 | 546 | public function log($message, $level) { 547 | if (isset($this->level) && $level <= $this->level) { 548 | printf("%s\n", $message); 549 | syslog(LOG_ERR, "DERP" . $message); 550 | } 551 | } 552 | } 553 | 554 | -------------------------------------------------------------------------------- /auth.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php 2 | getMessage()); 28 | exit; 29 | } 30 | file_put_contents(XUIDCACHE, $live->xuid); 31 | file_put_contents(AUTHCACHE, $live->authorization_header); 32 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dustinrue/php-xboxliveclient", 3 | "type": "library", 4 | "description": "The PHP-XboxLiveClient", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Dustin Rue" 9 | } 10 | ], 11 | "require": { 12 | "php": ">=5.2.1", 13 | "ext-curl": "*", 14 | "ext-openssl": "*", 15 | "guzzlehttp/guzzle": "*" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /getGameDVR.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php 2 | getMessage()); 18 | exit; 19 | } 20 | } 21 | 22 | 23 | $screenshot_data = array(); 24 | $continue = true; 25 | $params = array('maxItems' => '24'); 26 | do { 27 | $screenshots = json_decode($live->fetchGameDVRClips($params)); 28 | print_r($screenshots); 29 | exit; 30 | foreach($screenshots->screenshots AS $screenshot) { 31 | $screenshot_data[] = $screenshot; 32 | } 33 | 34 | if (!empty($screenshots->pagingInfo->continuationToken)) { 35 | $params['continuationToken'] = $screenshots->pagingInfo->continuationToken; 36 | } 37 | else { 38 | $continue = false; 39 | } 40 | } while ($continue); 41 | 42 | printf(""); 43 | foreach($screenshot_data AS $screenshot) { 44 | printf("\n", $screenshot->screenshotUris[0]->uri); 45 | } 46 | printf(""); -------------------------------------------------------------------------------- /getScreenshots.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php 2 | getMessage()); 18 | exit; 19 | } 20 | } 21 | 22 | 23 | $screenshot_data = array(); 24 | $continue = true; 25 | $params = array('maxItems' => '24'); 26 | do { 27 | $screenshots = json_decode($live->fetchUserScreenShots($params)); 28 | foreach($screenshots->screenshots AS $screenshot) { 29 | $screenshot_data[] = $screenshot; 30 | } 31 | 32 | if (!empty($screenshots->pagingInfo->continuationToken)) { 33 | $params['continuationToken'] = $screenshots->pagingInfo->continuationToken; 34 | } 35 | else { 36 | $continue = false; 37 | } 38 | } while ($continue); 39 | 40 | 41 | print_r($screenshot_data); 42 | exit; 43 | printf(""); 44 | foreach($screenshot_data AS $screenshot) { 45 | $uri_meta = parse_url($screenshot->screenshotUris[0]->uri); 46 | printf("\n", "http://i.fccinteractive.com/unsafe/1280x768",$uri_meta['scheme'], $uri_meta['host'], $uri_meta['path']); 47 | } 48 | printf(""); -------------------------------------------------------------------------------- /online.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php 2 | getMessage()); 21 | exit; 22 | } 23 | } 24 | 25 | 26 | //$url = sprintf('https://gameclipsmetadata.xboxlive.com/users/xuid(%s)/clips?continuationToken=abcde_vwxyzMAAAAA2&maxItems=24', '2615706747868666'); 27 | 28 | //$url = sprintf("https://social.xboxlive.com/users/xuid(%s)/people?maxItems=1000", $live->xuid); 29 | 30 | $users_followed = json_decode($live->fetchFollowedUsers()); 31 | 32 | $user_list = array(); 33 | 34 | foreach($users_followed->people AS $user) { 35 | $user_list[] = $user->xuid; 36 | } 37 | 38 | $raw_user_presense = json_decode($live->fetchUserPresence($user_list)); 39 | $raw_user_details = json_decode($live->fetchUserDetails($user_list)); 40 | 41 | 42 | $user_detail_lookup = array(); 43 | foreach($raw_user_details->profileUsers AS $current_user) { 44 | foreach($current_user->settings AS $value) { 45 | $user_detail_lookup[$current_user->id][$value->id] = $value->value; 46 | } 47 | } 48 | 49 | 50 | foreach($raw_user_presense AS $current_user) { 51 | $user_detail_lookup[$current_user->xuid]['state'] = $current_user->state; 52 | 53 | 54 | if ($current_user->state == "Online") { 55 | $user_detail_lookup[$current_user->xuid]['type'] = $current_user->devices[0]->type; 56 | foreach($current_user->devices[0]->titles AS $current_title) { 57 | 58 | if ($current_title->placement != "Background") { 59 | $current_state = $current_title; 60 | } 61 | } 62 | 63 | $user_detail_lookup[$current_user->xuid]['title'] = $current_state->name; 64 | if (array_key_exists('activity', $current_state)) { 65 | $user_detail_lookup[$current_user->xuid]['richPresence'] = $current_state->activity->richPresence; 66 | } 67 | } 68 | 69 | } 70 | 71 | foreach($user_detail_lookup AS $user) { 72 | 73 | if ($user['state'] == "Online") { 74 | printf("%s: %s", $user['Gamertag'], $user['title']); 75 | if (array_key_exists('richPresence', $user)) { 76 | printf(" - %s (%s)\n", $user['richPresence'], $user['type']); 77 | } 78 | else { 79 | printf(" (%s)\n", $user['type']); 80 | } 81 | } 82 | } 83 | exit; 84 | //echo sha1($live->authorization_header); 85 | $continue = 0; 86 | $params = array(); 87 | $achievements = array(); 88 | do { 89 | $data = json_decode($live->fetchUserAchievements($params)); 90 | 91 | 92 | if (isset($data->pagingInfo->continuationToken)) { 93 | $params['continuationToken'] = $data->pagingInfo->continuationToken; 94 | $continue = 1; 95 | } 96 | else { 97 | $continue = 0; 98 | } 99 | $achievements = array_merge($achievements, $data->achievements); 100 | 101 | } while ($continue); 102 | 103 | print_r($achievements); 104 | 105 | //printf("%s\n", $live->authorization_header); 106 | 107 | // https://gameclipsmetadata.xboxlive.com/users/xuid(2615706747868666)/clips?continuationToken=abcde_vwxyzGAAAAA2&maxItems=24 -------------------------------------------------------------------------------- /sendMessage.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php 2 | getMessage()); 18 | exit; 19 | } 20 | } 21 | 22 | 23 | 24 | $shortopts = ""; 25 | $shortopts .= "g:"; 26 | $shortopts .= "m:"; 27 | 28 | $options = getopt($shortopts); 29 | 30 | if (!array_key_exists('g', $options)) { 31 | printf("pass -g\nComma separate multiple recipients\n"); 32 | exit; 33 | } 34 | 35 | if (!array_key_exists('m', $options)) { 36 | printf("pass -m\"fetchXuidForGamertag($gt); 43 | } 44 | 45 | 46 | $message = $options['m']; 47 | 48 | //$live->xuid = '2533274868737812'; 49 | $live->sendMessage($recipients, $message); 50 | -------------------------------------------------------------------------------- /status.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/php 2 | getMessage()); 20 | exit; 21 | } 22 | } 23 | 24 | 25 | //$url = sprintf('https://gameclipsmetadata.xboxlive.com/users/xuid(%s)/clips?continuationToken=abcde_vwxyzMAAAAA2&maxItems=24', '2615706747868666'); 26 | 27 | //$url = sprintf("https://social.xboxlive.com/users/xuid(%s)/people?maxItems=1000", $live->xuid); 28 | $shortopts = ""; 29 | $shortopts .= "g:"; 30 | 31 | $options = getopt($shortopts); 32 | 33 | if (!array_key_exists('g', $options)) { 34 | printf("pass -g\n"); 35 | } 36 | $gt = $options['g']; 37 | 38 | $doc = $live->fetchXuidForGamertag($options['g']); 39 | $users[] = $doc; 40 | $raw = $live->fetchUserPresence($users); 41 | echo $raw; 42 | $doc_presence = json_decode($raw); 43 | 44 | print_r($doc_presence); 45 | $state = $doc_presence[0]->state; 46 | 47 | 48 | if ($state == "Online") { 49 | $game_info = $doc_presence[0]->devices[0]->titles; 50 | $game = array_pop($game_info); 51 | $game_name = $game->name; 52 | if (array_key_exists('activity', $game)) 53 | $game_extended = $game->activity->richPresence; 54 | else 55 | $game_extended = "N/A"; 56 | } 57 | 58 | printf("%s is %s\n", $gt, $state); 59 | if ($state == "Online") { 60 | printf("Playing: %s - %s\n", $game_name, $game_extended); 61 | } 62 | exit; 63 | 64 | --------------------------------------------------------------------------------