python-xbox 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. python_xbox-0.1.0.dist-info/METADATA +217 -0
  2. python_xbox-0.1.0.dist-info/RECORD +64 -0
  3. python_xbox-0.1.0.dist-info/WHEEL +4 -0
  4. python_xbox-0.1.0.dist-info/entry_points.txt +6 -0
  5. python_xbox-0.1.0.dist-info/licenses/LICENSE +20 -0
  6. pythonxbox/__init__.py +4 -0
  7. pythonxbox/api/__init__.py +0 -0
  8. pythonxbox/api/client.py +166 -0
  9. pythonxbox/api/language.py +76 -0
  10. pythonxbox/api/provider/__init__.py +0 -0
  11. pythonxbox/api/provider/account/__init__.py +73 -0
  12. pythonxbox/api/provider/account/models.py +11 -0
  13. pythonxbox/api/provider/achievements/__init__.py +164 -0
  14. pythonxbox/api/provider/achievements/models.py +133 -0
  15. pythonxbox/api/provider/baseprovider.py +22 -0
  16. pythonxbox/api/provider/catalog/__init__.py +86 -0
  17. pythonxbox/api/provider/catalog/const.py +15 -0
  18. pythonxbox/api/provider/catalog/models.py +428 -0
  19. pythonxbox/api/provider/cqs/__init__.py +85 -0
  20. pythonxbox/api/provider/cqs/models.py +59 -0
  21. pythonxbox/api/provider/gameclips/__init__.py +167 -0
  22. pythonxbox/api/provider/gameclips/models.py +58 -0
  23. pythonxbox/api/provider/lists/__init__.py +71 -0
  24. pythonxbox/api/provider/lists/models.py +33 -0
  25. pythonxbox/api/provider/mediahub/__init__.py +64 -0
  26. pythonxbox/api/provider/mediahub/models.py +82 -0
  27. pythonxbox/api/provider/message/__init__.py +135 -0
  28. pythonxbox/api/provider/message/models.py +96 -0
  29. pythonxbox/api/provider/people/__init__.py +193 -0
  30. pythonxbox/api/provider/people/models.py +252 -0
  31. pythonxbox/api/provider/presence/__init__.py +110 -0
  32. pythonxbox/api/provider/presence/models.py +53 -0
  33. pythonxbox/api/provider/profile/__init__.py +140 -0
  34. pythonxbox/api/provider/profile/models.py +47 -0
  35. pythonxbox/api/provider/ratelimitedprovider.py +79 -0
  36. pythonxbox/api/provider/screenshots/__init__.py +167 -0
  37. pythonxbox/api/provider/screenshots/models.py +56 -0
  38. pythonxbox/api/provider/smartglass/__init__.py +402 -0
  39. pythonxbox/api/provider/smartglass/models.py +186 -0
  40. pythonxbox/api/provider/titlehub/__init__.py +143 -0
  41. pythonxbox/api/provider/titlehub/models.py +106 -0
  42. pythonxbox/api/provider/usersearch/__init__.py +29 -0
  43. pythonxbox/api/provider/usersearch/models.py +17 -0
  44. pythonxbox/api/provider/userstats/__init__.py +164 -0
  45. pythonxbox/api/provider/userstats/models.py +44 -0
  46. pythonxbox/authentication/__init__.py +0 -0
  47. pythonxbox/authentication/manager.py +161 -0
  48. pythonxbox/authentication/models.py +162 -0
  49. pythonxbox/authentication/xal.py +348 -0
  50. pythonxbox/common/__init__.py +0 -0
  51. pythonxbox/common/exceptions.py +59 -0
  52. pythonxbox/common/filetimes.py +81 -0
  53. pythonxbox/common/models.py +34 -0
  54. pythonxbox/common/ratelimits/__init__.py +268 -0
  55. pythonxbox/common/ratelimits/models.py +23 -0
  56. pythonxbox/common/request_signer.py +190 -0
  57. pythonxbox/common/signed_session.py +60 -0
  58. pythonxbox/py.typed +0 -0
  59. pythonxbox/scripts/__init__.py +15 -0
  60. pythonxbox/scripts/authenticate.py +159 -0
  61. pythonxbox/scripts/change_gamertag.py +111 -0
  62. pythonxbox/scripts/friends.py +80 -0
  63. pythonxbox/scripts/search.py +43 -0
  64. pythonxbox/scripts/xal.py +113 -0
