From 247b873349dfbac07991f0c0f077883282d01cad Mon Sep 17 00:00:00 2001 From: misiektoja Date: Mon, 2 Mar 2026 01:53:04 +0100 Subject: [PATCH 1/3] feat(people): add get_profile_by_xuid method --- .../api/provider/people/__init__.py | 29 ++++++- .../responses/people_profile_by_xuid.json | 87 +++++++++++++++++++ tests/test_people.py | 15 ++++ 3 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 tests/data/responses/people_profile_by_xuid.json diff --git a/src/pythonxbox/api/provider/people/__init__.py b/src/pythonxbox/api/provider/people/__init__.py index f7f3801..81daf4f 100644 --- a/src/pythonxbox/api/provider/people/__init__.py +++ b/src/pythonxbox/api/provider/people/__init__.py @@ -1,5 +1,5 @@ """ -People - Access friendlist from own profiles and others +People - Access friendlist and profile info from own profile and others """ from typing import TYPE_CHECKING, ClassVar @@ -93,6 +93,33 @@ async def get_friends_by_xuid( resp.raise_for_status() return PeopleResponse.model_validate_json(resp.text) + async def get_profile_by_xuid(self, xuid: str, decoration_fields: list[PeopleDecoration] | None = None, **kwargs) -> PeopleResponse: + """ + Get a single user's profile from the authenticated user's perspective + + This returns relationship metadata (isFriend, canBeFriended, etc.) as seen + by the caller, making it useful for profile lookups and relationship checks. + + Args: + xuid: XUID of the user to retrieve profile for + + Returns: + :class:`PeopleResponse`: People Response with a single person entry + """ + if not decoration_fields: + decoration_fields = [ + PeopleDecoration.PREFERRED_COLOR, + PeopleDecoration.DETAIL, + PeopleDecoration.MULTIPLAYER_SUMMARY, + PeopleDecoration.PRESENCE_DETAIL, + ] + decoration = self.SEPERATOR.join(decoration_fields) + + url = f"{self.PEOPLE_URL}/users/me/people/xuids({xuid})/decoration/{decoration}" + resp = await self.client.session.get(url, headers=self._headers, **kwargs) + resp.raise_for_status() + return PeopleResponse.model_validate_json(resp.text) + async def get_friends_own_batch( self, xuids: list[str], diff --git a/tests/data/responses/people_profile_by_xuid.json b/tests/data/responses/people_profile_by_xuid.json new file mode 100644 index 0000000..d31ea8d --- /dev/null +++ b/tests/data/responses/people_profile_by_xuid.json @@ -0,0 +1,87 @@ +{ + "people": [ + { + "xuid": "2533274812261808", + "isFavorite": false, + "isFollowingCaller": false, + "isFollowedByCaller": true, + "isIdentityShared": false, + "addedDateTimeUtc": null, + "displayName": "VolekTheFNDwarf", + "realName": "", + "displayPicRaw": "https://images-eds-ssl.xboxlive.com/image?url=wHwbXKif8cus8csoZ03RW3apWESZjav65Yncai8aRmVbSlZ3zqRpg1sdxEje_JmFTQaIPE", + "showUserAsAvatar": "1", + "gamertag": "VolekTheFNDwarf", + "gamerScore": "70700", + "modernGamertag": "VolekTheFNDwarf", + "modernGamertagSuffix": "", + "uniqueModernGamertag": "VolekTheFNDwarf", + "xboxOneRep": "GoodPlayer", + "presenceState": "Offline", + "presenceText": "Offline", + "presenceDevices": null, + "isFriend": true, + "isFriendRequestReceived": false, + "isFriendRequestSent": false, + "isBroadcasting": false, + "isCloaked": null, + "isQuarantined": false, + "isXbox360Gamerpic": false, + "lastSeenDateTimeUtc": null, + "suggestion": null, + "recommendation": null, + "search": null, + "titleHistory": null, + "multiplayerSummary": { + "inMultiplayerSession": 0, + "inParty": 0 + }, + "recentPlayer": null, + "follower": null, + "preferredColor": { + "primaryColor": "1081ca", + "secondaryColor": "10314f", + "tertiaryColor": "105080" + }, + "presenceDetails": [], + "titlePresence": null, + "titleSummaries": null, + "presenceTitleIds": null, + "detail": { + "accountTier": "Gold", + "bio": "Lover of nerd culture & those who embrace it.", + "isVerified": false, + "location": "Charlotte", + "tenure": "12", + "watermarks": [], + "blocked": false, + "mute": false, + "followerCount": 50, + "followingCount": 34, + "hasGamePass": false, + "isFriend": true, + "canBeFriended": true, + "canBeFollowed": true, + "friendCount": 150, + "isFriendRequestReceived": false, + "isFriendRequestSent": false, + "isFriendListShared": true, + "isFollowingCaller": false, + "isFollowedByCaller": false, + "isFavorite": false + }, + "communityManagerTitles": null, + "socialManager": null, + "broadcast": null, + "tournamentSummary": null, + "avatar": null, + "linkedAccounts": [], + "colorTheme": "gamerpicblur", + "preferredFlag": "", + "preferredPlatforms": [] + } + ], + "recommendationSummary": null, + "friendFinderState": null, + "accountLinkDetails": null +} diff --git a/tests/test_people.py b/tests/test_people.py index 5f84f7e..074ec1b 100644 --- a/tests/test_people.py +++ b/tests/test_people.py @@ -32,6 +32,21 @@ async def test_people_friends_by_xuid( assert route.called +@pytest.mark.asyncio +async def test_people_profile_by_xuid( + respx_mock: MockRouter, xbl_client: XboxLiveClient +) -> None: + route = respx_mock.get("https://peoplehub.xboxlive.com").mock( + return_value=Response(200, json=get_response_json("people_profile_by_xuid")) + ) + ret = await xbl_client.people.get_profile_by_xuid("2533274812261808") + + assert len(ret.people) == 1 + assert ret.people[0].gamertag == "VolekTheFNDwarf" + assert ret.people[0].is_friend is True + assert route.called + + @pytest.mark.asyncio async def test_profiles_batch( respx_mock: MockRouter, xbl_client: XboxLiveClient From 96a423547f6c5ba92d7cead7104f1261a8297a23 Mon Sep 17 00:00:00 2001 From: misiektoja Date: Mon, 2 Mar 2026 16:11:31 +0100 Subject: [PATCH 2/3] refactor(people): rename get_profile_by_xuid to get_friend_by_xuid --- tests/test_people.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_people.py b/tests/test_people.py index 074ec1b..5ec1ae1 100644 --- a/tests/test_people.py +++ b/tests/test_people.py @@ -33,13 +33,13 @@ async def test_people_friends_by_xuid( @pytest.mark.asyncio -async def test_people_profile_by_xuid( +async def test_people_friend_by_xuid( respx_mock: MockRouter, xbl_client: XboxLiveClient ) -> None: route = respx_mock.get("https://peoplehub.xboxlive.com").mock( return_value=Response(200, json=get_response_json("people_profile_by_xuid")) ) - ret = await xbl_client.people.get_profile_by_xuid("2533274812261808") + ret = await xbl_client.people.get_friend_by_xuid("2533274812261808") assert len(ret.people) == 1 assert ret.people[0].gamertag == "VolekTheFNDwarf" From 7ec4d1c12cd5bb86284c1da63d55aa759ed097b9 Mon Sep 17 00:00:00 2001 From: misiektoja Date: Mon, 2 Mar 2026 16:12:33 +0100 Subject: [PATCH 3/3] refactor(people): rename get_profile_by_xuid to get_friend_by_xuid --- src/pythonxbox/api/provider/people/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pythonxbox/api/provider/people/__init__.py b/src/pythonxbox/api/provider/people/__init__.py index 81daf4f..9a4e35f 100644 --- a/src/pythonxbox/api/provider/people/__init__.py +++ b/src/pythonxbox/api/provider/people/__init__.py @@ -93,9 +93,9 @@ async def get_friends_by_xuid( resp.raise_for_status() return PeopleResponse.model_validate_json(resp.text) - async def get_profile_by_xuid(self, xuid: str, decoration_fields: list[PeopleDecoration] | None = None, **kwargs) -> PeopleResponse: + async def get_friend_by_xuid(self, xuid: str, decoration_fields: list[PeopleDecoration] | None = None, **kwargs) -> PeopleResponse: """ - Get a single user's profile from the authenticated user's perspective + Get a single friend's profile from the authenticated user's perspective This returns relationship metadata (isFriend, canBeFriended, etc.) as seen by the caller, making it useful for profile lookups and relationship checks.