94 lines
3.1 KiB
Python
94 lines
3.1 KiB
Python
![]() |
import json
|
||
|
import os
|
||
|
import time
|
||
|
import webbrowser
|
||
|
import requests
|
||
|
import subprocess
|
||
|
from urllib.parse import urlencode, urlparse, parse_qs
|
||
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||
|
|
||
|
CONFIG_FILE = "config.json"
|
||
|
TOKEN_URL = "https://accounts.spotify.com/api/token"
|
||
|
AUTH_URL = "https://accounts.spotify.com/authorize"
|
||
|
SCOPE = "user-library-read"
|
||
|
def load_config():
|
||
|
with open(CONFIG_FILE, "r") as f:
|
||
|
return json.load(f)
|
||
|
def save_config(data):
|
||
|
with open(CONFIG_FILE, "w") as f:
|
||
|
json.dump(data, f, indent=2)
|
||
|
def start_auth_flow(config):
|
||
|
params = {
|
||
|
"client_id": config["client_id"],
|
||
|
"response_type": "code",
|
||
|
"redirect_uri": config["redirect_uri"],
|
||
|
"scope": SCOPE
|
||
|
}
|
||
|
auth_url = f"{AUTH_URL}?{urlencode(params)}"
|
||
|
webbrowser.open(auth_url)
|
||
|
print("Waiting for callback at /callback...")
|
||
|
|
||
|
class SpotifyAuthHandler(BaseHTTPRequestHandler):
|
||
|
def do_GET(self):
|
||
|
query = urlparse(self.path).query
|
||
|
params = parse_qs(query)
|
||
|
code = params.get("code", [None])[0]
|
||
|
|
||
|
self.send_response(200)
|
||
|
self.send_header("Content-type", "text/html")
|
||
|
self.end_headers()
|
||
|
self.wfile.write(b"<h1>You can close this window now.</h1>")
|
||
|
|
||
|
if code:
|
||
|
self.server.auth_code = code
|
||
|
|
||
|
httpd = HTTPServer(("localhost", 8888), SpotifyAuthHandler)
|
||
|
httpd.handle_request()
|
||
|
return getattr(httpd, "auth_code", None)
|
||
|
def exchange_code_for_token(code, config):
|
||
|
auth = (config["client_id"], config["client_secret"])
|
||
|
data = {
|
||
|
"grant_type": "authorization_code",
|
||
|
"code": code,
|
||
|
"redirect_uri": config["redirect_uri"]
|
||
|
}
|
||
|
res = requests.post(TOKEN_URL, auth=auth, data=data)
|
||
|
res.raise_for_status()
|
||
|
token_data = res.json()
|
||
|
config["access_token"] = token_data["access_token"]
|
||
|
config["refresh_token"] = token_data["refresh_token"]
|
||
|
config["expires_at"] = int(time.time()) + token_data["expires_in"]
|
||
|
save_config(config)
|
||
|
print("Token saved to config.json")
|
||
|
return token_data["access_token"]
|
||
|
def refresh_token_if_needed(config):
|
||
|
now = int(time.time())
|
||
|
if "access_token" not in config or now >= config.get("expires_at", 0):
|
||
|
print("Refreshing token...")
|
||
|
auth = (config["client_id"], config["client_secret"])
|
||
|
data = {
|
||
|
"grant_type": "refresh_token",
|
||
|
"refresh_token": config["refresh_token"]
|
||
|
}
|
||
|
res = requests.post(TOKEN_URL, auth=auth, data=data)
|
||
|
res.raise_for_status()
|
||
|
token_data = res.json()
|
||
|
config["access_token"] = token_data["access_token"]
|
||
|
config["expires_at"] = now + token_data["expires_in"]
|
||
|
save_config(config)
|
||
|
print("Refreshed and saved token.")
|
||
|
return config["access_token"]
|
||
|
if __name__ == "__main__":
|
||
|
config = load_config()
|
||
|
|
||
|
if "refresh_token" not in config:
|
||
|
code = start_auth_flow(config)
|
||
|
if not code:
|
||
|
print("Authorization failed.")
|
||
|
exit(1)
|
||
|
token = exchange_code_for_token(code, config)
|
||
|
else:
|
||
|
token = refresh_token_if_needed(config)
|
||
|
|
||
|
print("Spotify access token ready.")
|