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,167 @@
1
+ """
2
+ Gameclips - Get gameclip info
3
+ """
4
+
5
+ from pythonxbox.api.provider.baseprovider import BaseProvider
6
+ from pythonxbox.api.provider.gameclips.models import GameclipsResponse
7
+
8
+
9
+ class GameclipProvider(BaseProvider):
10
+ GAMECLIPS_METADATA_URL = "https://gameclipsmetadata.xboxlive.com"
11
+ HEADERS_GAMECLIPS_METADATA = {"x-xbl-contract-version": "1"}
12
+
13
+ async def get_recent_community_clips_by_title_id(
14
+ self, title_id: str, **kwargs
15
+ ) -> GameclipsResponse:
16
+ """
17
+ Get recent community clips by Title Id
18
+
19
+ Args:
20
+ title_id: Title Id to get clips for
21
+
22
+ Returns:
23
+ :class:`GameclipsResponse`: Game clip Response
24
+ """
25
+ url = self.GAMECLIPS_METADATA_URL + f"/public/titles/{title_id}/clips"
26
+ params = {"qualifier": "created"}
27
+ resp = await self.client.session.get(
28
+ url, params=params, headers=self.HEADERS_GAMECLIPS_METADATA, **kwargs
29
+ )
30
+ resp.raise_for_status()
31
+ return GameclipsResponse(**resp.json())
32
+
33
+ async def get_recent_own_clips(
34
+ self, title_id: str = None, skip_items: int = 0, max_items: int = 25, **kwargs
35
+ ) -> GameclipsResponse:
36
+ """
37
+ Get own recent clips, optionally filter for title Id
38
+
39
+ Args:
40
+ title_id: Title ID to filter
41
+ skip_items: Item count to skip
42
+ max_items: Maximum item count to load
43
+
44
+ Returns:
45
+ :class:`GameclipsResponse`: Game clip Response
46
+ """
47
+ url = self.GAMECLIPS_METADATA_URL + "/users/me"
48
+ if title_id:
49
+ url += f"/titles/{title_id}"
50
+ url += "/clips"
51
+
52
+ params = {"skipItems": skip_items, "maxItems": max_items}
53
+ resp = await self.client.session.get(
54
+ url, params=params, headers=self.HEADERS_GAMECLIPS_METADATA, **kwargs
55
+ )
56
+ resp.raise_for_status()
57
+ return GameclipsResponse(**resp.json())
58
+
59
+ async def get_recent_clips_by_xuid(
60
+ self,
61
+ xuid: str,
62
+ title_id: str = None,
63
+ skip_items: int = 0,
64
+ max_items: int = 25,
65
+ **kwargs,
66
+ ) -> GameclipsResponse:
67
+ """
68
+ Get clips by XUID, optionally filter for title Id
69
+
70
+ Args:
71
+ xuid: XUID of user to get clips from
72
+ title_id: Optional title id filter
73
+ skip_items: Item count to skip
74
+ max_items: Maximum item count to load
75
+
76
+ Returns:
77
+ :class:`GameclipsResponse`: Game clip Response
78
+ """
79
+ url = self.GAMECLIPS_METADATA_URL + f"/users/xuid({xuid})"
80
+ if title_id:
81
+ url += f"/titles/{title_id}"
82
+ url += "/clips"
83
+
84
+ params = {"skipItems": skip_items, "maxItems": max_items}
85
+ resp = await self.client.session.get(
86
+ url, params=params, headers=self.HEADERS_GAMECLIPS_METADATA, **kwargs
87
+ )
88
+ resp.raise_for_status()
89
+ return GameclipsResponse(**resp.json())
90
+
91
+ async def get_saved_community_clips_by_title_id(
92
+ self, title_id: str, **kwargs
93
+ ) -> GameclipsResponse:
94
+ """
95
+ Get saved community clips by Title Id
96
+
97
+ Args:
98
+ title_id: Title Id to get screenshots for
99
+
100
+ Returns:
101
+ :class:`GameclipsResponse`: Game clip Response
102
+ """
103
+ url = self.GAMECLIPS_METADATA_URL + f"/public/titles/{title_id}/clips/saved"
104
+ params = {"qualifier": "created"}
105
+ resp = await self.client.session.get(
106
+ url, params=params, headers=self.HEADERS_GAMECLIPS_METADATA, **kwargs
107
+ )
108
+ resp.raise_for_status()
109
+ return GameclipsResponse(**resp.json())
110
+
111
+ async def get_saved_own_clips(
112
+ self, title_id: str = None, skip_items: int = 0, max_items: int = 25, **kwargs
113
+ ) -> GameclipsResponse:
114
+ """
115
+ Get own saved clips, optionally filter for title Id an
116
+
117
+ Args:
118
+ title_id: Optional Title ID to filter
119
+ skip_items: Item count to skip
120
+ max_items: Maximum item count to load
121
+
122
+ Returns:
123
+ :class:`GameclipsResponse`: Game clip Response
124
+ """
125
+ url = self.GAMECLIPS_METADATA_URL + "/users/me"
126
+ if title_id:
127
+ url += f"/titles/{title_id}"
128
+ url += "/clips/saved"
129
+
130
+ params = {"skipItems": skip_items, "maxItems": max_items}
131
+ resp = await self.client.session.get(
132
+ url, params=params, headers=self.HEADERS_GAMECLIPS_METADATA, **kwargs
133
+ )
134
+ resp.raise_for_status()
135
+ return GameclipsResponse(**resp.json())
136
+
137
+ async def get_saved_clips_by_xuid(
138
+ self,
139
+ xuid: str,
140
+ title_id: str = None,
141
+ skip_items: int = 0,
142
+ max_items: int = 25,
143
+ **kwargs,
144
+ ) -> GameclipsResponse:
145
+ """
146
+ Get saved clips by XUID, optionally filter for title Id
147
+
148
+ Args:
149
+ xuid: XUID of user to get screenshots from
150
+ title_id: Optional title id filter
151
+ skip_items: Item count to skip
152
+ max_items: Maximum item count to load
153
+
154
+ Returns:
155
+ :class:`GameclipsResponse`: Game clip Response
156
+ """
157
+ url = self.GAMECLIPS_METADATA_URL + f"/users/xuid({xuid})"
158
+ if title_id:
159
+ url += f"/titles/{title_id}"
160
+ url += "/clips/saved"
161
+
162
+ params = {"skipItems": skip_items, "maxItems": max_items}
163
+ resp = await self.client.session.get(
164
+ url, params=params, headers=self.HEADERS_GAMECLIPS_METADATA, **kwargs
165
+ )
166
+ resp.raise_for_status()
167
+ return GameclipsResponse(**resp.json())
@@ -0,0 +1,58 @@
1
+ from datetime import datetime
2
+
3
+ from pythonxbox.common.models import CamelCaseModel
4
+
5
+
6
+ class Thumbnail(CamelCaseModel):
7
+ uri: str
8
+ file_size: int
9
+ thumbnail_type: int
10
+
11
+
12
+ class GameClipUri(CamelCaseModel):
13
+ uri: str
14
+ file_size: int
15
+ uri_type: int
16
+ expiration: str
17
+
18
+
19
+ class GameClip(CamelCaseModel):
20
+ game_clip_id: str
21
+ state: int
22
+ date_published: datetime
23
+ date_recorded: datetime
24
+ last_modified: datetime
25
+ user_caption: str
26
+ type: int
27
+ duration_in_seconds: int
28
+ scid: str
29
+ title_id: int
30
+ rating: float
31
+ rating_count: int
32
+ views: int
33
+ title_data: str
34
+ system_properties: str
35
+ saved_by_user: bool
36
+ achievement_id: str
37
+ greatest_moment_id: str
38
+ thumbnails: list[Thumbnail]
39
+ game_clip_uris: list[GameClipUri]
40
+ xuid: str
41
+ clip_name: str
42
+ title_name: str
43
+ game_clip_locale: str
44
+ clip_content_attributes: int
45
+ device_type: str
46
+ comment_count: int
47
+ like_count: int
48
+ share_count: int
49
+ partial_views: int
50
+
51
+
52
+ class PagingInfo(CamelCaseModel):
53
+ continuation_token: str | None = None
54
+
55
+
56
+ class GameclipsResponse(CamelCaseModel):
57
+ game_clips: list[GameClip]
58
+ paging_info: PagingInfo
@@ -0,0 +1,71 @@
1
+ """
2
+ EPLists - Mainly used for XBL Pins
3
+ """
4
+
5
+ from pythonxbox.api.provider.baseprovider import BaseProvider
6
+ from pythonxbox.api.provider.lists.models import ListMetadata, ListsResponse
7
+
8
+
9
+ class ListsProvider(BaseProvider):
10
+ LISTS_URL = "https://eplists.xboxlive.com"
11
+ HEADERS_LISTS = {"Content-Type": "application/json", "x-xbl-contract-version": "2"}
12
+
13
+ SEPERATOR = "."
14
+
15
+ async def remove_items(
16
+ self, xuid: str, post_body: dict, listname: str = "XBLPins", **kwargs
17
+ ) -> ListMetadata:
18
+ """
19
+ Remove items from specific list, defaults to "XBLPins"
20
+
21
+ Args:
22
+ xuid (str/int): Xbox User Id
23
+ listname (str): Name of list to edit
24
+
25
+ Returns:
26
+ :class:`ListMetadata`: List Metadata Response
27
+ """
28
+ url = self.LISTS_URL + f"/users/xuid({xuid})/lists/PINS/{listname}"
29
+ resp = await self.client.session.delete(
30
+ url, json=post_body, headers=self.HEADERS_LISTS, **kwargs
31
+ )
32
+ resp.raise_for_status()
33
+ return ListMetadata(**resp.json())
34
+
35
+ async def get_items(
36
+ self, xuid: str, listname: str = "XBLPins", **kwargs
37
+ ) -> ListsResponse:
38
+ """
39
+ Get items from specific list, defaults to "XBLPins"
40
+
41
+ Args:
42
+ xuid (str/int): Xbox User Id
43
+ listname (str): Name of list to edit
44
+
45
+ Returns:
46
+ :class:`ListsResponse`: List Response
47
+ """
48
+ url = self.LISTS_URL + f"/users/xuid({xuid})/lists/PINS/{listname}"
49
+ resp = await self.client.session.get(url, headers=self.HEADERS_LISTS, **kwargs)
50
+ resp.raise_for_status()
51
+ return ListsResponse(**resp.json())
52
+
53
+ async def insert_items(
54
+ self, xuid: str, post_body: dict, listname: str = "XBLPins", **kwargs
55
+ ) -> ListMetadata:
56
+ """
57
+ Insert items to specific list, defaults to "XBLPins"
58
+
59
+ Args:
60
+ xuid (str/int): Xbox User Id
61
+ listname (str): Name of list to edit
62
+
63
+ Returns:
64
+ :class:`ListMetadata`: List Metadata Response
65
+ """
66
+ url = self.LISTS_URL + f"/users/xuid({xuid})/lists/PINS/{listname}"
67
+ resp = await self.client.session.post(
68
+ url, json=post_body, headers=self.HEADERS_LISTS, **kwargs
69
+ )
70
+ resp.raise_for_status()
71
+ return ListMetadata(**resp.json())
@@ -0,0 +1,33 @@
1
+ from pythonxbox.common.models import PascalCaseModel
2
+
3
+
4
+ class Item(PascalCaseModel):
5
+ item_id: str
6
+ content_type: str
7
+ title: str | None = None
8
+ device_type: str
9
+ provider: str | None = None
10
+ provider_id: str | None = None
11
+
12
+
13
+ class ListItem(PascalCaseModel):
14
+ date_added: str
15
+ date_modified: str
16
+ index: int
17
+ k_value: int
18
+ item: Item
19
+
20
+
21
+ class ListMetadata(PascalCaseModel):
22
+ list_title: str
23
+ list_version: int
24
+ list_count: int
25
+ allow_duplicates: bool
26
+ max_list_size: int
27
+ access_setting: str
28
+
29
+
30
+ class ListsResponse(PascalCaseModel):
31
+ impression_id: str
32
+ list_items: list[ListItem]
33
+ list_metadata: ListMetadata
@@ -0,0 +1,64 @@
1
+ """
2
+ Mediahub - Fetch screenshots and gameclips
3
+ """
4
+
5
+ from pythonxbox.api.provider.baseprovider import BaseProvider
6
+ from pythonxbox.api.provider.mediahub.models import (
7
+ MediahubGameclips,
8
+ MediahubScreenshots,
9
+ )
10
+
11
+
12
+ class MediahubProvider(BaseProvider):
13
+ MEDIAHUB_URL = "https://mediahub.xboxlive.com"
14
+ HEADERS = {"x-xbl-contract-version": "3"}
15
+
16
+ async def fetch_own_clips(
17
+ self, skip: int = 0, count: int = 500, **kwargs
18
+ ) -> MediahubGameclips:
19
+ """
20
+ Fetch own clips
21
+
22
+ Args:
23
+ skip: Number of items to skip
24
+ count: Max entries to fetch
25
+
26
+ Returns:
27
+ :class:`MediahubGameclips`: Gameclips
28
+ """
29
+ url = f"{self.MEDIAHUB_URL}/gameclips/search"
30
+ post_data = {
31
+ "max": count,
32
+ "query": f"OwnerXuid eq {self.client.xuid}",
33
+ "skip": skip,
34
+ }
35
+ resp = await self.client.session.post(
36
+ url, json=post_data, headers=self.HEADERS, **kwargs
37
+ )
38
+ resp.raise_for_status()
39
+ return MediahubGameclips(**resp.json())
40
+
41
+ async def fetch_own_screenshots(
42
+ self, skip: int = 0, count: int = 500, **kwargs
43
+ ) -> MediahubScreenshots:
44
+ """
45
+ Fetch own screenshots
46
+
47
+ Args:
48
+ skip: Number of items to skip
49
+ count: Max entries to fetch
50
+
51
+ Returns:
52
+ :class:`MediahubScreenshots`: Screenshots
53
+ """
54
+ url = f"{self.MEDIAHUB_URL}/screenshots/search"
55
+ post_data = {
56
+ "max": count,
57
+ "query": f"OwnerXuid eq {self.client.xuid}",
58
+ "skip": skip,
59
+ }
60
+ resp = await self.client.session.post(
61
+ url, json=post_data, headers=self.HEADERS, **kwargs
62
+ )
63
+ resp.raise_for_status()
64
+ return MediahubScreenshots(**resp.json())
@@ -0,0 +1,82 @@
1
+ from pythonxbox.common.models import CamelCaseModel
2
+
3
+
4
+ class ContentSegment(CamelCaseModel):
5
+ segment_id: int
6
+ creation_type: str
7
+ creator_channel_id: str | None = None
8
+ creator_xuid: int
9
+ record_date: str
10
+ duration_in_seconds: int
11
+ offset: int
12
+ secondary_title_id: int | None = None
13
+ title_id: int
14
+
15
+
16
+ class ContentLocator(CamelCaseModel):
17
+ expiration: str | None = None
18
+ file_size: int | None = None
19
+ locator_type: str
20
+ uri: str
21
+
22
+
23
+ class GameclipContent(CamelCaseModel):
24
+ content_id: str
25
+ content_locators: list[ContentLocator]
26
+ content_segments: list[ContentSegment]
27
+ creation_type: str
28
+ duration_in_seconds: int
29
+ local_id: str
30
+ owner_xuid: int
31
+ sandbox_id: str
32
+ shared_to: list[int]
33
+ title_id: int
34
+ title_name: str
35
+ upload_date: str
36
+ upload_language: str
37
+ upload_region: str
38
+ upload_title_id: int
39
+ upload_device_type: str
40
+ comment_count: int
41
+ like_count: int
42
+ share_count: int
43
+ view_count: int
44
+ content_state: str
45
+ enforcement_state: str
46
+ sessions: list[str]
47
+ tournaments: list[str]
48
+
49
+
50
+ class MediahubGameclips(CamelCaseModel):
51
+ values: list[GameclipContent]
52
+
53
+
54
+ class ScreenshotContent(CamelCaseModel):
55
+ content_id: str
56
+ capture_date: str
57
+ content_locators: list[ContentLocator]
58
+ local_id: str
59
+ owner_xuid: int
60
+ resolution_height: int
61
+ resolution_width: int
62
+ date_uploaded: str
63
+ sandbox_id: str
64
+ shared_to: list[int]
65
+ title_id: int
66
+ title_name: str
67
+ upload_language: str
68
+ upload_region: str
69
+ upload_title_id: int
70
+ upload_device_type: str
71
+ comment_count: int
72
+ like_count: int
73
+ share_count: int
74
+ view_count: int
75
+ content_state: str
76
+ enforcement_state: str
77
+ sessions: list[str]
78
+ tournaments: list[str]
79
+
80
+
81
+ class MediahubScreenshots(CamelCaseModel):
82
+ values: list[ScreenshotContent]
@@ -0,0 +1,135 @@
1
+ """
2
+ Message - Read and send messages
3
+
4
+ TODO: Support group messaging
5
+ """
6
+
7
+ from pythonxbox.api.provider.baseprovider import BaseProvider
8
+ from pythonxbox.api.provider.message.models import (
9
+ ConversationResponse,
10
+ InboxResponse,
11
+ SendMessageResponse,
12
+ )
13
+
14
+
15
+ class MessageProvider(BaseProvider):
16
+ MSG_URL = "https://xblmessaging.xboxlive.com"
17
+ HEADERS_MESSAGE = {"x-xbl-contract-version": "1"}
18
+ HEADERS_HORIZON = {"x-xbl-contract-version": "2"}
19
+
20
+ async def get_inbox(self, max_items: int = 100, **kwargs) -> InboxResponse:
21
+ """
22
+ Get messages
23
+
24
+ Returns:
25
+ :class:`InboxResponse`: Inbox Response
26
+ """
27
+ url = f"{self.MSG_URL}/network/Xbox/users/me/inbox"
28
+ params = {"maxItems": max_items}
29
+ resp = await self.client.session.get(
30
+ url, params=params, headers=self.HEADERS_MESSAGE, **kwargs
31
+ )
32
+ resp.raise_for_status()
33
+ return InboxResponse(**resp.json())
34
+
35
+ async def get_conversation(
36
+ self, xuid: str, max_items: int = 100, **kwargs
37
+ ) -> ConversationResponse:
38
+ """
39
+ Get detailed conversation info
40
+
41
+ Args:
42
+ xuid: Xuid of user having a conversation with
43
+
44
+ Returns:
45
+ :class:`ConversationResponse`: Conversation Response
46
+ """
47
+ url = f"{self.MSG_URL}/network/Xbox/users/me/conversations/users/xuid({xuid})"
48
+ params = {"maxItems": max_items}
49
+ resp = await self.client.session.get(
50
+ url, params=params, headers=self.HEADERS_MESSAGE, **kwargs
51
+ )
52
+ resp.raise_for_status()
53
+ return ConversationResponse(**resp.json())
54
+
55
+ async def delete_conversation(
56
+ self, conversation_id: str, horizon: str, **kwargs
57
+ ) -> bool:
58
+ """
59
+ Delete message
60
+
61
+ **NOTE**: Returns HTTP Status Code **200** on success
62
+
63
+ Args:
64
+ conversation_id: Message Id
65
+ horizon: Delete horizon from get conversation response
66
+
67
+ Returns: True on success, False otherwise
68
+ """
69
+ url = f"{self.MSG_URL}/network/Xbox/users/me/conversations/horizon"
70
+ post_data = {
71
+ "conversations": [
72
+ {
73
+ "conversationId": conversation_id,
74
+ "conversationType": "OneToOne",
75
+ "horizonType": "Delete",
76
+ "horizon": horizon,
77
+ }
78
+ ]
79
+ }
80
+ resp = await self.client.session.put(
81
+ url, json=post_data, headers=self.HEADERS_HORIZON, **kwargs
82
+ )
83
+ return resp.status_code == 200
84
+
85
+ async def delete_message(
86
+ self, conversation_id: str, message_id: str, **kwargs
87
+ ) -> bool:
88
+ """
89
+ Delete message
90
+
91
+ **NOTE**: Returns HTTP Status Code **200** on success
92
+
93
+ Args:
94
+ conversation_id: Conversation Id
95
+ message_id: Message Id
96
+
97
+ Returns: True on success, False otherwise
98
+ """
99
+ url = f"{self.MSG_URL}/network/Xbox/users/me/conversations/{conversation_id}/messages/{message_id}"
100
+ resp = await self.client.session.delete(
101
+ url, headers=self.HEADERS_MESSAGE, **kwargs
102
+ )
103
+ return resp.status_code == 200
104
+
105
+ async def send_message(
106
+ self, xuid: str, message_text: str, **kwargs
107
+ ) -> SendMessageResponse:
108
+ """
109
+ Send message to an xuid
110
+
111
+ Args:
112
+ xuid: Xuid
113
+ message_text: Message text
114
+
115
+ Returns:
116
+ :class:`SendMessageResponse`: Send Message Response
117
+ """
118
+ if len(message_text) > 256:
119
+ raise ValueError("Message text exceeds max length of 256 chars")
120
+
121
+ url = f"{self.MSG_URL}/network/Xbox/users/me/conversations/users/xuid({xuid})"
122
+ post_data = {
123
+ "parts": [
124
+ {
125
+ "contentType": "text",
126
+ "version": 0,
127
+ "text": message_text,
128
+ }
129
+ ]
130
+ }
131
+ resp = await self.client.session.post(
132
+ url, json=post_data, headers=self.HEADERS_MESSAGE, **kwargs
133
+ )
134
+ resp.raise_for_status()
135
+ return SendMessageResponse(**resp.json())
@@ -0,0 +1,96 @@
1
+ from datetime import datetime
2
+ from typing import Any
3
+
4
+ from pythonxbox.common.models import CamelCaseModel
5
+
6
+
7
+ class Part(CamelCaseModel):
8
+ content_type: str
9
+ version: int
10
+ text: str | None = None
11
+ unsuitable_for: list | None = None
12
+ locator: str | None = None
13
+
14
+
15
+ class Content(CamelCaseModel):
16
+ parts: list[Part]
17
+
18
+
19
+ class ContentPayload(CamelCaseModel):
20
+ content: Content
21
+
22
+
23
+ class Message(CamelCaseModel):
24
+ content_payload: ContentPayload | None = None
25
+ timestamp: datetime
26
+ last_update_timestamp: datetime
27
+ type: str
28
+ network_id: str
29
+ conversation_type: str
30
+ conversation_id: str
31
+ owner: int | None = None
32
+ sender: str
33
+ message_id: str
34
+ is_deleted: bool
35
+ is_server_updated: bool
36
+
37
+
38
+ class Conversation(CamelCaseModel):
39
+ timestamp: datetime
40
+ network_id: str
41
+ type: str
42
+ conversation_id: str
43
+ voice_id: str
44
+ participants: list[str]
45
+ read_horizon: str
46
+ delete_horizon: str
47
+ is_read: bool
48
+ muted: bool
49
+ folder: str
50
+ last_message: Message
51
+
52
+
53
+ class Primary(CamelCaseModel):
54
+ folder: str
55
+ total_count: int
56
+ unread_count: int
57
+ conversations: list[Conversation]
58
+
59
+
60
+ class SafetySettings(CamelCaseModel):
61
+ version: int
62
+ primary_inbox_media: str
63
+ primary_inbox_text: str
64
+ primary_inbox_url: str
65
+ secondary_inbox_media: str
66
+ secondary_inbox_text: str
67
+ secondary_inbox_url: str
68
+ can_unobscure: bool
69
+
70
+
71
+ class InboxResponse(CamelCaseModel):
72
+ primary: Primary
73
+ folders: list[Any]
74
+ safety_settings: SafetySettings
75
+
76
+
77
+ class ConversationResponse(CamelCaseModel):
78
+ timestamp: datetime
79
+ network_id: str
80
+ type: str
81
+ conversation_id: str
82
+ participants: list[str] | None = None
83
+ read_horizon: str
84
+ delete_horizon: str
85
+ is_read: bool
86
+ muted: bool
87
+ folder: str
88
+ messages: list[Message] | None = None
89
+ continuation_token: str | None = None
90
+ voice_id: str
91
+ voice_roster: list[Any] | None = None
92
+
93
+
94
+ class SendMessageResponse(CamelCaseModel):
95
+ message_id: str
96
+ conversation_id: str