python-xbox 0.1.0rc0__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 (63) hide show
  1. python_xbox-0.1.0rc0.dist-info/METADATA +213 -0
  2. python_xbox-0.1.0rc0.dist-info/RECORD +63 -0
  3. python_xbox-0.1.0rc0.dist-info/WHEEL +4 -0
  4. python_xbox-0.1.0rc0.dist-info/entry_points.txt +6 -0
  5. python_xbox-0.1.0rc0.dist-info/licenses/LICENSE +19 -0
  6. webapi/__init__.py +4 -0
  7. webapi/api/__init__.py +0 -0
  8. webapi/api/client.py +166 -0
  9. webapi/api/language.py +70 -0
  10. webapi/api/provider/__init__.py +0 -0
  11. webapi/api/provider/account/__init__.py +71 -0
  12. webapi/api/provider/account/models.py +11 -0
  13. webapi/api/provider/achievements/__init__.py +164 -0
  14. webapi/api/provider/achievements/models.py +133 -0
  15. webapi/api/provider/baseprovider.py +16 -0
  16. webapi/api/provider/catalog/__init__.py +88 -0
  17. webapi/api/provider/catalog/const.py +15 -0
  18. webapi/api/provider/catalog/models.py +428 -0
  19. webapi/api/provider/cqs/__init__.py +85 -0
  20. webapi/api/provider/cqs/models.py +59 -0
  21. webapi/api/provider/gameclips/__init__.py +167 -0
  22. webapi/api/provider/gameclips/models.py +59 -0
  23. webapi/api/provider/lists/__init__.py +71 -0
  24. webapi/api/provider/lists/models.py +35 -0
  25. webapi/api/provider/mediahub/__init__.py +64 -0
  26. webapi/api/provider/mediahub/models.py +84 -0
  27. webapi/api/provider/message/__init__.py +135 -0
  28. webapi/api/provider/message/models.py +96 -0
  29. webapi/api/provider/people/__init__.py +193 -0
  30. webapi/api/provider/people/models.py +252 -0
  31. webapi/api/provider/presence/__init__.py +112 -0
  32. webapi/api/provider/presence/models.py +54 -0
  33. webapi/api/provider/profile/__init__.py +142 -0
  34. webapi/api/provider/profile/models.py +48 -0
  35. webapi/api/provider/ratelimitedprovider.py +77 -0
  36. webapi/api/provider/screenshots/__init__.py +167 -0
  37. webapi/api/provider/screenshots/models.py +56 -0
  38. webapi/api/provider/smartglass/__init__.py +399 -0
  39. webapi/api/provider/smartglass/models.py +187 -0
  40. webapi/api/provider/titlehub/__init__.py +141 -0
  41. webapi/api/provider/titlehub/models.py +106 -0
  42. webapi/api/provider/usersearch/__init__.py +29 -0
  43. webapi/api/provider/usersearch/models.py +19 -0
  44. webapi/api/provider/userstats/__init__.py +166 -0
  45. webapi/api/provider/userstats/models.py +46 -0
  46. webapi/authentication/__init__.py +0 -0
  47. webapi/authentication/manager.py +162 -0
  48. webapi/authentication/models.py +163 -0
  49. webapi/authentication/xal.py +348 -0
  50. webapi/common/__init__.py +0 -0
  51. webapi/common/exceptions.py +57 -0
  52. webapi/common/filetimes.py +97 -0
  53. webapi/common/models.py +34 -0
  54. webapi/common/ratelimits/__init__.py +269 -0
  55. webapi/common/ratelimits/models.py +23 -0
  56. webapi/common/request_signer.py +189 -0
  57. webapi/common/signed_session.py +54 -0
  58. webapi/scripts/__init__.py +15 -0
  59. webapi/scripts/authenticate.py +159 -0
  60. webapi/scripts/change_gamertag.py +111 -0
  61. webapi/scripts/friends.py +80 -0
  62. webapi/scripts/search.py +43 -0
  63. webapi/scripts/xal.py +113 -0
