#!/usr/bin/env python3 """ Test script for Spotify token methods Tests both TOTP generation and tokener API fallback """ import json import logging import time from urllib.parse import urlencode import requests # Set up logging logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) # Constants from spotify_web_player_client.py SPOTIFY_TOTP_SECRET = "GM3TMMJTGYZTQNZVGM4DINJZHA4TGOBYGMZTCMRTGEYDSMJRHE4TEOBUG4YTCMRUGQ4DQOJUGQYTAMRRGA2TCMJSHE3TCMBY" SPOTIFY_TOTP_VERSION = 61 def generate_totp(): """Generate TOTP code using Spotify's hardcoded secret""" import base64 import hashlib import hmac import time # Base32 decode the secret secret_bytes = base64.b32decode(SPOTIFY_TOTP_SECRET) # Get current time in 30-second intervals current_time = int(time.time() // 30) # Convert to bytes (big-endian, 8 bytes) time_bytes = current_time.to_bytes(8, 'big') # HMAC-SHA1 h = hmac.new(secret_bytes, time_bytes, hashlib.sha1) hmac_result = h.digest() # Dynamic truncation offset = hmac_result[-1] & 0x0f code = ( ((hmac_result[offset] & 0x7f) << 24) | ((hmac_result[offset + 1] & 0xff) << 16) | ((hmac_result[offset + 2] & 0xff) << 8) | (hmac_result[offset + 3] & 0xff) ) # Get 6-digit code totp_code = str(code % 1000000).zfill(6) return totp_code def test_totp_method(): """Test TOTP token method""" logger.info("Testing TOTP token method...") try: totp_code = generate_totp() logger.info(f"Generated TOTP code: {totp_code}") # Build URL with query parameters params = { "reason": "init", "productType": "web-player", "totp": totp_code, "totpVer": SPOTIFY_TOTP_VERSION, "totpServer": totp_code, } url = f"https://open.spotify.com/api/token?{urlencode(params)}" logger.info(f"Requesting token from: {url}") headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Accept": "application/json", "Accept-Language": "en-US,en;q=0.9", } response = requests.get(url, headers=headers, timeout=30) logger.info(f"Response status: {response.status_code}") logger.info(f"Response headers: {dict(response.headers)}") if response.status_code == 200: data = response.json() logger.info("✅ TOTP method SUCCESS!") logger.info(f"Access token: {data.get('accessToken', '')[:20]}...") logger.info(f"Client ID: {data.get('clientId', '')}") logger.info(f"Expires in: {data.get('expiresIn', 'unknown')} seconds") return True else: logger.error(f"❌ TOTP method FAILED: HTTP {response.status_code}") logger.error(f"Response body: {response.text}") return False except Exception as e: logger.error(f"❌ TOTP method ERROR: {e}") return False def test_tokener_method(): """Test tokener API fallback method""" logger.info("Testing tokener API fallback method...") try: url = "https://spotify-tokener-api.vercel.app/api/getToken" logger.info(f"Requesting token from: {url}") headers = { "User-Agent": "SwingMusic/1.0 (https://github.com/geoffrey45/swingmusic)", "Accept": "application/json", } response = requests.get(url, headers=headers, timeout=10) logger.info(f"Response status: {response.status_code}") logger.info(f"Response headers: {dict(response.headers)}") if response.status_code == 200: data = response.json() logger.info("✅ Tokener API method SUCCESS!") logger.info(f"Access token: {data.get('accessToken', '')[:20]}...") logger.info(f"Client ID: {data.get('clientId', '')}") logger.info(f"Is anonymous: {data.get('isAnonymous', 'unknown')}") logger.info(f"Expires: {data.get('accessTokenExpirationTimestampMs', 'unknown')}") # Check for the note about terms if '_notes' in data: logger.warning(f"⚠️ API note: {data['_notes']}") return True else: logger.error(f"❌ Tokener API method FAILED: HTTP {response.status_code}") logger.error(f"Response body: {response.text}") return False except Exception as e: logger.error(f"❌ Tokener API method ERROR: {e}") return False def test_token_validity(token, client_id): """Test if a token is valid by making a simple API call""" logger.info("Testing token validity...") try: # Simple GraphQL query to test the token payload = { "variables": { "uri": "spotify:track:4cOdK2wGLETOMrsVzAojDx", # A popular track ID }, "operationName": "getTrack", "extensions": { "persistedQuery": { "version": 1, "sha256Hash": "612585ae06ba435ad26369870deaae23b5c8800a256cd8a57e08eddc25a37294", } } } headers = { "Authorization": f"Bearer {token}", "Content-Type": "application/json", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", } response = requests.post( "https://api-partner.spotify.com/pathfinder/v1/query", json=payload, headers=headers, timeout=30 ) if response.status_code == 200: data = response.json() if "data" in data and "trackUnion" in data["data"]: track = data["data"]["trackUnion"] logger.info("✅ Token is VALID!") logger.info(f"Test track: {track.get('name', 'Unknown')}") return True else: logger.error("❌ Token returned invalid data") return False else: logger.error(f"❌ Token validation FAILED: HTTP {response.status_code}") logger.error(f"Response: {response.text}") return False except Exception as e: logger.error(f"❌ Token validation ERROR: {e}") return False def main(): """Run all tests""" print("=" * 60) print("Spotify Token Methods Test") print("=" * 60) # Test TOTP method print("\n1. Testing TOTP Method (Primary)") print("-" * 40) totp_success = test_totp_method() # Test tokener API method print("\n2. Testing Tokener API Method (Fallback)") print("-" * 40) tokener_success = test_tokener_method() # Test token validity if either method worked if totp_success or tokener_success: print("\n3. Testing Token Validity") print("-" * 40) # Get a token from whichever method worked if totp_success: print("Testing TOTP token...") # This would require extracting the token from the TOTP test # For now, just indicate it would be tested logger.info("TOTP token would be validated here") if tokener_success: print("Testing tokener API token...") # Get a fresh token for testing try: response = requests.get("https://spotify-tokener-api.vercel.app/api/getToken", timeout=10) if response.status_code == 200: data = response.json() token = data.get('accessToken') client_id = data.get('clientId') if token: test_token_validity(token, client_id) except Exception as e: logger.error(f"Failed to get token for testing: {e}") # Summary print("\n" + "=" * 60) print("Test Results Summary") print("=" * 60) print(f"TOTP Method: {'✅ WORKING' if totp_success else '❌ FAILED'}") print(f"Tokener API: {'✅ WORKING' if tokener_success else '❌ FAILED'}") if totp_success or tokener_success: print("🎉 At least one method is working!") else: print("⚠️ Both methods failed - need to investigate") print("=" * 60) if __name__ == "__main__": main()