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.
- python_xbox-0.1.0rc0.dist-info/METADATA +213 -0
- python_xbox-0.1.0rc0.dist-info/RECORD +63 -0
- python_xbox-0.1.0rc0.dist-info/WHEEL +4 -0
- python_xbox-0.1.0rc0.dist-info/entry_points.txt +6 -0
- python_xbox-0.1.0rc0.dist-info/licenses/LICENSE +19 -0
- webapi/__init__.py +4 -0
- webapi/api/__init__.py +0 -0
- webapi/api/client.py +166 -0
- webapi/api/language.py +70 -0
- webapi/api/provider/__init__.py +0 -0
- webapi/api/provider/account/__init__.py +71 -0
- webapi/api/provider/account/models.py +11 -0
- webapi/api/provider/achievements/__init__.py +164 -0
- webapi/api/provider/achievements/models.py +133 -0
- webapi/api/provider/baseprovider.py +16 -0
- webapi/api/provider/catalog/__init__.py +88 -0
- webapi/api/provider/catalog/const.py +15 -0
- webapi/api/provider/catalog/models.py +428 -0
- webapi/api/provider/cqs/__init__.py +85 -0
- webapi/api/provider/cqs/models.py +59 -0
- webapi/api/provider/gameclips/__init__.py +167 -0
- webapi/api/provider/gameclips/models.py +59 -0
- webapi/api/provider/lists/__init__.py +71 -0
- webapi/api/provider/lists/models.py +35 -0
- webapi/api/provider/mediahub/__init__.py +64 -0
- webapi/api/provider/mediahub/models.py +84 -0
- webapi/api/provider/message/__init__.py +135 -0
- webapi/api/provider/message/models.py +96 -0
- webapi/api/provider/people/__init__.py +193 -0
- webapi/api/provider/people/models.py +252 -0
- webapi/api/provider/presence/__init__.py +112 -0
- webapi/api/provider/presence/models.py +54 -0
- webapi/api/provider/profile/__init__.py +142 -0
- webapi/api/provider/profile/models.py +48 -0
- webapi/api/provider/ratelimitedprovider.py +77 -0
- webapi/api/provider/screenshots/__init__.py +167 -0
- webapi/api/provider/screenshots/models.py +56 -0
- webapi/api/provider/smartglass/__init__.py +399 -0
- webapi/api/provider/smartglass/models.py +187 -0
- webapi/api/provider/titlehub/__init__.py +141 -0
- webapi/api/provider/titlehub/models.py +106 -0
- webapi/api/provider/usersearch/__init__.py +29 -0
- webapi/api/provider/usersearch/models.py +19 -0
- webapi/api/provider/userstats/__init__.py +166 -0
- webapi/api/provider/userstats/models.py +46 -0
- webapi/authentication/__init__.py +0 -0
- webapi/authentication/manager.py +162 -0
- webapi/authentication/models.py +163 -0
- webapi/authentication/xal.py +348 -0
- webapi/common/__init__.py +0 -0
- webapi/common/exceptions.py +57 -0
- webapi/common/filetimes.py +97 -0
- webapi/common/models.py +34 -0
- webapi/common/ratelimits/__init__.py +269 -0
- webapi/common/ratelimits/models.py +23 -0
- webapi/common/request_signer.py +189 -0
- webapi/common/signed_session.py +54 -0
- webapi/scripts/__init__.py +15 -0
- webapi/scripts/authenticate.py +159 -0
- webapi/scripts/change_gamertag.py +111 -0
- webapi/scripts/friends.py +80 -0
- webapi/scripts/search.py +43 -0
- webapi/scripts/xal.py +113 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Any, List, Optional
|
|
3
|
+
|
|
4
|
+
from xbox.webapi.common.models import CamelCaseModel
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Part(CamelCaseModel):
|
|
8
|
+
content_type: str
|
|
9
|
+
version: int
|
|
10
|
+
text: Optional[str] = None
|
|
11
|
+
unsuitable_for: Optional[List] = None
|
|
12
|
+
locator: Optional[str] = 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: Optional[ContentPayload] = 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: Optional[int] = 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: Optional[List[str]] = None
|
|
83
|
+
read_horizon: str
|
|
84
|
+
delete_horizon: str
|
|
85
|
+
is_read: bool
|
|
86
|
+
muted: bool
|
|
87
|
+
folder: str
|
|
88
|
+
messages: Optional[List[Message]] = None
|
|
89
|
+
continuation_token: Optional[str] = None
|
|
90
|
+
voice_id: str
|
|
91
|
+
voice_roster: Optional[List[Any]] = None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class SendMessageResponse(CamelCaseModel):
|
|
95
|
+
message_id: str
|
|
96
|
+
conversation_id: str
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""
|
|
2
|
+
People - Access friendlist from own profiles and others
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from xbox.webapi.api.provider.people.models import (
|
|
6
|
+
PeopleDecoration,
|
|
7
|
+
PeopleResponse,
|
|
8
|
+
PeopleSummaryResponse,
|
|
9
|
+
)
|
|
10
|
+
from xbox.webapi.api.provider.ratelimitedprovider import RateLimitedProvider
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from xbox.webapi.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 xbox.webapi.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,112 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Presence - Get online status of friends
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
from xbox.webapi.api.provider.baseprovider import BaseProvider
|
|
8
|
+
from xbox.webapi.api.provider.presence.models import (
|
|
9
|
+
PresenceBatchResponse,
|
|
10
|
+
PresenceItem,
|
|
11
|
+
PresenceLevel,
|
|
12
|
+
PresenceState,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PresenceProvider(BaseProvider):
|
|
17
|
+
PRESENCE_URL = "https://userpresence.xboxlive.com"
|
|
18
|
+
HEADERS_PRESENCE = {"x-xbl-contract-version": "3", "Accept": "application/json"}
|
|
19
|
+
|
|
20
|
+
async def get_presence(
|
|
21
|
+
self,
|
|
22
|
+
xuid,
|
|
23
|
+
presence_level: PresenceLevel = PresenceLevel.USER,
|
|
24
|
+
**kwargs,
|
|
25
|
+
) -> PresenceItem:
|
|
26
|
+
"""
|
|
27
|
+
Get presence for given xuid
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
xuid: XUID
|
|
31
|
+
presence_level: Filter level
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
:class:`PresenceItem`: Presence Response
|
|
35
|
+
"""
|
|
36
|
+
url = self.PRESENCE_URL + "/users/xuid(" + xuid + ")?level=" + presence_level
|
|
37
|
+
|
|
38
|
+
resp = await self.client.session.get(
|
|
39
|
+
url, headers=self.HEADERS_PRESENCE, **kwargs
|
|
40
|
+
)
|
|
41
|
+
resp.raise_for_status()
|
|
42
|
+
return PresenceItem(**resp.json())
|
|
43
|
+
|
|
44
|
+
async def get_presence_batch(
|
|
45
|
+
self,
|
|
46
|
+
xuids: List[str],
|
|
47
|
+
online_only: bool = False,
|
|
48
|
+
presence_level: PresenceLevel = PresenceLevel.USER,
|
|
49
|
+
**kwargs,
|
|
50
|
+
) -> List[PresenceItem]:
|
|
51
|
+
"""
|
|
52
|
+
Get presence for list of xuids
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
xuids: List of XUIDs
|
|
56
|
+
online_only: Only get online profiles
|
|
57
|
+
presence_level: Filter level
|
|
58
|
+
|
|
59
|
+
Returns: List[:class:`PresenceItem`]: List of presence items
|
|
60
|
+
"""
|
|
61
|
+
if len(xuids) > 1100:
|
|
62
|
+
raise Exception("Xuid list length is > 1100")
|
|
63
|
+
|
|
64
|
+
url = self.PRESENCE_URL + "/users/batch"
|
|
65
|
+
post_data = {
|
|
66
|
+
"users": [str(x) for x in xuids],
|
|
67
|
+
"onlineOnly": online_only,
|
|
68
|
+
"level": presence_level,
|
|
69
|
+
}
|
|
70
|
+
resp = await self.client.session.post(
|
|
71
|
+
url, json=post_data, headers=self.HEADERS_PRESENCE, **kwargs
|
|
72
|
+
)
|
|
73
|
+
resp.raise_for_status()
|
|
74
|
+
parsed = PresenceBatchResponse.model_validate(resp.json())
|
|
75
|
+
return parsed.root
|
|
76
|
+
|
|
77
|
+
async def get_presence_own(
|
|
78
|
+
self, presence_level: PresenceLevel = PresenceLevel.ALL, **kwargs
|
|
79
|
+
) -> PresenceItem:
|
|
80
|
+
"""
|
|
81
|
+
Get presence of own profile
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
presence_level: Filter level
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
:class:`PresenceItem`: Presence Response
|
|
88
|
+
"""
|
|
89
|
+
url = self.PRESENCE_URL + "/users/me"
|
|
90
|
+
params = {"level": presence_level}
|
|
91
|
+
resp = await self.client.session.get(
|
|
92
|
+
url, params=params, headers=self.HEADERS_PRESENCE, **kwargs
|
|
93
|
+
)
|
|
94
|
+
resp.raise_for_status()
|
|
95
|
+
return PresenceItem(**resp.json())
|
|
96
|
+
|
|
97
|
+
async def set_presence_own(self, presence_state: PresenceState, **kwargs) -> bool:
|
|
98
|
+
"""
|
|
99
|
+
Set presence of own profile
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
presence_state: State of presence
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
`True` on success, `False` otherwise
|
|
106
|
+
"""
|
|
107
|
+
url = self.PRESENCE_URL + f"/users/xuid({self.client.xuid})/state"
|
|
108
|
+
data = {"state": presence_state.value}
|
|
109
|
+
resp = await self.client.session.put(
|
|
110
|
+
url, json=data, headers=self.HEADERS_PRESENCE, **kwargs
|
|
111
|
+
)
|
|
112
|
+
return resp.status_code == 200
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
from pydantic import RootModel
|
|
4
|
+
|
|
5
|
+
from xbox.webapi.common.models import CamelCaseModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PresenceLevel(str, Enum):
|
|
9
|
+
USER = "user"
|
|
10
|
+
DEVICE = "device"
|
|
11
|
+
TITLE = "title"
|
|
12
|
+
ALL = "all"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PresenceState(str, Enum):
|
|
16
|
+
ACTIVE = "Active"
|
|
17
|
+
CLOAKED = "Cloaked"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class LastSeen(CamelCaseModel):
|
|
21
|
+
device_type: str
|
|
22
|
+
title_id: Optional[str] = None
|
|
23
|
+
title_name: str
|
|
24
|
+
timestamp: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ActivityRecord(CamelCaseModel):
|
|
28
|
+
richPresence: Optional[str] = None
|
|
29
|
+
media: Optional[str] = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TitleRecord(CamelCaseModel):
|
|
33
|
+
id: Optional[str] = None
|
|
34
|
+
name: Optional[str] = None
|
|
35
|
+
activity: Optional[List[ActivityRecord]] = None
|
|
36
|
+
lastModified: Optional[str] = None
|
|
37
|
+
placement: Optional[str] = None
|
|
38
|
+
state: Optional[str] = None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class DeviceRecord(CamelCaseModel):
|
|
42
|
+
titles: Optional[List[TitleRecord]] = None
|
|
43
|
+
type: Optional[str] = None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class PresenceItem(CamelCaseModel):
|
|
47
|
+
xuid: str
|
|
48
|
+
state: str
|
|
49
|
+
last_seen: Optional[LastSeen] = None
|
|
50
|
+
devices: Optional[List[DeviceRecord]] = None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class PresenceBatchResponse(RootModel[List[PresenceItem]], CamelCaseModel):
|
|
54
|
+
root: List[PresenceItem]
|