diff --git a/votify/spotify_api.py b/votify/spotify_api.py index c23350c..87eba0c 100644 --- a/votify/spotify_api.py +++ b/votify/spotify_api.py @@ -53,10 +53,10 @@ class SpotifyApi: def __init__( self, sp_dc: str | None = None, - use_totp: bool = False, + use_device_flow: bool = False, ) -> None: self.sp_dc = sp_dc - self.use_totp = use_totp + self.use_device_flow = use_device_flow self._set_session() @classmethod @@ -144,10 +144,10 @@ class SpotifyApi: ) def _setup_authorization(self) -> None: - if self.use_totp: - self._setup_authorization_with_totp() - else: + if self.use_device_flow: self._setup_authorization_with_device_flow() + else: + self._setup_authorization_with_totp() def _setup_authorization_with_device_flow(self) -> None: token_data = self._get_token_via_device_flow() diff --git a/votify/totp.py b/votify/totp.py index 18a23bb..9e6ea32 100644 --- a/votify/totp.py +++ b/votify/totp.py @@ -1,18 +1,40 @@ +from __future__ import annotations + import hashlib import hmac import math +import requests + # thanks to https://github.com/glomatico/votify/pull/42#issuecomment-2727036757 class TOTP: + SPOTIFY_SECRETS_JSON = "https://raw.githubusercontent.com/Thereallo1026/spotify-secrets/refs/heads/main/secrets/secretDict.json" + PERIOD = 30 + DIGITS = 6 + def __init__(self) -> None: - # dumped directly from the object, after all decryptions - self.secret = b"120887534115119493370126558380647147717911891615550" - self.version = 25 - self.period = 30 - self.digits = 6 + self._setup() + + def _setup(self) -> None: + version, secret_cipher_bytes = self.get_latest_secret() + self.version = version + self.secret = self.derive_secret_number(secret_cipher_bytes).encode() + + def derive_secret_number(self, secret_cipher_bytes: list[int]) -> str: + transformed = [ + byte ^ ((i % 33) + 9) for i, byte in enumerate(secret_cipher_bytes) + ] + return "".join(str(n) for n in transformed) + + def get_latest_secret(self) -> tuple[int, list[int]]: + response = requests.get(self.SPOTIFY_SECRETS_JSON) + response.raise_for_status() + secrets = response.json() + latest_version = max(int(v) for v in secrets.keys()) + return latest_version, secrets[str(latest_version)] def generate(self, timestamp: int) -> str: - counter = math.floor(timestamp / 1000 / self.period) + counter = math.floor(timestamp / 1000 / self.PERIOD) counter_bytes = counter.to_bytes(8, byteorder="big") h = hmac.new(self.secret, counter_bytes, hashlib.sha1) @@ -26,4 +48,4 @@ class TOTP: | (hmac_result[offset + 3] & 0xFF) ) - return str(binary % (10**self.digits)).zfill(self.digits) + return str(binary % (10**self.DIGITS)).zfill(self.DIGITS)