python-xbox 0.1.3__tar.gz → 0.2.1__tar.gz
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.3 → python_xbox-0.2.1}/PKG-INFO +3 -4
- {python_xbox-0.1.3 → python_xbox-0.2.1}/README.md +0 -1
- {python_xbox-0.1.3 → python_xbox-0.2.1}/pyproject.toml +10 -10
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/__init__.py +1 -1
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/people/__init__.py +32 -2
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/smartglass/models.py +2 -2
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/common/request_signer.py +33 -16
- {python_xbox-0.1.3 → python_xbox-0.2.1}/.gitignore +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/LICENSE +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/__init__.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/client.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/language.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/__init__.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/account/__init__.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/account/models.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/achievements/__init__.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/achievements/models.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/baseprovider.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/catalog/__init__.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/catalog/const.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/catalog/models.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/cqs/__init__.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/cqs/models.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/gameclips/__init__.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/gameclips/models.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/lists/__init__.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/lists/models.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/mediahub/__init__.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/mediahub/models.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/message/__init__.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/message/models.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/people/models.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/presence/__init__.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/presence/models.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/profile/__init__.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/profile/models.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/ratelimitedprovider.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/screenshots/__init__.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/screenshots/models.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/smartglass/__init__.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/titlehub/__init__.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/titlehub/models.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/usersearch/__init__.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/usersearch/models.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/userstats/__init__.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/userstats/models.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/authentication/__init__.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/authentication/manager.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/authentication/models.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/authentication/xal.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/common/__init__.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/common/exceptions.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/common/filetimes.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/common/models.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/common/ratelimits/__init__.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/common/ratelimits/models.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/common/signed_session.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/py.typed +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/scripts/__init__.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/scripts/authenticate.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/scripts/change_gamertag.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/scripts/friends.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/scripts/search.py +0 -0
- {python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/scripts/xal.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: python-xbox
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: A library to authenticate with Xbox Network and use their API
|
|
5
5
|
Project-URL: Source, https://github.com/tr4nt0r/python-xbox
|
|
6
6
|
Author-email: Manfred Dennerlein Rodelo <manfred@dennerlein.name>
|
|
@@ -18,7 +18,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.14
|
|
19
19
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
20
|
Requires-Python: >=3.11
|
|
21
|
-
Requires-Dist:
|
|
21
|
+
Requires-Dist: cryptography>=42.0
|
|
22
22
|
Requires-Dist: httpx>=0.25.1
|
|
23
23
|
Requires-Dist: ms-cv>=0.1.1
|
|
24
24
|
Requires-Dist: pydantic>=2.12
|
|
@@ -32,7 +32,6 @@ Description-Content-Type: text/markdown
|
|
|
32
32
|
[](https://github.com/tr4nt0r/python-xbox/actions/workflows/build.yml)
|
|
33
33
|
[](https://codecov.io/gh/tr4nt0r/python-xbox)
|
|
34
34
|

|
|
35
|
-
[](https://www.buymeacoffee.com/tr4nt0r)
|
|
36
35
|
[](https://github.com/sponsors/tr4nt0r)
|
|
37
36
|
|
|
38
37
|
python-xbox is a python library to authenticate with Xbox Network via your Microsoft Account and provides Xbox related Web-API.
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
[](https://github.com/tr4nt0r/python-xbox/actions/workflows/build.yml)
|
|
5
5
|
[](https://codecov.io/gh/tr4nt0r/python-xbox)
|
|
6
6
|

|
|
7
|
-
[](https://www.buymeacoffee.com/tr4nt0r)
|
|
8
7
|
[](https://github.com/sponsors/tr4nt0r)
|
|
9
8
|
|
|
10
9
|
python-xbox is a python library to authenticate with Xbox Network via your Microsoft Account and provides Xbox related Web-API.
|
|
@@ -5,7 +5,7 @@ authors = [
|
|
|
5
5
|
{name = "Manfred Dennerlein Rodelo", email = "manfred@dennerlein.name"},
|
|
6
6
|
]
|
|
7
7
|
dependencies = [
|
|
8
|
-
"
|
|
8
|
+
"cryptography>=42.0",
|
|
9
9
|
"httpx>=0.25.1",
|
|
10
10
|
"ms_cv>=0.1.1",
|
|
11
11
|
"pydantic>=2.12",
|
|
@@ -83,14 +83,14 @@ packages = ["src/pythonxbox"]
|
|
|
83
83
|
[tool.hatch.envs.default]
|
|
84
84
|
python = "3.13"
|
|
85
85
|
dependencies = [
|
|
86
|
-
"platformdirs==4.
|
|
87
|
-
"
|
|
86
|
+
"platformdirs==4.11.4",
|
|
87
|
+
"cryptography==50.0.1",
|
|
88
88
|
"httpx==0.28.1",
|
|
89
89
|
"ms_cv==0.1.1",
|
|
90
|
-
"pydantic==2.
|
|
91
|
-
"pytest==9.
|
|
92
|
-
"pytest-asyncio==1.
|
|
93
|
-
"pytest-cov==7.
|
|
90
|
+
"pydantic==2.13.4",
|
|
91
|
+
"pytest==9.1.1",
|
|
92
|
+
"pytest-asyncio==1.4.0",
|
|
93
|
+
"pytest-cov==7.1.0",
|
|
94
94
|
"freezegun==1.5.5",
|
|
95
95
|
"respx~=0.22",
|
|
96
96
|
]
|
|
@@ -115,14 +115,14 @@ python = ["3.14", "3.13", "3.12", "3.11"]
|
|
|
115
115
|
|
|
116
116
|
[tool.hatch.envs.hatch-static-analysis]
|
|
117
117
|
dependencies = [
|
|
118
|
-
"ruff==0.
|
|
118
|
+
"ruff==0.15.22",
|
|
119
119
|
]
|
|
120
120
|
config-path = "none"
|
|
121
121
|
|
|
122
122
|
[tool.hatch.envs.docs]
|
|
123
123
|
dependencies = [
|
|
124
|
-
"mkdocs-material==9.7.
|
|
125
|
-
"mkdocstrings[python]==1.0.
|
|
124
|
+
"mkdocs-material==9.7.7",
|
|
125
|
+
"mkdocstrings[python]==1.0.6",
|
|
126
126
|
]
|
|
127
127
|
[tool.hatch.envs.docs.scripts]
|
|
128
128
|
serve = "mkdocs serve"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
People - Access friendlist from own
|
|
2
|
+
People - Access friendlist and profile info from own profile and others
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from typing import TYPE_CHECKING, ClassVar
|
|
@@ -71,7 +71,10 @@ class PeopleProvider(RateLimitedProvider):
|
|
|
71
71
|
**kwargs,
|
|
72
72
|
) -> PeopleResponse:
|
|
73
73
|
"""
|
|
74
|
-
Get friendlist of
|
|
74
|
+
Get friendlist of a user by their XUID
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
xuid: XUID of the user to get friends from
|
|
75
78
|
|
|
76
79
|
Returns:
|
|
77
80
|
:class:`PeopleResponse`: People Response
|
|
@@ -85,6 +88,33 @@ class PeopleProvider(RateLimitedProvider):
|
|
|
85
88
|
]
|
|
86
89
|
decoration = self.SEPERATOR.join(decoration_fields)
|
|
87
90
|
|
|
91
|
+
url = f"{self.PEOPLE_URL}/users/xuid({xuid})/people/friends/decoration/{decoration}"
|
|
92
|
+
resp = await self.client.session.get(url, headers=self._headers, **kwargs)
|
|
93
|
+
resp.raise_for_status()
|
|
94
|
+
return PeopleResponse.model_validate_json(resp.text)
|
|
95
|
+
|
|
96
|
+
async def get_friend_by_xuid(self, xuid: str, decoration_fields: list[PeopleDecoration] | None = None, **kwargs) -> PeopleResponse:
|
|
97
|
+
"""
|
|
98
|
+
Get a single friend's profile from the authenticated user's perspective
|
|
99
|
+
|
|
100
|
+
This returns relationship metadata (isFriend, canBeFriended, etc.) as seen
|
|
101
|
+
by the caller, making it useful for profile lookups and relationship checks.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
xuid: XUID of the user to retrieve profile for
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
:class:`PeopleResponse`: People Response with a single person entry
|
|
108
|
+
"""
|
|
109
|
+
if not decoration_fields:
|
|
110
|
+
decoration_fields = [
|
|
111
|
+
PeopleDecoration.PREFERRED_COLOR,
|
|
112
|
+
PeopleDecoration.DETAIL,
|
|
113
|
+
PeopleDecoration.MULTIPLAYER_SUMMARY,
|
|
114
|
+
PeopleDecoration.PRESENCE_DETAIL,
|
|
115
|
+
]
|
|
116
|
+
decoration = self.SEPERATOR.join(decoration_fields)
|
|
117
|
+
|
|
88
118
|
url = f"{self.PEOPLE_URL}/users/me/people/xuids({xuid})/decoration/{decoration}"
|
|
89
119
|
resp = await self.client.session.get(url, headers=self._headers, **kwargs)
|
|
90
120
|
resp.raise_for_status()
|
|
@@ -141,8 +141,8 @@ class CommandDestination(CamelCaseModel):
|
|
|
141
141
|
id: str
|
|
142
142
|
name: str
|
|
143
143
|
power_state: PowerState
|
|
144
|
-
remote_management_enabled: bool
|
|
145
|
-
console_streaming_enabled: bool
|
|
144
|
+
remote_management_enabled: bool | None = None
|
|
145
|
+
console_streaming_enabled: bool | None = None
|
|
146
146
|
console_type: ConsoleType
|
|
147
147
|
wireless_warning: str | None = None
|
|
148
148
|
out_of_home_warning: str | None = None
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Request Signer
|
|
1
|
+
"""Request Signer
|
|
3
2
|
|
|
4
3
|
Employed for generating the "Signature" header in authentication requests.
|
|
5
4
|
"""
|
|
@@ -9,7 +8,8 @@ from datetime import UTC, datetime
|
|
|
9
8
|
import hashlib
|
|
10
9
|
import struct
|
|
11
10
|
|
|
12
|
-
from
|
|
11
|
+
from cryptography.hazmat.primitives import hashes, serialization
|
|
12
|
+
from cryptography.hazmat.primitives.asymmetric import ec, utils
|
|
13
13
|
|
|
14
14
|
from pythonxbox.authentication.models import SignaturePolicy
|
|
15
15
|
from pythonxbox.common import filetimes
|
|
@@ -22,31 +22,39 @@ DEFAULT_SIGNING_POLICY = SignaturePolicy(
|
|
|
22
22
|
class RequestSigner:
|
|
23
23
|
def __init__(
|
|
24
24
|
self,
|
|
25
|
-
signing_key:
|
|
25
|
+
signing_key: ec.EllipticCurvePrivateKey | None = None,
|
|
26
26
|
signing_policy: SignaturePolicy | None = None,
|
|
27
27
|
) -> None:
|
|
28
|
-
self.signing_key = signing_key or
|
|
28
|
+
self.signing_key = signing_key or ec.generate_private_key(ec.SECP256R1())
|
|
29
29
|
self.signing_policy = signing_policy or DEFAULT_SIGNING_POLICY
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
pub_nums = self.signing_key.public_key().public_numbers()
|
|
32
32
|
self.proof_field = {
|
|
33
33
|
"use": "sig",
|
|
34
34
|
"alg": self.signing_policy.supported_algorithms[0],
|
|
35
35
|
"kty": "EC",
|
|
36
36
|
"crv": "P-256",
|
|
37
|
-
"x": self.__encode_ec_coord(
|
|
38
|
-
"y": self.__encode_ec_coord(
|
|
37
|
+
"x": self.__encode_ec_coord(pub_nums.x),
|
|
38
|
+
"y": self.__encode_ec_coord(pub_nums.y),
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
def export_signing_key(self) -> str:
|
|
42
|
-
return self.signing_key.
|
|
42
|
+
return self.signing_key.private_bytes(
|
|
43
|
+
serialization.Encoding.PEM,
|
|
44
|
+
serialization.PrivateFormat.TraditionalOpenSSL,
|
|
45
|
+
serialization.NoEncryption(),
|
|
46
|
+
).decode()
|
|
43
47
|
|
|
44
48
|
@staticmethod
|
|
45
|
-
def import_signing_key(signing_key: str) ->
|
|
46
|
-
|
|
49
|
+
def import_signing_key(signing_key: str) -> ec.EllipticCurvePrivateKey:
|
|
50
|
+
key = serialization.load_pem_private_key(signing_key.encode(), password=None)
|
|
51
|
+
if not isinstance(key, ec.EllipticCurvePrivateKey):
|
|
52
|
+
msg = "Expected an EC private key"
|
|
53
|
+
raise TypeError(msg)
|
|
54
|
+
return key
|
|
47
55
|
|
|
48
56
|
@classmethod
|
|
49
|
-
def from_pem(cls, pem_string: str) ->
|
|
57
|
+
def from_pem(cls, pem_string: str) -> "RequestSigner":
|
|
50
58
|
request_signer = RequestSigner.import_signing_key(pem_string)
|
|
51
59
|
return cls(request_signer)
|
|
52
60
|
|
|
@@ -79,7 +87,7 @@ class RequestSigner:
|
|
|
79
87
|
self,
|
|
80
88
|
signature: bytes,
|
|
81
89
|
digest: bytes,
|
|
82
|
-
verifying_key:
|
|
90
|
+
verifying_key: ec.EllipticCurvePublicKey | None = None,
|
|
83
91
|
) -> bool:
|
|
84
92
|
"""
|
|
85
93
|
Verify signature against digest
|
|
@@ -91,8 +99,12 @@ class RequestSigner:
|
|
|
91
99
|
|
|
92
100
|
Returns: True on successful verification, False otherwise
|
|
93
101
|
"""
|
|
94
|
-
verifier = verifying_key or self.signing_key.
|
|
95
|
-
|
|
102
|
+
verifier = verifying_key or self.signing_key.public_key()
|
|
103
|
+
r = int.from_bytes(signature[:32], "big")
|
|
104
|
+
s = int.from_bytes(signature[32:], "big")
|
|
105
|
+
der_sig = utils.encode_dss_signature(r, s)
|
|
106
|
+
verifier.verify(der_sig, digest, ec.ECDSA(utils.Prehashed(hashes.SHA256())))
|
|
107
|
+
return True
|
|
96
108
|
|
|
97
109
|
def sign(
|
|
98
110
|
self,
|
|
@@ -139,7 +151,12 @@ class RequestSigner:
|
|
|
139
151
|
digest = self._hash(data)
|
|
140
152
|
|
|
141
153
|
# Sign the hash
|
|
142
|
-
|
|
154
|
+
der_sig = self.signing_key.sign(
|
|
155
|
+
digest,
|
|
156
|
+
ec.ECDSA(utils.Prehashed(hashes.SHA256()), deterministic_signing=True),
|
|
157
|
+
)
|
|
158
|
+
r, s = utils.decode_dss_signature(der_sig)
|
|
159
|
+
signature = r.to_bytes(32, "big") + s.to_bytes(32, "big")
|
|
143
160
|
|
|
144
161
|
# Return signature version + timestamp encoded + signature
|
|
145
162
|
return signature_version_bytes + ts_bytes + signature
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_xbox-0.1.3 → python_xbox-0.2.1}/src/pythonxbox/api/provider/achievements/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|