Source code for audible.metadata

import base64
import binascii
import json
import math
import struct
from datetime import datetime


# key used for encrypt/decrypt metadata1
METADATA_KEY: bytes = b"a\x03\x8fp4\x18\x97\x99:\xeb\xe7\x8b\x85\x97$4"


[docs] def raw_xxtea(v: list[int], n: int, k: list[int] | tuple[int, ...]) -> int: if not isinstance(v, list): raise ValueError("arg `v` is not of type list") if not isinstance(k, list | tuple): raise ValueError("arg `key` is not of type list or tuple") if not isinstance(n, int): raise ValueError("arg `n` is not of type int") def mx() -> int: return ((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4)) ^ (sum_ ^ y) + ( k[(p & 3) ^ e] ^ z ) def u32(x: int) -> int: return x % 2**32 y = v[0] sum_ = 0 delta = 2654435769 if n > 1: # Encoding z = v[n - 1] q = math.floor(6 + (52 / n // 1)) while q > 0: q -= 1 sum_ = u32(sum_ + delta) e = u32(sum_ >> 2) & 3 p = 0 while p < n - 1: y = v[p + 1] z = v[p] = u32(v[p] + mx()) p += 1 y = v[0] z = v[n - 1] = u32(v[n - 1] + mx()) return 0 if n < -1: # Decoding n = -n q = math.floor(6 + (52 / n // 1)) sum_ = u32(q * delta) while sum_ != 0: e = u32(sum_ >> 2) & 3 p = n - 1 while p > 0: z = v[p - 1] y = v[p] = u32(v[p] - mx()) p -= 1 z = v[n - 1] y = v[0] = u32(v[0] - mx()) sum_ = u32(sum_ - delta) return 0 return 1
def _bytes_to_longs(data: str | bytes) -> list[int]: data_bytes = data.encode() if isinstance(data, str) else data return [ int.from_bytes(data_bytes[i : i + 4], "little") for i in range(0, len(data_bytes), 4) ] def _longs_to_bytes(data: list[int]) -> bytes: return b"".join([i.to_bytes(4, "little") for i in data]) def _generate_hex_checksum(data: str) -> str: crc_checksum = binascii.crc32(data.encode()) % 2**32 hex_checksum = format(crc_checksum, "X") if len(hex_checksum) < 8: pad = (8 - len(hex_checksum)) * "0" hex_checksum = pad + hex_checksum return hex_checksum
[docs] class XXTEAException(Exception): pass
[docs] class XXTEA: """XXTEA wrapper class. Easy to use and compatible (by duck typing) with the Blowfish class. Args: key: The encryption key (must be 128-bit / 16 characters). Raises: XXTEAException: If the key is not exactly 16 bytes long. Note: Partial copied from https://github.com/andersekbom/prycut and ported from PY2 to PY3 """ def __init__(self, key: str | bytes) -> None: key = key.encode() if isinstance(key, str) else key if len(key) != 16: raise XXTEAException("Invalid key") unpacked_key = struct.unpack("IIII", key) if len(unpacked_key) != 4: raise XXTEAException("Invalid key") self.key = unpacked_key
[docs] def encrypt(self, data: str | bytes) -> bytes: """Encrypts and returns a block of data.""" ldata = math.ceil(len(data) / 4) idata = _bytes_to_longs(data) if raw_xxtea(idata, ldata, self.key) != 0: raise XXTEAException("Cannot encrypt") return _longs_to_bytes(idata)
[docs] def decrypt(self, data: str | bytes) -> bytes: """Decrypts and returns a block of data.""" ldata = math.ceil(len(data) / 4) idata = _bytes_to_longs(data) if raw_xxtea(idata, -ldata, self.key) != 0: raise XXTEAException("Cannot decrypt") return _longs_to_bytes(idata).rstrip(b"\0")
metadata_crypter = XXTEA(METADATA_KEY)
[docs] def encrypt_metadata(metadata: str) -> str: """Encrypts metadata to be used to log in to Amazon.""" checksum = _generate_hex_checksum(metadata) object_str = f"{checksum}#{metadata}" object_encrypted = metadata_crypter.encrypt(object_str) object_base64 = base64.b64encode(object_encrypted) object_base64_decoded = object_base64.decode("utf-8") encrypted_metadata = f"ECdITeCs:{object_base64_decoded}" return encrypted_metadata
[docs] def decrypt_metadata(encrypted_metadata: str) -> str: """Decrypts metadata for testing purposes only.""" metadata_prefix_position = encrypted_metadata.find("ECdITeCs:") if metadata_prefix_position != 0: raise Exception("malformed encrypted metadata") object_base64_decoded = encrypted_metadata[9:] object_base64 = object_base64_decoded.encode("utf-8") object_encrypted = base64.b64decode(object_base64) object_str = metadata_crypter.decrypt(object_encrypted).decode("utf-8") checksum, metadata = object_str.split("#", 1) if _generate_hex_checksum(metadata) != checksum: raise XXTEAException("Checksum mismatch during decryption.") return metadata
[docs] def now_to_unix_ms() -> int: return math.floor(datetime.now().timestamp() * 1000)
[docs] def meta_audible_app(user_agent: str, oauth_url: str) -> str: """Returns json-formatted metadata to simulate sign-in from iOS audible app.""" meta_dict = { "start": now_to_unix_ms(), "interaction": { "keys": 0, "keyPressTimeIntervals": [], "copies": 0, "cuts": 0, "pastes": 0, "clicks": 0, "touches": 0, "mouseClickPositions": [], "keyCycles": [], "mouseCycles": [], "touchCycles": [], }, "version": "3.0.0", "lsUbid": "X39-6721012-8795219:1549849158", "timeZone": -6, "scripts": { "dynamicUrls": [ ( "https://images-na.ssl-images-amazon.com/images/I/" "61HHaoAEflL._RC|11-BZEJ8lnL.js,01qkmZhGmAL.js,71qOHv6nKaL." "js_.js?AUIClients/AudibleiOSMobileWhiteAuthSkin#mobile" ), ( "https://images-na.ssl-images-amazon.com/images/I/" "21T7I7qVEeL._RC|21T1XtqIBZL.js,21WEJWRAQlL.js,31DwnWh8lFL." "js,21VKEfzET-L.js,01fHQhWQYWL.js,51TfwrUQAQL.js_.js?" "AUIClients/AuthenticationPortalAssets#mobile" ), ( "https://images-na.ssl-images-amazon.com/images/I/" "0173Lf6yxEL.js?AUIClients/AuthenticationPortalInlineAssets" ), ( "https://images-na.ssl-images-amazon.com/images/I/" "211S6hvLW6L.js?AUIClients/CVFAssets" ), ( "https://images-na.ssl-images-amazon.com/images/G/" "01/x-locale/common/login/fwcim._CB454428048_.js" ), ], "inlineHashes": [ -1746719145, 1334687281, -314038750, 1184642547, -137736901, 318224283, 585973559, 1103694443, 11288800, -1611905557, 1800521327, -1171760960, -898892073, ], "elapsed": 52, "dynamicUrlCount": 5, "inlineHashesCount": 13, }, "plugins": "unknown||320-568-548-32-*-*-*", "dupedPlugins": "unknown||320-568-548-32-*-*-*", "screenInfo": "320-568-548-32-*-*-*", "capabilities": { "js": { "audio": True, "geolocation": True, "localStorage": "supported", "touch": True, "video": True, "webWorker": True, }, "css": { "textShadow": True, "textStroke": True, "boxShadow": True, "borderRadius": True, "borderImage": True, "opacity": True, "transform": True, "transition": True, }, "elapsed": 1, }, "referrer": "", "userAgent": user_agent, "location": oauth_url, "webDriver": None, "history": {"length": 1}, "gpu": {"vendor": "Apple Inc.", "model": "Apple A9 GPU", "extensions": []}, "math": { "tan": "-1.4214488238747243", "sin": "0.8178819121159085", "cos": "-0.5753861119575491", }, "performance": { "timing": { "navigationStart": now_to_unix_ms(), "unloadEventStart": 0, "unloadEventEnd": 0, "redirectStart": 0, "redirectEnd": 0, "fetchStart": now_to_unix_ms(), "domainLookupStart": now_to_unix_ms(), "domainLookupEnd": now_to_unix_ms(), "connectStart": now_to_unix_ms(), "connectEnd": now_to_unix_ms(), "secureConnectionStart": now_to_unix_ms(), "requestStart": now_to_unix_ms(), "responseStart": now_to_unix_ms(), "responseEnd": now_to_unix_ms(), "domLoading": now_to_unix_ms(), "domInteractive": now_to_unix_ms(), "domContentLoadedEventStart": now_to_unix_ms(), "domContentLoadedEventEnd": now_to_unix_ms(), "domComplete": now_to_unix_ms(), "loadEventStart": now_to_unix_ms(), "loadEventEnd": now_to_unix_ms(), } }, "end": now_to_unix_ms(), "timeToSubmit": 108873, "form": { "email": { "keys": 0, "keyPressTimeIntervals": [], "copies": 0, "cuts": 0, "pastes": 0, "clicks": 0, "touches": 0, "mouseClickPositions": [], "keyCycles": [], "mouseCycles": [], "touchCycles": [], "width": 290, "height": 43, "checksum": "C860E86B", "time": 12773, "autocomplete": False, "prefilled": False, }, "password": { "keys": 0, "keyPressTimeIntervals": [], "copies": 0, "cuts": 0, "pastes": 0, "clicks": 0, "touches": 0, "mouseClickPositions": [], "keyCycles": [], "mouseCycles": [], "touchCycles": [], "width": 290, "height": 43, "time": 10353, "autocomplete": False, "prefilled": False, }, }, "canvas": {"hash": -373378155, "emailHash": -1447130560, "histogramBins": []}, "token": None, "errors": [], "metrics": [ {"n": "fwcim-mercury-collector", "t": 0}, {"n": "fwcim-instant-collector", "t": 0}, {"n": "fwcim-element-telemetry-collector", "t": 2}, {"n": "fwcim-script-version-collector", "t": 0}, {"n": "fwcim-local-storage-identifier-collector", "t": 0}, {"n": "fwcim-timezone-collector", "t": 0}, {"n": "fwcim-script-collector", "t": 1}, {"n": "fwcim-plugin-collector", "t": 0}, {"n": "fwcim-capability-collector", "t": 1}, {"n": "fwcim-browser-collector", "t": 0}, {"n": "fwcim-history-collector", "t": 0}, {"n": "fwcim-gpu-collector", "t": 1}, {"n": "fwcim-battery-collector", "t": 0}, {"n": "fwcim-dnt-collector", "t": 0}, {"n": "fwcim-math-fingerprint-collector", "t": 0}, {"n": "fwcim-performance-collector", "t": 0}, {"n": "fwcim-timer-collector", "t": 0}, {"n": "fwcim-time-to-submit-collector", "t": 0}, {"n": "fwcim-form-input-telemetry-collector", "t": 4}, {"n": "fwcim-canvas-collector", "t": 2}, {"n": "fwcim-captcha-telemetry-collector", "t": 0}, {"n": "fwcim-proof-of-work-collector", "t": 1}, {"n": "fwcim-ubf-collector", "t": 0}, {"n": "fwcim-timer-collector", "t": 0}, ], } return json.dumps(meta_dict, separators=(",", ":"))