Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-audible for openSUSE:Factory 
checked in at 2026-10-26 10:00:21
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-audible (Old)
 and      /work/SRC/openSUSE:Factory/.python-audible.new.2252680 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-audible"

Mon Oct 26 10:00:21 2026 rev:4 rq:1392393 version:0.13.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-audible/python-audible.changes    
2026-08-03 18:02:14.065235035 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-audible.new.2252680/python-audible.changes   
    2026-10-26 10:01:11.620711028 +0100
@@ -1,0 +2,11 @@
+Sun Oct 25 11:48:42 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 0.13.1:
+  * auth: Register new iPhone devices with the Audible 4.60
+    profile
+- update to 0.13.0:
+  * Accept PKCS#8 device private keys from Android profiles
+  * deps: Update dependency cryptography to v50 [security]
+  * deps: Declare pyasn1 as a runtime dependency
+
+-------------------------------------------------------------------

Old:
----
  audible-0.12.0.tar.gz

New:
----
  audible-0.13.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-audible.spec ++++++
--- /var/tmp/diff_new_pack.8uqtzn/_old  2026-10-26 10:01:12.326740649 +0100
+++ /var/tmp/diff_new_pack.8uqtzn/_new  2026-10-26 10:01:12.332740901 +0100
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-audible
-Version:        0.12.0
+Version:        0.13.1
 Release:        0
 Summary:        A(Sync) Interface for internal Audible API
 License:        AGPL-3.0-only

++++++ audible-0.12.0.tar.gz -> audible-0.13.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/audible-0.12.0/PKG-INFO new/audible-0.13.1/PKG-INFO
--- old/audible-0.12.0/PKG-INFO 2026-07-23 08:02:55.000000000 +0200
+++ new/audible-0.13.1/PKG-INFO 2026-10-24 10:08:28.000000000 +0200
@@ -1,6 +1,6 @@
-Metadata-Version: 2.4
+Metadata-Version: 2.5
 Name: audible
-Version: 0.12.0
+Version: 0.13.1
 Summary: A(Sync) Interface for internal Audible API written in pure Python.
 Project-URL: Changelog, https://github.com/mkb79/Audible/releases
 Author-email: mkb79 <[email protected]>
@@ -20,9 +20,10 @@
 Requires-Dist: pbkdf2>=1.3
 Requires-Dist: pillow>=12.3.0
 Requires-Dist: pyaes>=1.6.1
+Requires-Dist: pyasn1>=0.6.1
 Requires-Dist: rsa>=4.9.1
 Provides-Extra: cryptography
-Requires-Dist: cryptography>=48.0.1; extra == 'cryptography'
+Requires-Dist: cryptography>=50.0.0; extra == 'cryptography'
 Provides-Extra: json-fast
 Requires-Dist: orjson>=3.11.6; extra == 'json-fast'
 Provides-Extra: json-full
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/audible-0.12.0/pyproject.toml 
new/audible-0.13.1/pyproject.toml
--- old/audible-0.12.0/pyproject.toml   2026-07-23 08:02:55.000000000 +0200
+++ new/audible-0.13.1/pyproject.toml   2026-10-24 10:08:28.000000000 +0200
@@ -1,6 +1,6 @@
 [project]
 name = "audible"
-version = "0.12.0"
+version = "0.13.1"
 description = "A(Sync) Interface for internal Audible API written in pure 
Python."
 authors = [{ name = "mkb79", email = "[email protected]" }]
 requires-python = ">=3.11,<3.15"
@@ -29,12 +29,13 @@
     "httpx (>=0.27.2,<0.29)",
     "pbkdf2 (>=1.3)",
     "pyaes (>=1.6.1)",
+    "pyasn1 (>=0.6.1)",
     "rsa (>=4.9.1)",
 ]
 
 [project.optional-dependencies]
 cryptography = [
-    "cryptography (>=48.0.1)",
+    "cryptography (>=50.0.0)",
 ]
 pycryptodome = [
     "pycryptodome (>=3.23.0)",
@@ -66,6 +67,7 @@
 ]
 
 docs = [
+    "git-cliff>=2.13.1",
     "myst-parser>=4.0.1",
     "sphinx>=8.2.3",
     "sphinx-autobuild>=2025.8.25",
@@ -79,6 +81,7 @@
     "types-beautifulsoup4>=4.12.0.20250516",
     "types-docutils>=0.22.3.20251115",
     "types-html5lib>=1.1.11.20251117",
+    "types-pyasn1>=0.6.0.20260724",
     "types-pygments>=2.19.0.20251121",
 ]
 
@@ -87,7 +90,7 @@
     "nox>=2025.11.12",
     "pre-commit>=4.5.1",
     "pre-commit-hooks>=6.0.0",
