main-portfolio/scripts/spotify-token.py
2026-08-10 15:09:14 +03:00

219 lines
5 KiB
Python

#!/usr/bin/env python3
import base64
import http.server
import urllib.parse
import webbrowser
import requests
CLIENT_ID = "4442e700b9ee46c08b24f351abbe030c"
REDIRECT_URI = "http://127.0.0.1:8888/callback"
SCOPES = [
"user-read-currently-playing",
"user-read-playback-state",
]
AUTH_URL = "https://accounts.spotify.com/authorize"
TOKEN_URL = "https://accounts.spotify.com/api/token"
class CallbackHandler(http.server.BaseHTTPRequestHandler):
code = None
error = None
def do_GET(self):
parsed = urllib.parse.urlparse(self.path)
params = urllib.parse.parse_qs(parsed.query)
CallbackHandler.code = params.get("code", [None])[0]
CallbackHandler.error = params.get("error", [None])[0]
self.send_response(200)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.end_headers()
if CallbackHandler.code:
message = """
<h1>Spotify authorization received.</h1>
<p>You can close this tab.</p>
"""
else:
message = """
<h1>Spotify authorization failed.</h1>
<p>Check the terminal for details.</p>
"""
self.wfile.write(
f"""
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Spotify OAuth</title>
</head>
<body>
{message}
</body>
</html>
""".encode("utf-8")
)
def log_message(self, format, *args):
pass
def get_client_secret():
secret = input("SPOTIFY_CLIENT_SECRET: ").strip()
if not secret:
raise SystemExit("Client secret cannot be empty.")
return secret
def build_authorization_url():
params = urllib.parse.urlencode(
{
"client_id": CLIENT_ID,
"response_type": "code",
"redirect_uri": REDIRECT_URI,
"scope": " ".join(SCOPES),
}
)
return f"{AUTH_URL}?{params}"
def exchange_code_for_token(client_secret, code):
credentials = f"{CLIENT_ID}:{client_secret}"
encoded_credentials = base64.b64encode(
credentials.encode("utf-8")
).decode("ascii")
response = requests.post(
TOKEN_URL,
data={
"grant_type": "authorization_code",
"code": code,
"redirect_uri": REDIRECT_URI,
},
headers={
"Authorization": f"Basic {encoded_credentials}",
"Content-Type": "application/x-www-form-urlencoded",
},
timeout=15,
)
return response
def main():
print()
print("Spotify OAuth token generator")
print("=" * 60)
print()
client_secret = get_client_secret()
server = http.server.HTTPServer(
("127.0.0.1", 8888),
CallbackHandler,
)
authorization_url = build_authorization_url()
print()
print("Opening Spotify authorization page...")
print()
print(authorization_url)
print()
webbrowser.open(authorization_url)
print("Waiting for Spotify callback...")
print()
while CallbackHandler.code is None and CallbackHandler.error is None:
server.handle_request()
server.server_close()
if CallbackHandler.error:
print("Spotify authorization failed:")
print(CallbackHandler.error)
raise SystemExit(1)
code = CallbackHandler.code
if not code:
raise SystemExit("No authorization code received.")
print("Authorization code received.")
print("Exchanging code for tokens...")
print()
response = exchange_code_for_token(client_secret, code)
if not response.ok:
print("=" * 60)
print("SPOTIFY TOKEN REQUEST FAILED")
print("=" * 60)
print()
print(f"HTTP status: {response.status_code}")
print()
print("Spotify response:")
print(response.text)
print()
raise SystemExit(1)
try:
data = response.json()
except ValueError:
print("Spotify returned invalid JSON:")
print(response.text)
raise SystemExit(1)
refresh_token = data.get("refresh_token")
access_token = data.get("access_token")
expires_in = data.get("expires_in")
scope = data.get("scope")
print("=" * 60)
print("SPOTIFY TOKENS")
print("=" * 60)
print()
if refresh_token:
print("SPOTIFY_REFRESH_TOKEN:")
print(refresh_token)
else:
print("WARNING: Spotify did not return a refresh_token.")
print()
if access_token:
print("Access token received: YES")
else:
print("Access token received: NO")
print(f"Expires in: {expires_in} seconds")
print(f"Granted scopes: {scope}")
print()
print("=" * 60)
print("IMPORTANT")
print("=" * 60)
print()
print("Store the refresh token in your .env file.")
print("Do not commit it to Git.")
print()
if __name__ == "__main__":
main()