157 lines
5.8 KiB
Python
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()
|