Skip to content

Commit 4bb89d4

Browse files
authored
feat(people): add get_profile_by_xuid method (#108)
Adds `get_profile_by_xuid` to PeopleProvider for single-user profile lookups using the `/users/me/people/xuids({xuid})` endpoint. Returns relationship metadata (isFriend, canBeFriended etc.) from the caller's perspective, useful for profile lookups and relationship checks.
1 parent 67f5834 commit 4bb89d4

File tree

3 files changed

+130
-1
lines changed

3 files changed

+130
-1
lines changed

src/pythonxbox/api/provider/people/__init__.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
People - Access friendlist from own profiles and others
2+
People - Access friendlist and profile info from own profile and others
33
"""
44

55
from typing import TYPE_CHECKING, ClassVar
@@ -93,6 +93,33 @@ async def get_friends_by_xuid(
9393
resp.raise_for_status()
9494
return PeopleResponse.model_validate_json(resp.text)
9595

96+
async def get_friend_by_xuid(self, xuid: str, decoration_fields: list[PeopleDecoration] | None = None, **kwargs) -> PeopleResponse:
97+
"""
98+
Get a single friend's profile from the authenticated user's perspective
99+
100+
This returns relationship metadata (isFriend, canBeFriended, etc.) as seen
101+
by the caller, making it useful for profile lookups and relationship checks.
102+
103+
Args:
104+
xuid: XUID of the user to retrieve profile for
105+
106+
Returns:
107+
:class:`PeopleResponse`: People Response with a single person entry
108+
"""
109+
if not decoration_fields:
110+
decoration_fields = [
111+
PeopleDecoration.PREFERRED_COLOR,
112+
PeopleDecoration.DETAIL,
113+
PeopleDecoration.MULTIPLAYER_SUMMARY,
114+
PeopleDecoration.PRESENCE_DETAIL,
115+
]
116+
decoration = self.SEPERATOR.join(decoration_fields)
117+
118+
url = f"{self.PEOPLE_URL}/users/me/people/xuids({xuid})/decoration/{decoration}"
119+
resp = await self.client.session.get(url, headers=self._headers, **kwargs)
120+
resp.raise_for_status()
121+
return PeopleResponse.model_validate_json(resp.text)
122+
96123
async def get_friends_own_batch(
97124
self,
98125
xuids: list[str],
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
{
2+
"people": [
3+
{
4+
"xuid": "2533274812261808",
5+
"isFavorite": false,
6+
"isFollowingCaller": false,
7+
"isFollowedByCaller": true,
8+
"isIdentityShared": false,
9+
"addedDateTimeUtc": null,
10+
"displayName": "VolekTheFNDwarf",
11+
"realName": "",
12+
"displayPicRaw": "https://images-eds-ssl.xboxlive.com/image?url=wHwbXKif8cus8csoZ03RW3apWESZjav65Yncai8aRmVbSlZ3zqRpg1sdxEje_JmFTQaIPE",
13+
"showUserAsAvatar": "1",
14+
"gamertag": "VolekTheFNDwarf",
15+
"gamerScore": "70700",
16+
"modernGamertag": "VolekTheFNDwarf",
17+
"modernGamertagSuffix": "",
18+
"uniqueModernGamertag": "VolekTheFNDwarf",
19+
"xboxOneRep": "GoodPlayer",
20+
"presenceState": "Offline",
21+
"presenceText": "Offline",
22+
"presenceDevices": null,
23+
"isFriend": true,
24+
"isFriendRequestReceived": false,
25+
"isFriendRequestSent": false,
26+
"isBroadcasting": false,
27+
"isCloaked": null,
28+
"isQuarantined": false,
29+
"isXbox360Gamerpic": false,
30+
"lastSeenDateTimeUtc": null,
31+
"suggestion": null,
32+
"recommendation": null,
33+
"search": null,
34+
"titleHistory": null,
35+
"multiplayerSummary": {
36+
"inMultiplayerSession": 0,
37+
"inParty": 0
38+
},
39+
"recentPlayer": null,
40+
"follower": null,
41+
"preferredColor": {
42+
"primaryColor": "1081ca",
43+
"secondaryColor": "10314f",
44+
"tertiaryColor": "105080"
45+
},
46+
"presenceDetails": [],
47+
"titlePresence": null,
48+
"titleSummaries": null,
49+
"presenceTitleIds": null,
50+
"detail": {
51+
"accountTier": "Gold",
52+
"bio": "Lover of nerd culture & those who embrace it.",
53+
"isVerified": false,
54+
"location": "Charlotte",
55+
"tenure": "12",
56+
"watermarks": [],
57+
"blocked": false,
58+
"mute": false,
59+
"followerCount": 50,
60+
"followingCount": 34,
61+
"hasGamePass": false,
62+
"isFriend": true,
63+
"canBeFriended": true,
64+
"canBeFollowed": true,
65+
"friendCount": 150,
66+
"isFriendRequestReceived": false,
67+
"isFriendRequestSent": false,
68+
"isFriendListShared": true,
69+
"isFollowingCaller": false,
70+
"isFollowedByCaller": false,
71+
"isFavorite": false
72+
},
73+
"communityManagerTitles": null,
74+
"socialManager": null,
75+
"broadcast": null,
76+
"tournamentSummary": null,
77+
"avatar": null,
78+
"linkedAccounts": [],
79+
"colorTheme": "gamerpicblur",
80+
"preferredFlag": "",
81+
"preferredPlatforms": []
82+
}
83+
],
84+
"recommendationSummary": null,
85+
"friendFinderState": null,
86+
"accountLinkDetails": null
87+
}

tests/test_people.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,21 @@ async def test_people_friends_by_xuid(
3232
assert route.called
3333

3434

35+
@pytest.mark.asyncio
36+
async def test_people_friend_by_xuid(
37+
respx_mock: MockRouter, xbl_client: XboxLiveClient
38+
) -> None:
39+
route = respx_mock.get("https://peoplehub.xboxlive.com").mock(
40+
return_value=Response(200, json=get_response_json("people_profile_by_xuid"))
41+
)
42+
ret = await xbl_client.people.get_friend_by_xuid("2533274812261808")
43+
44+
assert len(ret.people) == 1
45+
assert ret.people[0].gamertag == "VolekTheFNDwarf"
46+
assert ret.people[0].is_friend is True
47+
assert route.called
48+
49+
3550
@pytest.mark.asyncio
3651
async def test_profiles_batch(
3752
respx_mock: MockRouter, xbl_client: XboxLiveClient

0 commit comments

Comments
 (0)