/
/
/
1"""Helpers for the MusicMe provider (crypto, etc.)."""
2
3
4def decrypt(encrypted: str) -> str:
5 """
6 Decrypt a MusicMe API response or ticket string.
7
8 Reverse-engineered from audioFrame-bundle.min.js (apache_crypto module).
9 Algorithm: strip padding, find marker, rearrange halves, hex-decode XOR 0xAA.
10 """
11 if len(encrypted) < 20:
12 msg = f"Encrypted payload too short ({len(encrypted)} chars)"
13 raise ValueError(msg)
14
15 # Strip first 10 and last 8 padding characters
16 encrypted = encrypted[10 : len(encrypted) - 8]
17
18 # Find position marker ('8' or 'B' at an odd index within the first 10 chars)
19 pos = -1
20 for i in range(min(10, len(encrypted))):
21 c = encrypted[i]
22 if i % 2 == 1 and c in ("8", "B"):
23 pos = i + 1
24 break
25 if pos == -1:
26 pos = 10
27 encrypted = encrypted[pos:]
28
29 # Rearrange: swap two halves of the hex string
30 test = list(encrypted)
31 first_length = len(test) // 2 - (len(test) // 2) % 2
32 sec_length = len(test) - first_length
33 chars: list[str] = [""] * len(test)
34 for j in range(first_length):
35 chars[j + sec_length] = test[j]
36 for j in range(sec_length):
37 chars[j] = test[first_length + j]
38
39 chars_len = len(chars)
40 if chars_len == 0:
41 raise ValueError("Encrypted payload is empty after processing")
42 if chars_len % 2 != 0:
43 msg = f"Encrypted payload has odd hex length after processing ({chars_len})"
44 raise ValueError(msg)
45 decrypted_chars: list[int] = []
46 for i in range(chars_len // 2):
47 hex_str = chars[2 * i] + chars[2 * i + 1]
48 b = int(hex_str, 16)
49 decrypted_chars.append(b ^ 0xAA)
50
51 return "".join(chr(b) for b in decrypted_chars)
52