restore portfolio
This commit is contained in:
parent
82c21e9479
commit
13a67bbf8d
28 changed files with 7747 additions and 19 deletions
219
scripts/spotify-token.py
Normal file
219
scripts/spotify-token.py
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
#!/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()
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue