python-xbox 0.1.3__tar.gz → 0.2.0__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.
Files changed (64) hide show
  1. {python_xbox-0.1.3 → python_xbox-0.2.0}/PKG-INFO +2 -3
  2. {python_xbox-0.1.3 → python_xbox-0.2.0}/README.md +0 -1
  3. {python_xbox-0.1.3 → python_xbox-0.2.0}/pyproject.toml +6 -6
  4. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/__init__.py +1 -1
  5. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/people/__init__.py +32 -2
  6. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/common/request_signer.py +33 -16
  7. {python_xbox-0.1.3 → python_xbox-0.2.0}/.gitignore +0 -0
  8. {python_xbox-0.1.3 → python_xbox-0.2.0}/LICENSE +0 -0
  9. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/__init__.py +0 -0
  10. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/client.py +0 -0
  11. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/language.py +0 -0
  12. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/__init__.py +0 -0
  13. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/account/__init__.py +0 -0
  14. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/account/models.py +0 -0
  15. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/achievements/__init__.py +0 -0
  16. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/achievements/models.py +0 -0
  17. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/baseprovider.py +0 -0
  18. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/catalog/__init__.py +0 -0
  19. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/catalog/const.py +0 -0
  20. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/catalog/models.py +0 -0
  21. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/cqs/__init__.py +0 -0
  22. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/cqs/models.py +0 -0
  23. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/gameclips/__init__.py +0 -0
  24. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/gameclips/models.py +0 -0
  25. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/lists/__init__.py +0 -0
  26. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/lists/models.py +0 -0
  27. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/mediahub/__init__.py +0 -0
  28. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/mediahub/models.py +0 -0
  29. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/message/__init__.py +0 -0
  30. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/message/models.py +0 -0
  31. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/people/models.py +0 -0
  32. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/presence/__init__.py +0 -0
  33. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/presence/models.py +0 -0
  34. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/profile/__init__.py +0 -0
  35. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/profile/models.py +0 -0
  36. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/ratelimitedprovider.py +0 -0
  37. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/screenshots/__init__.py +0 -0
  38. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/screenshots/models.py +0 -0
  39. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/smartglass/__init__.py +0 -0
  40. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/smartglass/models.py +0 -0
  41. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/titlehub/__init__.py +0 -0
  42. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/titlehub/models.py +0 -0
  43. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/usersearch/__init__.py +0 -0
  44. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/usersearch/models.py +0 -0
  45. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/userstats/__init__.py +0 -0
  46. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/api/provider/userstats/models.py +0 -0
  47. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/authentication/__init__.py +0 -0
  48. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/authentication/manager.py +0 -0
  49. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/authentication/models.py +0 -0
  50. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/authentication/xal.py +0 -0
  51. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/common/__init__.py +0 -0
  52. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/common/exceptions.py +0 -0
  53. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/common/filetimes.py +0 -0
  54. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/common/models.py +0 -0
  55. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/common/ratelimits/__init__.py +0 -0
  56. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/common/ratelimits/models.py +0 -0
  57. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/common/signed_session.py +0 -0
  58. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/py.typed +0 -0
  59. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/scripts/__init__.py +0 -0
  60. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/scripts/authenticate.py +0 -0
  61. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/scripts/change_gamertag.py +0 -0
  62. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/scripts/friends.py +0 -0
  63. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/scripts/search.py +0 -0
  64. {python_xbox-0.1.3 → python_xbox-0.2.0}/src/pythonxbox/scripts/xal.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-xbox