-    "ruff>=0.14.11",
+    "ruff>=0.16.0",
 ]
 
 audit = [
@@ -97,7 +100,7 @@
 release = [
     "commitizen>=4.16.5",
     "git-cliff>=2.13.1",
-    "twine>=6.2.0",
+    "twine>=7.0.0",
 ]
 
 tests = [
@@ -117,7 +120,7 @@
 ]
 
 extra-deps = [
-    "cryptography>=48.0.1",
+    "cryptography>=50.0.0",
     "pycryptodome>=3.23.0",
     "orjson>=3.11.6",
     "ujson>=5.13.0",
@@ -208,6 +211,12 @@
 module = "tests.*"
 disallow_untyped_decorators = false
 
+# sphinx ships no type information and is not in the mypy dependency group;
+# docs/source/conf.py imports it only to type its build hooks.
+[[tool.mypy.overrides]]
+module = "sphinx.application"
+ignore_missing_imports = true
+
 [tool.ruff]
 #  missing rst-docstrings check, these should be adding through ruff
 line-length = 88
@@ -256,11 +265,11 @@
 force-single-line = false
 # changelog and the release helpers live in tools/ and are imported by the
 # tests, so they sort with the project rather than with third-party packages.
-known-first-party = ["audible", "changelog", "prepare_release"]
+known-first-party = ["audible", "changelog", "prepare_release", 
"unreleased_docs"]
 lines-after-imports = 2
 
 [tool.ruff.lint.per-file-ignores]
-"tests/*" = ["S101", "PLC0415", "S106"]
+"tests/*" = ["S101", "PLC0415", "S106", "PLR0917"]
 "examples/*" = ["S101", "PLC0415"]
 "src/audible/json_provider/protocols.py" = ["D417"]
 "src/audible/json_provider/*_provider.py" = ["PLC0415"]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/audible-0.12.0/src/audible/_device.py 
new/audible-0.13.1/src/audible/_device.py
--- old/audible-0.12.0/src/audible/_device.py   1970-01-01 01:00:00.000000000 
+0100
+++ new/audible-0.13.1/src/audible/_device.py   2026-10-24 10:08:28.000000000 
+0200
@@ -0,0 +1,5 @@
+"""Shared version values for the default Audible for iPhone device profile."""
+
+APP_VERSION = "4.60"
+OS_VERSION = "26.2"
+SOFTWARE_VERSION = "46000856"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/audible-0.12.0/src/audible/auth.py 
new/audible-0.13.1/src/audible/auth.py
--- old/audible-0.12.0/src/audible/auth.py      2026-07-23 08:02:55.000000000 
+0200
+++ new/audible-0.13.1/src/audible/auth.py      2026-10-24 10:08:28.000000000 
+0200
@@ -17,6 +17,7 @@
 import httpx
 from httpx import Cookies
 
+from ._device import APP_VERSION
 from .activation_bytes import get_activation_bytes as get_ab
 from .aescipher import AESCipher, detect_file_encryption
 from .crypto_provider import get_crypto_providers
@@ -43,7 +44,7 @@
     """Build the request body for an access token refresh."""
     return {
         "app_name": "Audible",
-        "app_version": "3.56.2",
+        "app_version": APP_VERSION,
         "source_token": refresh_token,
         "requested_token_type": "access_token",
         "source_token_type": "refresh_token",
@@ -148,7 +149,7 @@
 
     body = {
         "app_name": "Audible",
-        "app_version": "3.56.2",
+        "app_version": APP_VERSION,
         "source_token": refresh_token,
         "requested_token_type": "auth_cookies",
         "source_token_type": "refresh_token",
@@ -220,7 +221,7 @@
     return profile
 
 
-def sign_request(
+def sign_request(  # noqa: PLR0917
     method: str,
     path: str,
     body: bytes,
@@ -496,7 +497,7 @@
         return auth
 
     @classmethod
-    def from_login(
+    def from_login(  # noqa: PLR0917
         cls,
         username: str,
         password: str,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/audible-0.12.0/src/audible/client.py 
new/audible-0.13.1/src/audible/client.py
--- old/audible-0.12.0/src/audible/client.py    2026-07-23 08:02:55.000000000 
+0200
+++ new/audible-0.13.1/src/audible/client.py    2026-10-24 10:08:28.000000000 
+0200
@@ -19,7 +19,7 @@
 # namespace of the module using the annotation. Without the binding, tools that
 # evaluate annotations (sphinx-autodoc-typehints among them) cannot resolve it.
 from httpx import URL, Headers
-from httpx._models import HeaderTypes  # type: ignore[attr-defined]
+from httpx._types import HeaderTypes
 
 from ._types import TrueFalseT
 from .auth import Authenticator
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/audible-0.12.0/src/audible/crypto_provider/legacy_provider.py 
new/audible-0.13.1/src/audible/crypto_provider/legacy_provider.py
--- old/audible-0.12.0/src/audible/crypto_provider/legacy_provider.py   
2026-07-23 08:02:55.000000000 +0200
+++ new/audible-0.13.1/src/audible/crypto_provider/legacy_provider.py   
2026-10-24 10:08:28.000000000 +0200
@@ -10,10 +10,15 @@
 import hmac
 import logging
 import warnings
+from base64 import b64decode
 from collections.abc import Callable
 from hashlib import sha1, sha256
+from re import DOTALL, match
 from typing import TYPE_CHECKING, Any
 
+from pyasn1.codec.der import decoder
+from pyasn1.type import namedtype, univ
+
 
 if TYPE_CHECKING:
     from .protocols import HashAlgorithm
@@ -30,6 +35,42 @@
 logger = logging.getLogger("audible.crypto_provider.legacy")
 
 
+def _convert_pkcs8_der_rsa_to_pkcs1_der(pkcs8_der: bytes) -> bytes:
+    """Converts a PKCS#8 DER RSA key to a PKCS#1 DER key.
+
+    Args:
+        pkcs8_der: An RSA key in PKCS#8 DER format.
+
+    Returns:
+        An RSA key in PKCS#1 DER format.
+
+    Raises:
+        ValueError: If PKCS#8 algorithm OID is not RSA
+            (1.2.840.113549.1.1.1).
+    """
+
+    class PrivateKeyAlgorithm(univ.Sequence):
+        componentType = namedtype.NamedTypes(  # noqa: N815
+            namedtype.NamedType("algorithm", univ.ObjectIdentifier()),
+            namedtype.NamedType("parameters", univ.Any()),
+        )
+
+    class PrivateKeyInfo(univ.Sequence):
+        componentType = namedtype.NamedTypes(  # noqa: N815
+            namedtype.NamedType("version", univ.Integer()),
+            namedtype.NamedType("pkalgo", PrivateKeyAlgorithm()),
+            namedtype.NamedType("key", univ.OctetString()),
+        )
+
+    logger.debug("Converting PKCS#8 RSA key to PKCS#1 (legacy provider)")
+    (key_info, _) = decoder.decode(pkcs8_der, asn1Spec=PrivateKeyInfo())
+    rsa_oid = univ.ObjectIdentifier((1, 2, 840, 113549, 1, 1, 1))
+    if key_info["pkalgo"]["algorithm"] != rsa_oid:
+        raise ValueError("key_info.pkalgo.algorithm: Algorithm is not RSA.")
+
+    return bytes(key_info["key"])
+
+
 def _load_rsa_private_key_legacy(pem_data: str) -> rsa.PrivateKey:
     """Load an RSA private key from PEM.
 
@@ -42,9 +83,26 @@
 
     Returns:
         A parsed rsa.PrivateKey object.
+
+    Raises:
+        ValueError: If the PEM data is in an incorrect format or if key type 
is not
+            'PRIVATE KEY' or 'RSA PRIVATE KEY'.
     """
+    pem_fmt = (
+        r"^-----BEGIN (?P<private_key_type>(?:RSA )?PRIVATE KEY)-----"
+        r"(?P<der_b64_str>.*)-----END \1-----\n$"
+    )
+    key_match = match(pem_fmt, pem_data, DOTALL)
+    if not key_match:
+        raise ValueError("pem_data: Invalid token.")
+
+    private_key_type = key_match.group("private_key_type")
+    private_key_der = b64decode(key_match.group("der_b64_str"))
+    if private_key_type == "PRIVATE KEY":
+        private_key_der = _convert_pkcs8_der_rsa_to_pkcs1_der(private_key_der)
+
     logger.debug("Loading RSA private key (legacy provider)")
-    return rsa.PrivateKey.load_pkcs1(pem_data.encode("utf-8"))
+    return rsa.PrivateKey.load_pkcs1(private_key_der, "DER")
 
 
 class LegacyAESProvider:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/audible-0.12.0/src/audible/login.py 
new/audible-0.13.1/src/audible/login.py
--- old/audible-0.12.0/src/audible/login.py     2026-07-23 08:02:55.000000000 
+0200
+++ new/audible-0.13.1/src/audible/login.py     2026-10-24 10:08:28.000000000 
+0200
@@ -4,7 +4,7 @@
 import logging
 import re
 import secrets
-import uuid
+import string
 from collections.abc import Callable
 from textwrap import dedent
 from typing import Any
@@ -14,6 +14,7 @@
 from bs4 import BeautifulSoup, NavigableString, Tag
 from PIL import Image
 
+from ._device import APP_VERSION, SOFTWARE_VERSION
 from .exceptions import AudibleError
 from .json_provider import get_json_provider
 from .metadata import encrypt_metadata, meta_audible_app
@@ -21,8 +22,10 @@
 
 logger = logging.getLogger("audible.login")
 
+# WebKit freezes the OS token since iOS 26: the app's web view reports 18_7
+# while the device registers as iOS 26.x.
 USER_AGENT = (
-    "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) "
+    "Mozilla/5.0 (iPhone; CPU iPhone OS 18_7 like Mac OS X) "
     "AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148"
 )
 
@@ -241,7 +244,13 @@
 
 
 def build_device_serial() -> str:
-    return uuid.uuid4().hex.upper()
+    """Generate a fresh 40-character alphanumeric device serial.
+
+    Returns:
+        A random serial containing only ASCII letters and digits.
+    """
+    alphabet = string.ascii_letters + string.digits
+    return "".join(secrets.choice(alphabet) for _ in range(40))
 
 
 def build_client_id(serial: str) -> str:
@@ -249,7 +258,7 @@
     return client_id.hex()
 
 
-def build_oauth_url(
+def build_oauth_url(  # noqa: PLR0917
     country_code: str,
     domain: str,
     market_place_id: str,
@@ -309,8 +318,11 @@
 
     map_md_dict = {
         "device_user_dictionary": [],
-        "device_registration_data": {"software_version": "35602678"},
-        "app_identifier": {"app_version": "3.56.2", "bundle_id": 
"com.audible.iphone"},
+        "device_registration_data": {"software_version": SOFTWARE_VERSION},
+        "app_identifier": {
+            "app_version": APP_VERSION,
+            "bundle_id": "com.audible.iphone",
+        },
     }
     map_md_str = get_json_provider().dumps(map_md_dict)
     map_md = base64.b64encode(map_md_str.encode()).decode().rstrip("=")
@@ -375,7 +387,7 @@
     return False
 
 
-def login(
+def login(  # noqa: PLR0917
     username: str,
     password: str,
     country_code: str,
@@ -591,7 +603,7 @@
     }
 
 
-def external_login(
+def external_login(  # noqa: PLR0917
     country_code: str,
     domain: str,
     market_place_id: str,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/audible-0.12.0/src/audible/register.py 
new/audible-0.13.1/src/audible/register.py
--- old/audible-0.12.0/src/audible/register.py  2026-07-23 08:02:55.000000000 
+0200
+++ new/audible-0.13.1/src/audible/register.py  2026-10-24 10:08:28.000000000 
+0200
@@ -3,6 +3,7 @@
 
 import httpx
 
+from ._device import APP_VERSION, OS_VERSION, SOFTWARE_VERSION
 from .login import build_client_id
 from .utils import build_access_token_header
 
@@ -42,15 +43,15 @@
         "cookies": {"website_cookies": [], "domain": f".amazon.{domain}"},
         "registration_data": {
             "domain": "Device",
-            "app_version": "3.56.2",
+            "app_version": APP_VERSION,
             "device_serial": serial,
             "device_type": "A2CZJZGLK2JJVM",
             "device_name": (
                 "%FIRST_NAME%%FIRST_NAME_POSSESSIVE_STRING%%DUPE_"
                 "STRATEGY_1ST%Audible for iPhone"
             ),
-            "os_version": "15.0.0",
-            "software_version": "35602678",
+            "os_version": OS_VERSION,
+            "software_version": SOFTWARE_VERSION,
             "device_model": "iPhone",
             "app_name": "Audible",
         },
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/audible-0.12.0/src/audible/utils.py 
new/audible-0.13.1/src/audible/utils.py
--- old/audible-0.12.0/src/audible/utils.py     2026-07-23 08:02:55.000000000 
+0200
+++ new/audible-0.13.1/src/audible/utils.py     2026-10-24 10:08:28.000000000 
+0200
@@ -74,10 +74,12 @@
         )
 
     fmt = (
-        r"^(?P<device_private_key>-----BEGIN RSA PRIVATE KEY-----.*"
-        r"-----END RSA PRIVATE KEY-----\n)$"
+        r"^(?P<device_private_key>"
+        r"-----BEGIN (?P<private_key_type>(?:RSA )?PRIVATE KEY)-----.*"
+        r"-----END \2-----\n)$"
     )
-    if not re.match(fmt, value, re.S):
+    key_match = re.match(fmt, value, re.S)
+    if not key_match:
         raise ValueError("device_private_key: Invalid token.")
 
 

Reply via email to