├── 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 |
--------------------------------------------------------------------------------