Files
hermes-agent/scripts/spotify_manual_auth.py
T

157 lines
5.8 KiB
Python

#!/usr/bin/env python3
"""Manueller Spotify-PKCE-Login fuer Hermes -- OHNE lokalen Callback-Server
und OHNE SSH-Port-Forward.
Hintergrund: `hermes auth spotify` startet einen HTTP-Listener auf
127.0.0.1:43827 IM Container und wartet auf den OAuth-Redirect. Der
redirect_uri-Wert ist von Hermes hart auf 127.0.0.1/localhost validiert
(hermes_cli/auth.py:_spotify_validate_redirect_uri) -- eine oeffentliche
Callback-URL ist nicht moeglich. Ohne `ssh -N -L 43827:127.0.0.1:43827`
kann der Browser auf dem Laptop/Handy diesen Listener nie erreichen.
Dieses Skript umgeht genau das: es baut dieselbe Autorisierungs-URL wie
`hermes auth spotify` (identischer PKCE-Code, identischer Redirect-URI),
aber statt auf den Callback zu warten, fragt es Dich nach dem `code`, den
Du von Hand aus der Adressleiste kopierst (der Redirect schlaegt fehl, weil
nichts auf 127.0.0.1:43827 Deines eigenen Geraets lauscht -- die Adresse
mit dem code-Parameter bleibt aber sichtbar). Das Ergebnis wird 1:1 in
~/.hermes/auth.json geschrieben, exakt im selben Format wie beim normalen
Login -- Hermes' Auto-Refresh (resolve_spotify_runtime_credentials)
funktioniert danach unveraendert.
Nutzung (im hermes-agent-Container, wo hermes_cli installiert ist):
docker cp scripts/spotify_manual_auth.py hermes-agent:/tmp/spotify_manual_auth.py
docker exec -it hermes-agent python3 /tmp/spotify_manual_auth.py
Du kannst dieselbe Spotify-Dev-App wie fuer ARIA wiederverwenden -- dafuer
nur im Spotify-Dashboard (developer.spotify.com/dashboard -> eure App ->
Settings -> Redirect URIs) zusaetzlich
http://127.0.0.1:43827/spotify/callback
eintragen. Ein Client-Secret wird hier NICHT gebraucht (PKCE, public
client) -- nur die Client-ID.
"""
from __future__ import annotations
import sys
import uuid
try:
from hermes_cli.auth import (
_auth_store_lock,
_load_auth_store,
_save_auth_store,
_spotify_accounts_base_url,
_spotify_api_base_url,
_spotify_build_authorize_url,
_spotify_client_id,
_spotify_code_challenge,
_spotify_code_verifier,
_spotify_exchange_code_for_tokens,
_spotify_redirect_uri,
_spotify_scope_string,
_spotify_token_payload_to_state,
_store_provider_state,
get_provider_auth_state,
AuthError,
)
except ImportError as exc: # pragma: no cover
sys.exit(
"Konnte hermes_cli.auth nicht importieren -- Skript muss INNERHALB "
f"des hermes-agent-Containers laufen (python3 dort). Fehler: {exc}"
)
def main() -> None:
existing_state = get_provider_auth_state("spotify") or {}
default_client_id = ""
try:
default_client_id = _spotify_client_id(state=existing_state)
except AuthError:
pass
prompt = "Spotify Client-ID"
prompt += f" [{default_client_id}]: " if default_client_id else ": "
client_id = input(prompt).strip() or default_client_id
if not client_id:
sys.exit("Keine Client-ID angegeben -- Abbruch.")
redirect_uri = _spotify_redirect_uri(state=existing_state)
scope = _spotify_scope_string(existing_state.get("scope"))
accounts_base_url = _spotify_accounts_base_url(existing_state)
api_base_url = _spotify_api_base_url(existing_state)
code_verifier = _spotify_code_verifier()
code_challenge = _spotify_code_challenge(code_verifier)
state_nonce = uuid.uuid4().hex
authorize_url = _spotify_build_authorize_url(
client_id=client_id,
redirect_uri=redirect_uri,
scope=scope,
state=state_nonce,
code_challenge=code_challenge,
accounts_base_url=accounts_base_url,
)
print()
print("=" * 78)
print("1) Diese URL in IRGENDEINEM Browser oeffnen (Handy reicht) und bei")
print(" Spotify einloggen + 'Zugriff erlauben' bestaetigen:")
print()
print(authorize_url)
print()
print(f" (Diese Redirect-URI muss im Spotify-Dashboard eingetragen sein: {redirect_uri})")
print()
print("2) Der Browser versucht danach zu http://127.0.0.1:43827/... zu")
print(" springen und wird das NICHT laden koennen -- das ist erwartet,")
print(" da nichts auf DEINEM Geraet dort lauscht. Die Adresse bleibt in")
print(" der Adressleiste sichtbar (oder in der Fehlermeldung).")
print("3) Den Wert hinter 'code=' kopieren (bis exklusive '&state=').")
print("=" * 78)
print()
pasted_code = input("code=... hier einfuegen: ").strip()
if not pasted_code:
sys.exit("Kein Code eingegeben -- Abbruch.")
pasted_state = input(
f"state=... hier einfuegen (erwartet '{state_nonce}', Enter zum Ueberspringen): "
).strip()
if pasted_state and pasted_state != state_nonce:
sys.exit(
"State-Wert stimmt nicht mit dem erwarteten ueberein -- Abbruch "
"(evtl. eine alte URL/Fenster benutzt, bitte Skript neu starten)."
)
print("\nTausche Code gegen Access-/Refresh-Token...")
token_payload = _spotify_exchange_code_for_tokens(
client_id=client_id,
code=pasted_code,
redirect_uri=redirect_uri,
code_verifier=code_verifier,
accounts_base_url=accounts_base_url,
)
spotify_state = _spotify_token_payload_to_state(
token_payload,
client_id=client_id,
redirect_uri=redirect_uri,
requested_scope=scope,
accounts_base_url=accounts_base_url,
api_base_url=api_base_url,
)
with _auth_store_lock():
auth_store = _load_auth_store()
_store_provider_state(auth_store, "spotify", spotify_state, set_active=False)
saved_to = _save_auth_store(auth_store)
print()
print(f"Spotify-Login gespeichert unter: {saved_to}")
print("providers.spotify ist jetzt gesetzt.")
print("Nicht vergessen: `docker exec -it hermes-agent hermes tools` -> Spotify aktivieren, falls noch nicht geschehen.")
if __name__ == "__main__":
main()