[GH-ISSUE #3] Spotify updated how they issue the refresh token and queries? #3

Closed
opened 2026-02-28 14:28:01 +03:00 by kerem · 0 comments
Owner

Originally created by @sferez on GitHub (Mar 15, 2025).
Original GitHub issue: https://github.com/entriphy/sp-playcount/issues/3

Spotify update refresh how they issue the refresh token and queries?

A fix for the refresh token could be

app.py update the refresh_token function

    async def refresh_token(self):
        try:
            async with self.client.get("https://open.spotify.com/server-time") as resp:
                server_time_data = await resp.json()
            server_time_seconds = server_time_data["serverTime"]

            # Generate TOTP using server time
            totp = await generate_totp(server_time_seconds)
            timestamp = int(time.time())  # Current client timestamp
            params = {
                "reason": "transport",
                "productType": "web_player",
                "totp": totp,
                "totpVer": "5",
                "ts": str(timestamp),
            }
            headers = {
                # "Cookie": f"sp_dc=#PLACE_YOUR_SP_DC_COOKIE_HERE",
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
            }
            response = await self.client.get("https://open.spotify.com/get_access_token", params=params, headers=headers)
            access_token_json = await response.json()

            self.headers["authorization"] = "Bearer " + access_token_json["accessToken"]
            self.client_id = access_token_json["clientId"]
            self.access_token_expiration = access_token_json["accessTokenExpirationTimestampMs"]
            print(f'New acces token expires at {self.access_token_expiration}')
            return self.access_token_expiration
        except Exception as e:
            raise QueryError("Error while refreshing token: " + str(e))

and add those two utils

async def generate_totp(server_time_seconds):
    secret_cipher = [12, 56, 76, 33, 88, 44, 88, 33, 78, 78, 11, 66, 22, 22, 55, 69, 54]
    processed = [byte ^ (i % 33 + 9) for i, byte in enumerate(secret_cipher)]
    processed_str = ''.join(map(str, processed))
    utf8_bytes = processed_str.encode()
    hex_str = utf8_bytes.hex()
    cleaned_hex = clean_hex(hex_str)
    secret_bytes = bytes.fromhex(cleaned_hex)
    secret_base32 = base64.b32encode(secret_bytes).decode().strip('=')

    totp = pyotp.TOTP(secret_base32, interval=30, digits=6, digest=hashlib.sha1)
    return totp.at(int(server_time_seconds))


def clean_hex(hex_str):
    valid_chars = set(string.hexdigits)
    cleaned = ''.join(c for c in hex_str if c.lower() in valid_chars)
    if len(cleaned) % 2 != 0:
        cleaned = cleaned[:-1]
    return cleaned

CREDIT: https://github.com/librespot-org/librespot/issues/1475#issuecomment-2726445254

Originally created by @sferez on GitHub (Mar 15, 2025). Original GitHub issue: https://github.com/entriphy/sp-playcount/issues/3 Spotify update refresh how they issue the refresh token and queries? A fix for the refresh token could be app.py update the refresh_token function ``` async def refresh_token(self): try: async with self.client.get("https://open.spotify.com/server-time") as resp: server_time_data = await resp.json() server_time_seconds = server_time_data["serverTime"] # Generate TOTP using server time totp = await generate_totp(server_time_seconds) timestamp = int(time.time()) # Current client timestamp params = { "reason": "transport", "productType": "web_player", "totp": totp, "totpVer": "5", "ts": str(timestamp), } headers = { # "Cookie": f"sp_dc=#PLACE_YOUR_SP_DC_COOKIE_HERE", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" } response = await self.client.get("https://open.spotify.com/get_access_token", params=params, headers=headers) access_token_json = await response.json() self.headers["authorization"] = "Bearer " + access_token_json["accessToken"] self.client_id = access_token_json["clientId"] self.access_token_expiration = access_token_json["accessTokenExpirationTimestampMs"] print(f'New acces token expires at {self.access_token_expiration}') return self.access_token_expiration except Exception as e: raise QueryError("Error while refreshing token: " + str(e)) ``` and add those two utils ``` async def generate_totp(server_time_seconds): secret_cipher = [12, 56, 76, 33, 88, 44, 88, 33, 78, 78, 11, 66, 22, 22, 55, 69, 54] processed = [byte ^ (i % 33 + 9) for i, byte in enumerate(secret_cipher)] processed_str = ''.join(map(str, processed)) utf8_bytes = processed_str.encode() hex_str = utf8_bytes.hex() cleaned_hex = clean_hex(hex_str) secret_bytes = bytes.fromhex(cleaned_hex) secret_base32 = base64.b32encode(secret_bytes).decode().strip('=') totp = pyotp.TOTP(secret_base32, interval=30, digits=6, digest=hashlib.sha1) return totp.at(int(server_time_seconds)) def clean_hex(hex_str): valid_chars = set(string.hexdigits) cleaned = ''.join(c for c in hex_str if c.lower() in valid_chars) if len(cleaned) % 2 != 0: cleaned = cleaned[:-1] return cleaned ``` CREDIT: https://github.com/librespot-org/librespot/issues/1475#issuecomment-2726445254
kerem closed this issue 2026-02-28 14:28:01 +03:00
Sign in to join this conversation.
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/sp-playcount#3
No description provided.