3
- Version: 0.1.3
3
+ Version: 0.2.0
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: ecdsa>=0.19
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
  [![Build](https://github.com/tr4nt0r/python-xbox/actions/workflows/build.yml/badge.svg)](https://github.com/tr4nt0r/python-xbox/actions/workflows/build.yml)
33
33
  [![codecov](https://codecov.io/gh/tr4nt0r/python-xbox/graph/badge.svg?token=DWUTrV1tM4)](https://codecov.io/gh/tr4nt0r/python-xbox)
34
34
  ![PyPI - Downloads](https://img.shields.io/pypi/dm/python-xbox?style=flat&label=pypi%20downloads)
35
- [!["Buy Me A Coffee"](https://img.shields.io/badge/-buy_me_a%C2%A0coffee-gray?logo=buy-me-a-coffee)](https://www.buymeacoffee.com/tr4nt0r)
36
35
  [![GitHub Sponsor](https://img.shields.io/badge/GitHub-Sponsor-blue?logo=github)](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
  [![Build](https://github.com/tr4nt0r/python-xbox/actions/workflows/build.yml/badge.svg)](https://github.com/tr4nt0r/python-xbox/actions/workflows/build.yml)
5
5
  [![codecov](https://codecov.io/gh/tr4nt0r/python-xbox/graph/badge.svg?token=DWUTrV1tM4)](https://codecov.io/gh/tr4nt0r/python-xbox)
6
6
  ![PyPI - Downloads](https://img.shields.io/pypi/dm/python-xbox?style=flat&label=pypi%20downloads)
7
- [!["Buy Me A Coffee"](https://img.shields.io/badge/-buy_me_a%C2%A0coffee-gray?logo=buy-me-a-coffee)](https://www.buymeacoffee.com/tr4nt0r)
8
7
  [![GitHub Sponsor](https://img.shields.io/badge/GitHub-Sponsor-blue?logo=github)](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
- "ecdsa>=0.19",
8
+ "cryptography>=42.0",
9
9
  "httpx>=0.25.1",
10
10
  "ms_cv>=0.1.1",
11
11
  "pydantic>=2.12",
@@ -83,8 +83,8 @@ packages = ["src/pythonxbox"]
83
83
  [tool.hatch.envs.default]
84
84
  python = "3.13"
85
85
  dependencies = [
86
- "platformdirs==4.5.1",
87
- "ecdsa==0.19.1",
86
+ "platformdirs==4.9.2",
87
+ "cryptography==46.0.5",
88
88
  "httpx==0.28.1",
89
89
  "ms_cv==0.1.1",
90
90
  "pydantic==2.12.5",
@@ -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.14.13",
118
+ "ruff==0.15.4",
119
119
  ]
120
120
  config-path = "none"
121
121
 
122
122
  [tool.hatch.envs.docs]
123
123
  dependencies = [
124
- "mkdocs-material==9.7.1",
125
- "mkdocstrings[python]==1.0.1",
124
+ "mkdocs-material==9.7.3",
125
+ "mkdocstrings[python]==1.0.3",
126
126
  ]
127
127
  [tool.hatch.envs.docs.scripts]
128
128
  serve = "mkdocs serve"
@@ -1,4 +1,4 @@
1
1
  """Top-level package for python-xbox."""
2
2
 
3
3
  __author__ = """tr4nt0r"""
4
- __version__ = "0.1.3"
4
+ __version__ = "0.2.0"
@@ -1,5 +1,5 @@
1
1
  """
2
- People - Access friendlist from own profiles and others
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 own profile
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()
@@ -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 ecdsa import NIST256p, SigningKey, VerifyingKey
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: SigningKey | None = None,
25
+ signing_key: ec.EllipticCurvePrivateKey | None = None,
26
26
  signing_policy: SignaturePolicy | None = None,
27
27
  ) -> None:
28
- self.signing_key = signing_key or SigningKey.generate(curve=NIST256p)
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
- pk_point = self.signing_key.verifying_key.pubkey.point
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(pk_point.x()),
38
- "y": self.__encode_ec_coord(pk_point.y()),
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.to_pem().decode()
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) -> SigningKey:
46
- return SigningKey.from_pem(signing_key)
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) -> SigningKey:
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: VerifyingKey | None = None,
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.verifying_key
95
- return verifier.verify_digest(signature, digest)
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
- signature = self.signing_key.sign_digest_deterministic(digest)
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