@@ -0,0 +1,167 @@
1
+ """
2
+ Gameclips - Get gameclip info
3
+ """
4
+
5
+ from xbox.webapi.api.provider.baseprovider import BaseProvider
6
+ from xbox.webapi.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,59 @@
1
+ from datetime import datetime
2
+ from typing import List, Optional
3
+
4
+ from xbox.webapi.common.models import CamelCaseModel
5
+
6
+
7
+ class Thumbnail(CamelCaseModel):
8
+ uri: str
9
+ file_size: int
10
+ thumbnail_type: int
11
+
12
+
13
+ class GameClipUri(CamelCaseModel):
14
+ uri: str
15
+ file_size: int
16
+ uri_type: int
17
+ expiration: str
18
+
19
+
20
+ class GameClip(CamelCaseModel):
21
+ game_clip_id: str
22
+ state: int
23
+ date_published: datetime
24
+ date_recorded: datetime
25
+ last_modified: datetime
26
+ user_caption: str
27
+ type: int
28
+ duration_in_seconds: int
29
+ scid: str
30
+ title_id: int
31
+ rating: float
32
+ rating_count: int
33
+ views: int
34
+ title_data: str
35
+ system_properties: str
36
+ saved_by_user: bool
37
+ achievement_id: str
38
+ greatest_moment_id: str
39
+ thumbnails: List[Thumbnail]
40
+ game_clip_uris: List[GameClipUri]
41
+ xuid: str
42
+ clip_name: str
43
+ title_name: str
44
+ game_clip_locale: str
45
+ clip_content_attributes: int
46
+ device_type: str
47
+ comment_count: int
48
+ like_count: int
49
+ share_count: int
50
+ partial_views: int
51
+
52
+
53
+ class PagingInfo(CamelCaseModel):
54
+ continuation_token: Optional[str] = None
55
+
56
+
57
+ class GameclipsResponse(CamelCaseModel):
58
+ game_clips: List[GameClip]
59
+ paging_info: PagingInfo
@@ -0,0 +1,71 @@
1
+ """
2
+ EPLists - Mainly used for XBL Pins
3
+ """
4
+
5
+ from xbox.webapi.api.provider.baseprovider import BaseProvider
6
+ from xbox.webapi.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,35 @@
1
+ from typing import List, Optional
2
+
3
+ from xbox.webapi.common.models import PascalCaseModel
4
+
5
+
6
+ class Item(PascalCaseModel):
7
+ item_id: str
8
+ content_type: str
9
+ title: Optional[str] = None
10
+ device_type: str
11
+ provider: Optional[str] = None
12
+ provider_id: Optional[str] = None
13
+
14
+
15
+ class ListItem(PascalCaseModel):
16
+ date_added: str
17
+ date_modified: str
18
+ index: int
19
+ k_value: int
20
+ item: Item
21
+
22
+
23
+ class ListMetadata(PascalCaseModel):
24
+ list_title: str
25
+ list_version: int
26
+ list_count: int
27
+ allow_duplicates: bool
28
+ max_list_size: int
29
+ access_setting: str
30
+
31
+
32
+ class ListsResponse(PascalCaseModel):
33
+ impression_id: str
34
+ list_items: List[ListItem]
35
+ list_metadata: ListMetadata
@@ -0,0 +1,64 @@
1
+ """
2
+ Mediahub - Fetch screenshots and gameclips
3
+ """
4
+
5
+ from xbox.webapi.api.provider.baseprovider import BaseProvider
6
+ from xbox.webapi.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,84 @@
1
+ from typing import List, Optional
2
+
3
+ from xbox.webapi.common.models import CamelCaseModel
4
+
5
+
6
+ class ContentSegment(CamelCaseModel):
7
+ segment_id: int
8
+ creation_type: str
9
+ creator_channel_id: Optional[str] = None
10
+ creator_xuid: int
11
+ record_date: str
12
+ duration_in_seconds: int
13
+ offset: int
14
+ secondary_title_id: Optional[int] = None
15
+ title_id: int
16
+
17
+
18
+ class ContentLocator(CamelCaseModel):
19
+ expiration: Optional[str] = None
20
+ file_size: Optional[int] = None
21
+ locator_type: str
22
+ uri: str
23
+
24
+
25
+ class GameclipContent(CamelCaseModel):
26
+ content_id: str
27
+ content_locators: List[ContentLocator]
28
+ content_segments: List[ContentSegment]
29
+ creation_type: str
30
+ duration_in_seconds: int
31
+ local_id: str
32
+ owner_xuid: int
33
+ sandbox_id: str
34
+ shared_to: List[int]
35
+ title_id: int
36
+ title_name: str
37
+ upload_date: str
38
+ upload_language: str
39
+ upload_region: str
40
+ upload_title_id: int
41
+ upload_device_type: str
42
+ comment_count: int
43
+ like_count: int
44
+ share_count: int
45
+ view_count: int
46
+ content_state: str
47
+ enforcement_state: str
48
+ sessions: List[str]
49
+ tournaments: List[str]
50
+
51
+
52
+ class MediahubGameclips(CamelCaseModel):
53
+ values: List[GameclipContent]
54
+
55
+
56
+ class ScreenshotContent(CamelCaseModel):
57
+ content_id: str
58
+ capture_date: str
59
+ content_locators: List[ContentLocator]
60
+ local_id: str
61
+ owner_xuid: int
62
+ resolution_height: int
63
+ resolution_width: int
64
+ date_uploaded: str
65
+ sandbox_id: str
66
+ shared_to: List[int]
67
+ title_id: int
68
+ title_name: str
69
+ upload_language: str
70
+ upload_region: str
71
+ upload_title_id: int
72
+ upload_device_type: str
73
+ comment_count: int
74
+ like_count: int
75
+ share_count: int
76
+ view_count: int
77
+ content_state: str
78
+ enforcement_state: str
79
+ sessions: List[str]
80
+ tournaments: List[str]
81
+
82
+
83
+ class MediahubScreenshots(CamelCaseModel):
84
+ values: List[ScreenshotContent]
@@ -0,0 +1,135 @@
1
+ """
2
+ Message - Read and send messages
3
+
4
+ TODO: Support group messaging
5
+ """
6
+
7
+ from xbox.webapi.api.provider.baseprovider import BaseProvider
8
+ from xbox.webapi.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())