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.
- python_xbox-0.1.0.dist-info/METADATA +217 -0
- python_xbox-0.1.0.dist-info/RECORD +64 -0
- python_xbox-0.1.0.dist-info/WHEEL +4 -0
- python_xbox-0.1.0.dist-info/entry_points.txt +6 -0
- python_xbox-0.1.0.dist-info/licenses/LICENSE +20 -0
- pythonxbox/__init__.py +4 -0
- pythonxbox/api/__init__.py +0 -0
- pythonxbox/api/client.py +166 -0
- pythonxbox/api/language.py +76 -0
- pythonxbox/api/provider/__init__.py +0 -0
- pythonxbox/api/provider/account/__init__.py +73 -0
- pythonxbox/api/provider/account/models.py +11 -0
- pythonxbox/api/provider/achievements/__init__.py +164 -0
- pythonxbox/api/provider/achievements/models.py +133 -0
- pythonxbox/api/provider/baseprovider.py +22 -0
- pythonxbox/api/provider/catalog/__init__.py +86 -0
- pythonxbox/api/provider/catalog/const.py +15 -0
- pythonxbox/api/provider/catalog/models.py +428 -0
- pythonxbox/api/provider/cqs/__init__.py +85 -0
- pythonxbox/api/provider/cqs/models.py +59 -0
- pythonxbox/api/provider/gameclips/__init__.py +167 -0
- pythonxbox/api/provider/gameclips/models.py +58 -0
- pythonxbox/api/provider/lists/__init__.py +71 -0
- pythonxbox/api/provider/lists/models.py +33 -0
- pythonxbox/api/provider/mediahub/__init__.py +64 -0
- pythonxbox/api/provider/mediahub/models.py +82 -0
- pythonxbox/api/provider/message/__init__.py +135 -0
- pythonxbox/api/provider/message/models.py +96 -0
- pythonxbox/api/provider/people/__init__.py +193 -0
- pythonxbox/api/provider/people/models.py +252 -0
- pythonxbox/api/provider/presence/__init__.py +110 -0
- pythonxbox/api/provider/presence/models.py +53 -0
- pythonxbox/api/provider/profile/__init__.py +140 -0
- pythonxbox/api/provider/profile/models.py +47 -0
- pythonxbox/api/provider/ratelimitedprovider.py +79 -0
- pythonxbox/api/provider/screenshots/__init__.py +167 -0
- pythonxbox/api/provider/screenshots/models.py +56 -0
- pythonxbox/api/provider/smartglass/__init__.py +402 -0
- pythonxbox/api/provider/smartglass/models.py +186 -0
- pythonxbox/api/provider/titlehub/__init__.py +143 -0
- pythonxbox/api/provider/titlehub/models.py +106 -0
- pythonxbox/api/provider/usersearch/__init__.py +29 -0
- pythonxbox/api/provider/usersearch/models.py +17 -0
- pythonxbox/api/provider/userstats/__init__.py +164 -0
- pythonxbox/api/provider/userstats/models.py +44 -0
- pythonxbox/authentication/__init__.py +0 -0
- pythonxbox/authentication/manager.py +161 -0
- pythonxbox/authentication/models.py +162 -0
- pythonxbox/authentication/xal.py +348 -0
- pythonxbox/common/__init__.py +0 -0
- pythonxbox/common/exceptions.py +59 -0
- pythonxbox/common/filetimes.py +81 -0
- pythonxbox/common/models.py +34 -0
- pythonxbox/common/ratelimits/__init__.py +268 -0
- pythonxbox/common/ratelimits/models.py +23 -0
- pythonxbox/common/request_signer.py +190 -0
- pythonxbox/common/signed_session.py +60 -0
- pythonxbox/py.typed +0 -0
- pythonxbox/scripts/__init__.py +15 -0
- pythonxbox/scripts/authenticate.py +159 -0
- pythonxbox/scripts/change_gamertag.py +111 -0
- pythonxbox/scripts/friends.py +80 -0
- pythonxbox/scripts/search.py +43 -0
- 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]
|