Fix account swaping
Deploy HEOS panel / deploy (push) Successful in 26s

This commit is contained in:
2026-09-18 02:05:48 +02:00
parent 053be20b9c
commit 5ca504c680
10 changed files with 885 additions and 21 deletions
+34 -3
View File
@@ -1,6 +1,6 @@
"""Stand-in Spotify Web API: just enough of /api/token, .../player/devices,
.../player and .../player/seek to test spotify.py against, the same way fakes.py stands in
for real HEOS hardware."""
.../player, .../player/pause and .../player/seek to test spotify.py against, the same way
fakes.py stands in for real HEOS hardware."""
import json
import threading
@@ -23,8 +23,11 @@ class FakeSpotify(threading.Thread):
self.tokens_issued = 0
self.transfers = [] # every PUT /v1/me/player body
self.seeks = [] # every PUT /v1/me/player/seek's position_ms
self.pauses = 0 # how many PUT /v1/me/player/pause it took
self.reject_refresh = False # simulate a revoked refresh token
self.scope = "user-read-playback-state user-modify-playback-state"
self.player = None # GET /v1/me/player's body; None is no session (204)
self.forbid = None # a plain-text 403 body every API call answers with
fake = self
@@ -39,6 +42,19 @@ class FakeSpotify(threading.Thread):
if payload is not None:
self.wfile.write(json.dumps(payload).encode())
def _forbidden(self):
"""The real one refuses an account the developer app has not
listed with a bare sentence, not the usual JSON error object."""
if fake.forbid is None:
return False
body = fake.forbid.encode()
self.send_response(403)
self.send_header("Content-Type", "text/plain")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
return True
def _authorized(self):
header = self.headers.get("Authorization", "")
return header == f"Bearer {fake.valid_token}" and fake.valid_token is not None
@@ -51,11 +67,14 @@ class FakeSpotify(threading.Thread):
return
fake.tokens_issued += 1
fake.valid_token = f"token-{fake.tokens_issued}"
self._send(200, {"access_token": fake.valid_token, "expires_in": 3600})
self._send(200, {"access_token": fake.valid_token, "expires_in": 3600,
"scope": fake.scope})
return
self._send(404, {"error": {"message": "not found"}})
def do_PUT(self):
if self._forbidden():
return
if self.path == "/v1/me/player":
if not self._authorized():
self._send(401, {"error": {"message": "The access token expired"}})
@@ -65,6 +84,16 @@ class FakeSpotify(threading.Thread):
self.send_response(204)
self.end_headers()
return
if self.path == "/v1/me/player/pause":
if not self._authorized():
self._send(401, {"error": {"message": "The access token expired"}})
return
fake.pauses += 1
if fake.player is not None:
fake.player["is_playing"] = False
self.send_response(204)
self.end_headers()
return
if self.path.startswith("/v1/me/player/seek?"):
if not self._authorized():
self._send(401, {"error": {"message": "The access token expired"}})
@@ -83,6 +112,8 @@ class FakeSpotify(threading.Thread):
self._send(404, {"error": {"message": "not found"}})
def do_GET(self):
if self._forbidden():
return
if self.path == "/v1/me/player/devices":
if not self._authorized():
self._send(401, {"error": {"message": "The access token expired"}})