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,140 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Profile
|
|
3
|
+
|
|
4
|
+
Get Userprofiles by XUID or Gamertag
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pythonxbox.api.provider.profile.models import ProfileResponse, ProfileSettings
|
|
8
|
+
from pythonxbox.api.provider.ratelimitedprovider import RateLimitedProvider
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ProfileProvider(RateLimitedProvider):
|
|
12
|
+
PROFILE_URL = "https://profile.xboxlive.com"
|
|
13
|
+
HEADERS_PROFILE = {"x-xbl-contract-version": "3"}
|
|
14
|
+
SEPARATOR = ","
|
|
15
|
+
|
|
16
|
+
RATE_LIMITS = {"burst": 10, "sustain": 30}
|
|
17
|
+
|
|
18
|
+
async def get_profiles(self, xuid_list: list[str], **kwargs) -> ProfileResponse:
|
|
19
|
+
"""
|
|
20
|
+
Get profile info for list of xuids
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
xuid_list (list): List of xuids
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
:class:`ProfileResponse`: Profile Response
|
|
27
|
+
"""
|
|
28
|
+
post_data = {
|
|
29
|
+
"settings": [
|
|
30
|
+
ProfileSettings.GAME_DISPLAY_NAME,
|
|
31
|
+
ProfileSettings.APP_DISPLAY_NAME,
|
|
32
|
+
ProfileSettings.APP_DISPLAYPIC_RAW,
|
|
33
|
+
ProfileSettings.GAMERSCORE,
|
|
34
|
+
ProfileSettings.GAMERTAG,
|
|
35
|
+
ProfileSettings.GAME_DISPLAYPIC_RAW,
|
|
36
|
+
ProfileSettings.ACCOUNT_TIER,
|
|
37
|
+
ProfileSettings.TENURE_LEVEL,
|
|
38
|
+
ProfileSettings.XBOX_ONE_REP,
|
|
39
|
+
ProfileSettings.PREFERRED_COLOR,
|
|
40
|
+
ProfileSettings.LOCATION,
|
|
41
|
+
ProfileSettings.BIOGRAPHY,
|
|
42
|
+
ProfileSettings.WATERMARKS,
|
|
43
|
+
ProfileSettings.REAL_NAME,
|
|
44
|
+
],
|
|
45
|
+
"userIds": xuid_list,
|
|
46
|
+
}
|
|
47
|
+
url = self.PROFILE_URL + "/users/batch/profile/settings"
|
|
48
|
+
resp = await self.client.session.post(
|
|
49
|
+
url,
|
|
50
|
+
json=post_data,
|
|
51
|
+
headers=self.HEADERS_PROFILE,
|
|
52
|
+
rate_limits=self.rate_limit_read,
|
|
53
|
+
**kwargs,
|
|
54
|
+
)
|
|
55
|
+
resp.raise_for_status()
|
|
56
|
+
return ProfileResponse(**resp.json())
|
|
57
|
+
|
|
58
|
+
async def get_profile_by_xuid(self, target_xuid: str, **kwargs) -> ProfileResponse:
|
|
59
|
+
"""
|
|
60
|
+
Get Userprofile by xuid
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
target_xuid: XUID to get profile for
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
:class:`ProfileResponse`: Profile Response
|
|
67
|
+
"""
|
|
68
|
+
url = self.PROFILE_URL + f"/users/xuid({target_xuid})/profile/settings"
|
|
69
|
+
params = {
|
|
70
|
+
"settings": self.SEPARATOR.join(
|
|
71
|
+
[
|
|
72
|
+
ProfileSettings.GAMERTAG,
|
|
73
|
+
ProfileSettings.MODERN_GAMERTAG,
|
|
74
|
+
ProfileSettings.MODERN_GAMERTAG_SUFFIX,
|
|
75
|
+
ProfileSettings.UNIQUE_MODERN_GAMERTAG,
|
|
76
|
+
ProfileSettings.REAL_NAME_OVERRIDE,
|
|
77
|
+
ProfileSettings.BIOGRAPHY,
|
|
78
|
+
ProfileSettings.LOCATION,
|
|
79
|
+
ProfileSettings.GAMERSCORE,
|
|
80
|
+
ProfileSettings.GAME_DISPLAYPIC_RAW,
|
|
81
|
+
ProfileSettings.TENURE_LEVEL,
|
|
82
|
+
ProfileSettings.ACCOUNT_TIER,
|
|
83
|
+
ProfileSettings.XBOX_ONE_REP,
|
|
84
|
+
ProfileSettings.PREFERRED_COLOR,
|
|
85
|
+
ProfileSettings.WATERMARKS,
|
|
86
|
+
ProfileSettings.IS_QUARANTINED,
|
|
87
|
+
]
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
resp = await self.client.session.get(
|
|
91
|
+
url,
|
|
92
|
+
params=params,
|
|
93
|
+
headers=self.HEADERS_PROFILE,
|
|
94
|
+
rate_limits=self.rate_limit_read,
|
|
95
|
+
**kwargs,
|
|
96
|
+
)
|
|
97
|
+
resp.raise_for_status()
|
|
98
|
+
return ProfileResponse(**resp.json())
|
|
99
|
+
|
|
100
|
+
async def get_profile_by_gamertag(self, gamertag: str, **kwargs) -> ProfileResponse:
|
|
101
|
+
"""
|
|
102
|
+
Get Userprofile by gamertag
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
gamertag: Gamertag to get profile for
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
:class:`ProfileResponse`: Profile Response
|
|
109
|
+
"""
|
|
110
|
+
url = self.PROFILE_URL + f"/users/gt({gamertag})/profile/settings"
|
|
111
|
+
params = {
|
|
112
|
+
"settings": self.SEPARATOR.join(
|
|
113
|
+
[
|
|
114
|
+
ProfileSettings.GAMERTAG,
|
|
115
|
+
ProfileSettings.MODERN_GAMERTAG,
|
|
116
|
+
ProfileSettings.MODERN_GAMERTAG_SUFFIX,
|
|
117
|
+
ProfileSettings.UNIQUE_MODERN_GAMERTAG,
|
|
118
|
+
ProfileSettings.REAL_NAME_OVERRIDE,
|
|
119
|
+
ProfileSettings.BIOGRAPHY,
|
|
120
|
+
ProfileSettings.LOCATION,
|
|
121
|
+
ProfileSettings.GAMERSCORE,
|
|
122
|
+
ProfileSettings.GAME_DISPLAYPIC_RAW,
|
|
123
|
+
ProfileSettings.TENURE_LEVEL,
|
|
124
|
+
ProfileSettings.ACCOUNT_TIER,
|
|
125
|
+
ProfileSettings.XBOX_ONE_REP,
|
|
126
|
+
ProfileSettings.PREFERRED_COLOR,
|
|
127
|
+
ProfileSettings.WATERMARKS,
|
|
128
|
+
ProfileSettings.IS_QUARANTINED,
|
|
129
|
+
]
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
resp = await self.client.session.get(
|
|
133
|
+
url,
|
|
134
|
+
params=params,
|
|
135
|
+
headers=self.HEADERS_PROFILE,
|
|
136
|
+
rate_limits=self.rate_limit_read,
|
|
137
|
+
**kwargs,
|
|
138
|
+
)
|
|
139
|
+
resp.raise_for_status()
|
|
140
|
+
return ProfileResponse(**resp.json())
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
from pythonxbox.common.models import CamelCaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ProfileSettings(str, Enum):
|
|
7
|
+
"""
|
|
8
|
+
Profile settings, used as parameter for Profile API
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
GAME_DISPLAY_NAME = "GameDisplayName"
|
|
12
|
+
APP_DISPLAY_NAME = "AppDisplayName"
|
|
13
|
+
APP_DISPLAYPIC_RAW = "AppDisplayPicRaw"
|
|
14
|
+
GAME_DISPLAYPIC_RAW = "GameDisplayPicRaw"
|
|
15
|
+
PUBLIC_GAMERPIC = "PublicGamerpic"
|
|
16
|
+
SHOW_USER_AS_AVATAR = "ShowUserAsAvatar"
|
|
17
|
+
GAMERSCORE = "Gamerscore"
|
|
18
|
+
GAMERTAG = "Gamertag"
|
|
19
|
+
MODERN_GAMERTAG = "ModernGamertag"
|
|
20
|
+
MODERN_GAMERTAG_SUFFIX = "ModernGamertagSuffix"
|
|
21
|
+
UNIQUE_MODERN_GAMERTAG = "UniqueModernGamertag"
|
|
22
|
+
ACCOUNT_TIER = "AccountTier"
|
|
23
|
+
TENURE_LEVEL = "TenureLevel"
|
|
24
|
+
XBOX_ONE_REP = "XboxOneRep"
|
|
25
|
+
PREFERRED_COLOR = "PreferredColor"
|
|
26
|
+
LOCATION = "Location"
|
|
27
|
+
BIOGRAPHY = "Bio"
|
|
28
|
+
WATERMARKS = "Watermarks"
|
|
29
|
+
REAL_NAME = "RealName"
|
|
30
|
+
REAL_NAME_OVERRIDE = "RealNameOverride"
|
|
31
|
+
IS_QUARANTINED = "IsQuarantined"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Setting(CamelCaseModel):
|
|
35
|
+
id: str
|
|
36
|
+
value: str
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ProfileUser(CamelCaseModel):
|
|
40
|
+
id: str
|
|
41
|
+
host_id: str
|
|
42
|
+
settings: list[Setting]
|
|
43
|
+
is_sponsored_user: bool
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ProfileResponse(CamelCaseModel):
|
|
47
|
+
profile_users: list[ProfileUser]
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""
|
|
2
|
+
RateLimitedProvider
|
|
3
|
+
|
|
4
|
+
Subclassed by providers with rate limit support
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
from pythonxbox.api.provider.baseprovider import BaseProvider
|
|
9
|
+
from pythonxbox.common.exceptions import XboxException
|
|
10
|
+
from pythonxbox.common.ratelimits import CombinedRateLimit
|
|
11
|
+
from pythonxbox.common.ratelimits.models import LimitType, ParsedRateLimit, TimePeriod
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from pythonxbox.api.client import XboxLiveClient
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class RateLimitedProvider(BaseProvider):
|
|
18
|
+
# dict -> Dict (typing.dict) https://stackoverflow.com/a/63460173
|
|
19
|
+
RATE_LIMITS: dict[str, int | dict[str, int]]
|
|
20
|
+
|
|
21
|
+
def __init__(self, client: "XboxLiveClient") -> None:
|
|
22
|
+
"""
|
|
23
|
+
Initialize Baseclass
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
client (:class:`XboxLiveClient`): Instance of XboxLiveClient
|
|
27
|
+
"""
|
|
28
|
+
super().__init__(client)
|
|
29
|
+
|
|
30
|
+
# Check that RATE_LIMITS set defined in the child class
|
|
31
|
+
if hasattr(self, "RATE_LIMITS"):
|
|
32
|
+
# Note: we cannot check (type(self.RATE_LIMITS) == dict) as the type hints have already defined it as such
|
|
33
|
+
if "burst" and "sustain" in self.RATE_LIMITS:
|
|
34
|
+
# We have the required keys, attempt to parse.
|
|
35
|
+
# (type-checking for the values is performed in __parse_rate_limit_key)
|
|
36
|
+
self.__handle_rate_limit_setup()
|
|
37
|
+
else:
|
|
38
|
+
raise XboxException(
|
|
39
|
+
"RATE_LIMITS object missing required keys 'burst', 'sustain'"
|
|
40
|
+
)
|
|
41
|
+
else:
|
|
42
|
+
raise XboxException(
|
|
43
|
+
"RateLimitedProvider as parent class but RATE_LIMITS not set!"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
def __handle_rate_limit_setup(self) -> None:
|
|
47
|
+
# Retrieve burst and sustain from the dict
|
|
48
|
+
burst_key = self.RATE_LIMITS["burst"]
|
|
49
|
+
sustain_key = self.RATE_LIMITS["sustain"]
|
|
50
|
+
|
|
51
|
+
# Parse the rate limit dict values
|
|
52
|
+
burst_rate_limits = self.__parse_rate_limit_key(burst_key, TimePeriod.BURST)
|
|
53
|
+
sustain_rate_limits = self.__parse_rate_limit_key(
|
|
54
|
+
sustain_key, TimePeriod.SUSTAIN
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Instanciate CombinedRateLimits for read and write respectively
|
|
58
|
+
self.rate_limit_read = CombinedRateLimit(
|
|
59
|
+
burst_rate_limits, sustain_rate_limits, type=LimitType.READ
|
|
60
|
+
)
|
|
61
|
+
self.rate_limit_write = CombinedRateLimit(
|
|
62
|
+
burst_rate_limits, sustain_rate_limits, type=LimitType.WRITE
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def __parse_rate_limit_key(
|
|
66
|
+
self, key: int | dict[str, int], period: TimePeriod
|
|
67
|
+
) -> ParsedRateLimit:
|
|
68
|
+
if isinstance(key, int) and not isinstance(key, bool):
|
|
69
|
+
# bool is a subclass of int, hence the explicit check
|
|
70
|
+
return ParsedRateLimit(read=key, write=key, period=period)
|
|
71
|
+
elif isinstance(key, dict):
|
|
72
|
+
# TODO: schema here?
|
|
73
|
+
# Since the key-value pairs match we can just pass the dict to the model
|
|
74
|
+
return ParsedRateLimit(**key, period=period)
|
|
75
|
+
# return ParsedRateLimit(read=key["read"], write=key["write"])
|
|
76
|
+
else:
|
|
77
|
+
raise XboxException(
|
|
78
|
+
"RATE_LIMITS value types not recognised. Must be one of 'int, 'dict'."
|
|
79
|
+
)
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Screenshots - Get screenshot info
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from pythonxbox.api.provider.baseprovider import BaseProvider
|
|
6
|
+
from pythonxbox.api.provider.screenshots.models import ScreenshotResponse
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ScreenshotsProvider(BaseProvider):
|
|
10
|
+
SCREENSHOTS_METADATA_URL = "https://screenshotsmetadata.xboxlive.com"
|
|
11
|
+
HEADERS_SCREENSHOTS_METADATA = {"x-xbl-contract-version": "5"}
|
|
12
|
+
|
|
13
|
+
async def get_recent_community_screenshots_by_title_id(
|
|
14
|
+
self, title_id: str, **kwargs
|
|
15
|
+
) -> ScreenshotResponse:
|
|
16
|
+
"""
|
|
17
|
+
Get recent community screenshots by Title Id
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
title_id: Title Id to get screenshots for
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
:class:`ScreenshotResponse`: Screenshot Response
|
|
24
|
+
"""
|
|
25
|
+
url = self.SCREENSHOTS_METADATA_URL + f"/public/titles/{title_id}/screenshots"
|
|
26
|
+
params = {"qualifier": "created"}
|
|
27
|
+
resp = await self.client.session.get(
|
|
28
|
+
url, params=params, headers=self.HEADERS_SCREENSHOTS_METADATA, **kwargs
|
|
29
|
+
)
|
|
30
|
+
resp.raise_for_status()
|
|
31
|
+
return ScreenshotResponse(**resp.json())
|
|
32
|
+
|
|
33
|
+
async def get_recent_own_screenshots(
|
|
34
|
+
self, title_id: str = None, skip_items: int = 0, max_items: int = 25, **kwargs
|
|
35
|
+
) -> ScreenshotResponse:
|
|
36
|
+
"""
|
|
37
|
+
Get own recent screenshots, 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:`ScreenshotResponse`: Screenshot Response
|
|
46
|
+
"""
|
|
47
|
+
url = self.SCREENSHOTS_METADATA_URL + "/users/me"
|
|
48
|
+
if title_id:
|
|
49
|
+
url += f"/titles/{title_id}"
|
|
50
|
+
url += "/screenshots"
|
|
51
|
+
|
|
52
|
+
params = {"skipItems": skip_items, "maxItems": max_items}
|
|
53
|
+
resp = await self.client.session.get(
|
|
54
|
+
url, params=params, headers=self.HEADERS_SCREENSHOTS_METADATA, **kwargs
|
|
55
|
+
)
|
|
56
|
+
resp.raise_for_status()
|
|
57
|
+
return ScreenshotResponse(**resp.json())
|
|
58
|
+
|
|
59
|
+
async def get_recent_screenshots_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
|
+
) -> ScreenshotResponse:
|
|
67
|
+
"""
|
|
68
|
+
Get recent screenshots by XUID, optionally filter for title Id
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
xuid: XUID of user to get screenshots 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:`ScreenshotResponse`: Screenshot Response
|
|
78
|
+
"""
|
|
79
|
+
url = self.SCREENSHOTS_METADATA_URL + f"/users/xuid({xuid})"
|
|
80
|
+
if title_id:
|
|
81
|
+
url += f"/titles/{title_id}"
|
|
82
|
+
url += "/screenshots"
|
|
83
|
+
|
|
84
|
+
params = {"skipItems": skip_items, "maxItems": max_items}
|
|
85
|
+
resp = await self.client.session.get(
|
|
86
|
+
url, params=params, headers=self.HEADERS_SCREENSHOTS_METADATA, **kwargs
|
|
87
|
+
)
|
|
88
|
+
resp.raise_for_status()
|
|
89
|
+
return ScreenshotResponse(**resp.json())
|
|
90
|
+
|
|
91
|
+
async def get_saved_community_screenshots_by_title_id(
|
|
92
|
+
self, title_id: str, **kwargs
|
|
93
|
+
) -> ScreenshotResponse:
|
|
94
|
+
"""
|
|
95
|
+
Get saved community screenshots by Title Id
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
title_id: Title Id to get screenshots for
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
:class:`ScreenshotResponse`: Screenshot Response
|
|
102
|
+
"""
|
|
103
|
+
url = f"{self.SCREENSHOTS_METADATA_URL}/public/titles/{title_id}/screenshots/saved"
|
|
104
|
+
params = {"qualifier": "created"}
|
|
105
|
+
resp = await self.client.session.get(
|
|
106
|
+
url, params=params, headers=self.HEADERS_SCREENSHOTS_METADATA, **kwargs
|
|
107
|
+
)
|
|
108
|
+
resp.raise_for_status()
|
|
109
|
+
return ScreenshotResponse(**resp.json())
|
|
110
|
+
|
|
111
|
+
async def get_saved_own_screenshots(
|
|
112
|
+
self, title_id: str = None, skip_items: int = 0, max_items: int = 25, **kwargs
|
|
113
|
+
) -> ScreenshotResponse:
|
|
114
|
+
"""
|
|
115
|
+
Get own saved screenshots, 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:`ScreenshotResponse`: Screenshot Response
|
|
124
|
+
"""
|
|
125
|
+
url = self.SCREENSHOTS_METADATA_URL + "/users/me"
|
|
126
|
+
if title_id:
|
|
127
|
+
url += f"/titles/{title_id}"
|
|
128
|
+
url += "/screenshots/saved"
|
|
129
|
+
|
|
130
|
+
params = {"skipItems": skip_items, "maxItems": max_items}
|
|
131
|
+
resp = await self.client.session.get(
|
|
132
|
+
url, params=params, headers=self.HEADERS_SCREENSHOTS_METADATA, **kwargs
|
|
133
|
+
)
|
|
134
|
+
resp.raise_for_status()
|
|
135
|
+
return ScreenshotResponse(**resp.json())
|
|
136
|
+
|
|
137
|
+
async def get_saved_screenshots_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
|
+
) -> ScreenshotResponse:
|
|
145
|
+
"""
|
|
146
|
+
Get saved screenshots 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:`ScreenshotResponse`: Screenshot Response
|
|
156
|
+
"""
|
|
157
|
+
url = self.SCREENSHOTS_METADATA_URL + f"/users/xuid({xuid})"
|
|
158
|
+
if title_id:
|
|
159
|
+
url += f"/titles/{title_id}"
|
|
160
|
+
url += "/screenshots/saved"
|
|
161
|
+
|
|
162
|
+
params = {"skipItems": skip_items, "maxItems": max_items}
|
|
163
|
+
resp = await self.client.session.get(
|
|
164
|
+
url, params=params, headers=self.HEADERS_SCREENSHOTS_METADATA, **kwargs
|
|
165
|
+
)
|
|
166
|
+
resp.raise_for_status()
|
|
167
|
+
return ScreenshotResponse(**resp.json())
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from pythonxbox.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 ScreenshotUri(CamelCaseModel):
|
|
14
|
+
uri: str
|
|
15
|
+
file_size: int
|
|
16
|
+
uri_type: int
|
|
17
|
+
expiration: datetime
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Screenshot(CamelCaseModel):
|
|
21
|
+
screenshot_id: str
|
|
22
|
+
resolution_height: int
|
|
23
|
+
resolution_width: int
|
|
24
|
+
state: int
|
|
25
|
+
date_published: datetime
|
|
26
|
+
date_taken: datetime
|
|
27
|
+
last_modified: datetime
|
|
28
|
+
user_caption: str
|
|
29
|
+
type: int
|
|
30
|
+
scid: str
|
|
31
|
+
title_id: int
|
|
32
|
+
rating: float
|
|
33
|
+
rating_count: int
|
|
34
|
+
views: int
|
|
35
|
+
title_data: str
|
|
36
|
+
system_properties: str
|
|
37
|
+
saved_by_user: bool
|
|
38
|
+
achievement_id: str
|
|
39
|
+
greatest_moment_id: Any = None
|
|
40
|
+
thumbnails: list[Thumbnail]
|
|
41
|
+
screenshot_uris: list[ScreenshotUri]
|
|
42
|
+
xuid: str
|
|
43
|
+
screenshot_name: str
|
|
44
|
+
title_name: str
|
|
45
|
+
screenshot_locale: str
|
|
46
|
+
screenshot_content_attributes: int
|
|
47
|
+
device_type: str
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class PagingInfo(CamelCaseModel):
|
|
51
|
+
continuation_token: str | None = None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ScreenshotResponse(CamelCaseModel):
|
|
55
|
+
screenshots: list[Screenshot]
|
|
56
|
+
paging_info: PagingInfo
|