mirror of
https://github.com/Dvorinka/swingmusic-extended.git
synced 2026-06-03 20:13:02 +00:00
Fix CI/CD pipeline and code quality issues
## Major Changes - Fixed all TypeScript errors in web client for successful compilation - Resolved 82+ Python lint errors across backend services - Updated Flutter SDK compatibility for mobile app - Fixed security workflow configuration ## Web Client Fixes - Fixed import path in DragonflyDashboard.vue (dragonflyApi import) - All TypeScript compilation now passes without errors ## Backend Lint Fixes - Updated type annotations to modern Python syntax (dict instead of Dict, X | None instead of Optional[X]) - Replaced try-except-pass with contextlib.suppress(Exception) - Removed unused imports (Dict, Optional, Any, Iterator, etc.) - Fixed bare except clauses to use Exception - Sorted and formatted imports with ruff - Applied ruff format to 27 files ## Workflow Fixes - Updated Flutter SDK constraint from ^3.10.4 to ^3.5.0 (compatible with Flutter 3.24.0) - Changed pip-audit format from github to json in security.yml - Added comprehensive CI workflows (readiness-gate.yml, security.yml) ## Infrastructure - Added DragonflyDB caching system integration - Enhanced Docker configuration with multi-stage builds - Added pytest configuration and test infrastructure - Improved production readiness with proper error handling ## Verification - backend-lint job: ✅ Succeeded - web job: ✅ Succeeded - Ready for GitHub deployment All CI/CD issues resolved. Codebase now passes all quality checks.
This commit is contained in:
@@ -0,0 +1,251 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user