@@ -0,0 +1,193 @@
1
+ """
2
+ People - Access friendlist from own profiles and others
3
+ """
4
+
5
+ from pythonxbox.api.provider.people.models import (
6
+ PeopleDecoration,
7
+ PeopleResponse,
8
+ PeopleSummaryResponse,
9
+ )
10
+ from pythonxbox.api.provider.ratelimitedprovider import RateLimitedProvider
11
+ from typing import TYPE_CHECKING
12
+
13
+ if TYPE_CHECKING:
14
+ from pythonxbox.api.client import XboxLiveClient
15
+
16
+
17
+ class PeopleProvider(RateLimitedProvider):
18
+ SOCIAL_URL = "https://social.xboxlive.com"
19
+ HEADERS_SOCIAL = {"x-xbl-contract-version": "2"}
20
+ PEOPLE_URL = "https://peoplehub.xboxlive.com"
21
+ HEADERS_PEOPLE = {
22
+ "x-xbl-contract-version": "7",
23
+ "Accept-Language": "overwrite in __init__",
24
+ }
25
+ SEPERATOR = ","
26
+
27
+ # NOTE: Rate Limits are noted for social.xboxlive.com ONLY
28
+ RATE_LIMITS = {"burst": 10, "sustain": 30}
29
+
30
+ client: "XboxLiveClient"
31
+
32
+ def __init__(self, client: "XboxLiveClient") -> None:
33
+ """
34
+ Initialize Baseclass, set 'Accept-Language' header from client instance
35
+
36
+ Args:
37
+ client (:class:`XboxLiveClient`): Instance of client
38
+ """
39
+ super().__init__(client)
40
+ self._headers = {**self.HEADERS_PEOPLE}
41
+ self._headers.update({"Accept-Language": self.client.language.locale})
42
+
43
+ async def get_friends_own(
44
+ self, decoration_fields: list[PeopleDecoration] | None = None, **kwargs
45
+ ) -> PeopleResponse:
46
+ """
47
+ Get friendlist of own profile
48
+
49
+ Returns:
50
+ :class:`PeopleResponse`: People Response
51
+ """
52
+ if not decoration_fields:
53
+ decoration_fields = [
54
+ PeopleDecoration.PREFERRED_COLOR,
55
+ PeopleDecoration.DETAIL,
56
+ PeopleDecoration.MULTIPLAYER_SUMMARY,
57
+ PeopleDecoration.PRESENCE_DETAIL,
58
+ ]
59
+ decoration = self.SEPERATOR.join(decoration_fields)
60
+
61
+ url = f"{self.PEOPLE_URL}/users/me/people/friends/decoration/{decoration}"
62
+ resp = await self.client.session.get(url, headers=self._headers, **kwargs)
63
+ resp.raise_for_status()
64
+ return PeopleResponse(**resp.json())
65
+
66
+ async def get_friends_by_xuid(
67
+ self,
68
+ xuid: str,
69
+ decoration_fields: list[PeopleDecoration] | None = None,
70
+ **kwargs,
71
+ ) -> PeopleResponse:
72
+ """
73
+ Get friendlist of own profile
74
+
75
+ Returns:
76
+ :class:`PeopleResponse`: People Response
77
+ """
78
+ if not decoration_fields:
79
+ decoration_fields = [
80
+ PeopleDecoration.PREFERRED_COLOR,
81
+ PeopleDecoration.DETAIL,
82
+ PeopleDecoration.MULTIPLAYER_SUMMARY,
83
+ PeopleDecoration.PRESENCE_DETAIL,
84
+ ]
85
+ decoration = self.SEPERATOR.join(decoration_fields)
86
+
87
+ url = f"{self.PEOPLE_URL}/users/me/people/xuids({xuid})/decoration/{decoration}"
88
+ resp = await self.client.session.get(url, headers=self._headers, **kwargs)
89
+ resp.raise_for_status()
90
+ return PeopleResponse(**resp.json())
91
+
92
+ async def get_friends_own_batch(
93
+ self,
94
+ xuids: list[str],
95
+ decoration_fields: list[PeopleDecoration] | None = None,
96
+ **kwargs,
97
+ ) -> PeopleResponse:
98
+ """
99
+ Get friends metadata by providing a list of XUIDs
100
+
101
+ Args:
102
+ xuids: List of XUIDs
103
+
104
+ Returns:
105
+ :class:`PeopleResponse`: People Response
106
+ """
107
+ if not decoration_fields:
108
+ decoration_fields = [
109
+ PeopleDecoration.PREFERRED_COLOR,
110
+ PeopleDecoration.DETAIL,
111
+ PeopleDecoration.MULTIPLAYER_SUMMARY,
112
+ PeopleDecoration.PRESENCE_DETAIL,
113
+ ]
114
+ decoration = self.SEPERATOR.join(decoration_fields)
115
+
116
+ url = f"{self.PEOPLE_URL}/users/me/people/batch/decoration/{decoration}"
117
+ resp = await self.client.session.post(
118
+ url, json={"xuids": xuids}, headers=self._headers, **kwargs
119
+ )
120
+ resp.raise_for_status()
121
+ return PeopleResponse(**resp.json())
122
+
123
+ async def get_friend_recommendations(
124
+ self, decoration_fields: list[PeopleDecoration] | None = None, **kwargs
125
+ ) -> PeopleResponse:
126
+ """
127
+ Get recommended friends
128
+
129
+ Returns:
130
+ :class:`PeopleResponse`: People Response
131
+ """
132
+ if not decoration_fields:
133
+ decoration_fields = [PeopleDecoration.DETAIL]
134
+ decoration = self.SEPERATOR.join(decoration_fields)
135
+
136
+ url = (
137
+ f"{self.PEOPLE_URL}/users/me/people/recommendations/decoration/{decoration}"
138
+ )
139
+ resp = await self.client.session.get(url, headers=self._headers, **kwargs)
140
+ resp.raise_for_status()
141
+ return PeopleResponse(**resp.json())
142
+
143
+ async def get_friends_summary_own(self, **kwargs) -> PeopleSummaryResponse:
144
+ """
145
+ Get friendlist summary of own profile
146
+
147
+ Returns:
148
+ :class:`PeopleSummaryResponse`: People Summary Response
149
+ """
150
+ url = self.SOCIAL_URL + "/users/me/summary"
151
+ resp = await self.client.session.get(
152
+ url, headers=self.HEADERS_SOCIAL, rate_limits=self.rate_limit_read, **kwargs
153
+ )
154
+ resp.raise_for_status()
155
+ return PeopleSummaryResponse(**resp.json())
156
+
157
+ async def get_friends_summary_by_xuid(
158
+ self, xuid: str, **kwargs
159
+ ) -> PeopleSummaryResponse:
160
+ """
161
+ Get friendlist summary of user by xuid
162
+
163
+ Args:
164
+ xuid: XUID to request summary from
165
+
166
+ Returns:
167
+ :class:`PeopleSummaryResponse`: People Summary Response
168
+ """
169
+ url = self.SOCIAL_URL + f"/users/xuid({xuid})/summary"
170
+ resp = await self.client.session.get(
171
+ url, headers=self.HEADERS_SOCIAL, rate_limits=self.rate_limit_read, **kwargs
172
+ )
173
+ resp.raise_for_status()
174
+ return PeopleSummaryResponse(**resp.json())
175
+
176
+ async def get_friends_summary_by_gamertag(
177
+ self, gamertag: str, **kwargs
178
+ ) -> PeopleSummaryResponse:
179
+ """
180
+ Get friendlist summary of user by gamertag
181
+
182
+ Args:
183
+ gamertag: Gamertag to request friendlist from
184
+
185
+ Returns:
186
+ :class:`PeopleSummaryResponse`: People Summary Response
187
+ """
188
+ url = self.SOCIAL_URL + f"/users/gt({gamertag})/summary"
189
+ resp = await self.client.session.get(
190
+ url, headers=self.HEADERS_SOCIAL, rate_limits=self.rate_limit_read, **kwargs
191
+ )
192
+ resp.raise_for_status()
193
+ return PeopleSummaryResponse(**resp.json())
@@ -0,0 +1,252 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime
4
+ from enum import StrEnum
5
+ from typing import Any
6
+
7
+ from pydantic import Field
8
+
9
+ from pythonxbox.common.models import CamelCaseModel, PascalCaseModel
10
+
11
+
12
+ class PeopleDecoration(StrEnum):
13
+ SUGGESTION = "suggestion"
14
+ RECENT_PLAYER = "recentPlayer"
15
+ FOLLOWER = "follower"
16
+ PREFERRED_COLOR = "preferredColor"
17
+ DETAIL = "detail"
18
+ MULTIPLAYER_SUMMARY = "multiplayerSummary"
19
+ PRESENCE_DETAIL = "presenceDetail"
20
+ TITLE_PRESENCE = "titlePresence"
21
+ TITLE_SUMMARY = "titleSummary"
22
+ PRESENCE_TITLE_IDS = "presenceTitleIds"
23
+ COMMUNITY_MANAGER_TITLES = "communityManagerTitles"
24
+ SOCIAL_MANAGER = "socialManager"
25
+ BROADCAST = "broadcast"
26
+ TOURNAMENT_SUMMARY = "tournamentSummary"
27
+ AVATAR = "avatar"
28
+
29
+
30
+ class PeopleSummaryResponse(CamelCaseModel):
31
+ target_following_count: int
32
+ target_follower_count: int
33
+ is_caller_following_target: bool
34
+ is_target_following_caller: bool
35
+ has_caller_marked_target_as_favorite: bool
36
+ has_caller_marked_target_as_identity_shared: bool
37
+ legacy_friend_status: str
38
+ available_people_slots: int | None = None
39
+ recent_change_count: int | None = None
40
+ watermark: str | None = None
41
+ is_friend: bool
42
+
43
+
44
+ class Suggestion(PascalCaseModel):
45
+ type: str | None = None
46
+ priority: int
47
+ reasons: str | None = None
48
+ title_id: str | None = None
49
+
50
+
51
+ class Recommendation(PascalCaseModel):
52
+ type: str
53
+ reasons: list[str]
54
+
55
+
56
+ class SessionRef(CamelCaseModel):
57
+ scid: str
58
+ template_name: str
59
+ name: str
60
+
61
+
62
+ class PartyDetails(CamelCaseModel):
63
+ session_ref: SessionRef
64
+ status: str
65
+ visibility: str
66
+ join_restriction: str
67
+ accepted: int
68
+
69
+
70
+ class MultiplayerSummary(CamelCaseModel):
71
+ in_multiplayer_session: int | None = None
72
+ in_party: int
73
+ joinable_activities: list = Field(default_factory=list)
74
+ party_details: list[PartyDetails] = Field(default_factory=list)
75
+
76
+
77
+ class RecentPlayer(CamelCaseModel):
78
+ titles: list[str]
79
+ text: str | None = None
80
+
81
+
82
+ class Follower(CamelCaseModel):
83
+ text: str | None = None
84
+ followed_date_time_utc: datetime | None = None
85
+
86
+
87
+ class PreferredColor(CamelCaseModel):
88
+ primary_color: str | None = None
89
+ secondary_color: str | None = None
90
+ tertiary_color: str | None = None
91
+
92
+
93
+ class PresenceDetail(PascalCaseModel):
94
+ is_broadcasting: bool
95
+ device: str
96
+ device_sub_type: str | None = None
97
+ gameplay_type: str | None = None
98
+ presence_text: str
99
+ state: str
100
+ title_id: str
101
+ title_type: str | None = None
102
+ is_primary: bool
103
+ is_game: bool
104
+ rich_presence_text: str | None = None
105
+
106
+
107
+ class TitlePresence(PascalCaseModel):
108
+ is_currently_playing: bool
109
+ presence_text: str | None = None
110
+ title_name: str | None = None
111
+ title_id: str | None = None
112
+
113
+
114
+ class Detail(CamelCaseModel):
115
+ account_tier: str
116
+ bio: str | None = None
117
+ is_verified: bool
118
+ location: str | None = None
119
+ tenure: str | None = None
120
+ watermarks: list[str]
121
+ blocked: bool
122
+ mute: bool
123
+ follower_count: int
124
+ following_count: int
125
+ has_game_pass: bool
126
+ can_be_friended: bool
127
+ can_be_followed: bool
128
+ is_friend: bool
129
+ friend_count: int
130
+ is_friend_request_received: bool
131
+ is_friend_request_sent: bool
132
+ is_friend_list_shared: bool
133
+ is_following_caller: bool
134
+ is_followed_by_caller: bool
135
+ is_favorite: bool
136
+
137
+
138
+ class SocialManager(CamelCaseModel):
139
+ title_ids: list[str]
140
+ pages: list[str]
141
+
142
+
143
+ class Avatar(CamelCaseModel):
144
+ update_time_offset: datetime | None = None
145
+ spritesheet_metadata: Any | None = None
146
+
147
+
148
+ class LinkedAccount(CamelCaseModel):
149
+ network_name: str
150
+ display_name: str | None = None
151
+ show_on_profile: bool
152
+ is_family_friendly: bool
153
+ deeplink: str | None = None
154
+
155
+
156
+ class Person(CamelCaseModel):
157
+ xuid: str
158
+ is_favorite: bool
159
+ is_following_caller: bool
160
+ is_followed_by_caller: bool
161
+ is_identity_shared: bool
162
+ added_date_time_utc: datetime | None = None
163
+ display_name: str | None = None
164
+ real_name: str
165
+ display_pic_raw: str
166
+ show_user_as_avatar: str
167
+ gamertag: str
168
+ gamer_score: str
169
+ modern_gamertag: str
170
+ modern_gamertag_suffix: str
171
+ unique_modern_gamertag: str
172
+ xbox_one_rep: str
173
+ presence_state: str
174
+ presence_text: str
175
+ presence_devices: Any | None = None
176
+ is_broadcasting: bool
177
+ is_cloaked: bool | None = None
178
+ is_quarantined: bool
179
+ is_xbox_360_gamerpic: bool
180
+ last_seen_date_time_utc: datetime | None = None
181
+ suggestion: Suggestion | None = None
182
+ recommendation: Recommendation | None = None
183
+ search: Any | None = None
184
+ titleHistory: Any | None = None
185
+ multiplayer_summary: MultiplayerSummary | None = None
186
+ recent_player: RecentPlayer | None = None
187
+ follower: Follower | None = None
188
+ preferred_color: PreferredColor | None = None
189
+ presence_details: list[PresenceDetail] | None = None
190
+ title_presence: TitlePresence | None = None
191
+ title_summaries: Any | None = None
192
+ presence_title_ids: list[str] | None = None
193
+ detail: Detail | None = None
194
+ community_manager_titles: Any | None = None
195
+ social_manager: SocialManager | None = None
196
+ broadcast: list[Any] | None = None
197
+ tournament_summary: Any | None = None
198
+ avatar: Avatar | None = None
199
+ linked_accounts: list[LinkedAccount] | None = None
200
+ color_theme: str
201
+ preferred_flag: str
202
+ preferred_platforms: list[Any]
203
+ friended_date_time_utc: datetime | None = None
204
+ is_friend: bool
205
+ is_friend_request_received: bool
206
+ is_friend_request_sent: bool
207
+
208
+
209
+ class RecommendationSummary(CamelCaseModel):
210
+ friend_of_friend: int | None = None
211
+ facebook_friend: int | None = None
212
+ phone_contact: int | None = None
213
+ follower: int | None = None
214
+ VIP: int | None = None
215
+ steam_friend: int
216
+ promote_suggestions: bool
217
+ community_suggestion: int
218
+
219
+
220
+ class FriendFinderState(CamelCaseModel):
221
+ facebook_opt_in_status: str
222
+ facebook_token_status: str
223
+ phone_opt_in_status: str
224
+ phone_token_status: str
225
+ steam_opt_in_status: str
226
+ steam_token_status: str
227
+ discord_opt_in_status: str
228
+ discord_token_status: str
229
+ instagram_opt_in_status: str
230
+ instagram_token_status: str
231
+ mixer_opt_in_status: str
232
+ mixer_token_status: str
233
+ reddit_opt_in_status: str
234
+ reddit_token_status: str
235
+ twitch_opt_in_status: str
236
+ twitch_token_status: str
237
+ twitter_opt_in_status: str
238
+ twitter_token_status: str
239
+ you_tube_opt_in_status: str
240
+ you_tube_token_status: str
241
+
242
+
243
+ class FriendRequestSummary(CamelCaseModel):
244
+ friend_requests_received_count: int
245
+
246
+
247
+ class PeopleResponse(CamelCaseModel):
248
+ people: list[Person]
249
+ recommendation_summary: RecommendationSummary | None = None
250
+ friend_finder_state: FriendFinderState | None = None
251
+ account_link_details: list[LinkedAccount] | None = None
252
+ friend_request_summary: FriendRequestSummary | None = None
@@ -0,0 +1,110 @@
1
+ """
2
+ Presence - Get online status of friends
3
+ """
4
+
5
+ from pythonxbox.api.provider.baseprovider import BaseProvider
6
+ from pythonxbox.api.provider.presence.models import (
7
+ PresenceBatchResponse,
8
+ PresenceItem,
9
+ PresenceLevel,
10
+ PresenceState,
11
+ )
12
+
13
+
14
+ class PresenceProvider(BaseProvider):
15
+ PRESENCE_URL = "https://userpresence.xboxlive.com"
16
+ HEADERS_PRESENCE = {"x-xbl-contract-version": "3", "Accept": "application/json"}
17
+
18
+ async def get_presence(
19
+ self,
20
+ xuid: str,
21
+ presence_level: PresenceLevel = PresenceLevel.USER,
22
+ **kwargs,
23
+ ) -> PresenceItem:
24
+ """
25
+ Get presence for given xuid
26
+
27
+ Args:
28
+ xuid: XUID
29
+ presence_level: Filter level
30
+
31
+ Returns:
32
+ :class:`PresenceItem`: Presence Response
33
+ """
34
+ url = self.PRESENCE_URL + "/users/xuid(" + xuid + ")?level=" + presence_level
35
+
36
+ resp = await self.client.session.get(
37
+ url, headers=self.HEADERS_PRESENCE, **kwargs
38
+ )
39
+ resp.raise_for_status()
40
+ return PresenceItem(**resp.json())
41
+
42
+ async def get_presence_batch(
43
+ self,
44
+ xuids: list[str],
45
+ online_only: bool = False,
46
+ presence_level: PresenceLevel = PresenceLevel.USER,
47
+ **kwargs,
48
+ ) -> list[PresenceItem]:
49
+ """
50
+ Get presence for list of xuids
51
+
52
+ Args:
53
+ xuids: List of XUIDs
54
+ online_only: Only get online profiles
55
+ presence_level: Filter level
56
+
57
+ Returns: List[:class:`PresenceItem`]: List of presence items
58
+ """
59
+ if len(xuids) > 1100:
60
+ raise Exception("Xuid list length is > 1100")
61
+
62
+ url = self.PRESENCE_URL + "/users/batch"
63
+ post_data = {
64
+ "users": [str(x) for x in xuids],
65
+ "onlineOnly": online_only,
66
+ "level": presence_level,
67
+ }
68
+ resp = await self.client.session.post(
69
+ url, json=post_data, headers=self.HEADERS_PRESENCE, **kwargs
70
+ )
71
+ resp.raise_for_status()
72
+ parsed = PresenceBatchResponse.model_validate(resp.json())
73
+ return parsed.root
74
+
75
+ async def get_presence_own(
76
+ self, presence_level: PresenceLevel = PresenceLevel.ALL, **kwargs
77
+ ) -> PresenceItem:
78
+ """
79
+ Get presence of own profile
80
+
81
+ Args:
82
+ presence_level: Filter level
83
+
84
+ Returns:
85
+ :class:`PresenceItem`: Presence Response
86
+ """
87
+ url = self.PRESENCE_URL + "/users/me"
88
+ params = {"level": presence_level}
89
+ resp = await self.client.session.get(
90
+ url, params=params, headers=self.HEADERS_PRESENCE, **kwargs
91
+ )
92
+ resp.raise_for_status()
93
+ return PresenceItem(**resp.json())
94
+
95
+ async def set_presence_own(self, presence_state: PresenceState, **kwargs) -> bool:
96
+ """
97
+ Set presence of own profile
98
+
99
+ Args:
100
+ presence_state: State of presence
101
+
102
+ Returns:
103
+ `True` on success, `False` otherwise
104
+ """
105
+ url = self.PRESENCE_URL + f"/users/xuid({self.client.xuid})/state"
106
+ data = {"state": presence_state.value}
107
+ resp = await self.client.session.put(
108
+ url, json=data, headers=self.HEADERS_PRESENCE, **kwargs
109
+ )
110
+ return resp.status_code == 200
@@ -0,0 +1,53 @@
1
+ from enum import Enum
2
+ from pydantic import RootModel
3
+
4
+ from pythonxbox.common.models import CamelCaseModel
5
+
6
+
7
+ class PresenceLevel(str, Enum):
8
+ USER = "user"
9
+ DEVICE = "device"
10
+ TITLE = "title"
11
+ ALL = "all"
12
+
13
+
14
+ class PresenceState(str, Enum):
15
+ ACTIVE = "Active"
16
+ CLOAKED = "Cloaked"
17
+
18
+
19
+ class LastSeen(CamelCaseModel):
20
+ device_type: str
21
+ title_id: str | None = None
22
+ title_name: str
23
+ timestamp: str
24
+
25
+
26
+ class ActivityRecord(CamelCaseModel):
27
+ richPresence: str | None = None
28
+ media: str | None = None
29
+
30
+
31
+ class TitleRecord(CamelCaseModel):
32
+ id: str | None = None
33
+ name: str | None = None
34
+ activity: list[ActivityRecord] | None = None
35
+ lastModified: str | None = None
36
+ placement: str | None = None
37
+ state: str | None = None
38
+
39
+
40
+ class DeviceRecord(CamelCaseModel):
41
+ titles: list[TitleRecord] | None = None
42
+ type: str | None = None
43
+
44
+
45
+ class PresenceItem(CamelCaseModel):
46
+ xuid: str
47
+ state: str
48
+ last_seen: LastSeen | None = None
49
+ devices: list[DeviceRecord] | None = None
50
+
51
+
52
+ class PresenceBatchResponse(RootModel[list[PresenceItem]], CamelCaseModel):
53
+ root: list[PresenceItem]