0.6: Spotify OAuth connect flow, AzuraCast base URL, fingerprint/buy-url fixes

- Add "Connect Spotify" OAuth flow (/connect/spotify) so newly created Spotify
  apps can read playlists: Spotify now requires a user token for playlist
  reads, which client-credentials alone can no longer provide. The stored
  refresh token is rendered as spotify-refresh into each playlist's sldl
  .conf -- sldl's own docs confirm supplying it skips its interactive login
  flow, which is what was causing multi-hour hangs on a stuck sync.
  Grandfathered apps keep working unchanged if no account is connected.
- Fix the connect flow's redirect_uri: request.url_for() reflected the raw
  connection scheme (http) rather than what the reverse proxy actually
  served over, which Spotify's exact-match redirect_uri check rejected.
  Force https rather than trust the unproxied scheme.
- Add an AzuraCast base URL credential field alongside the API key, with a
  keyfile fallback so the scheduled enrich-buy-url.py job can actually reach
  AzuraCast (previously it had no way to receive a base URL at all when run
  from the scheduler, so the reprocess call was silently always skipped).
- Fix build-fingerprint-index.py exiting non-zero on any single fingerprint
  failure instead of only when nothing was written.
- Guard enrich-buy-url.py's AzuraCast reprocess against an empty
  --azuracast-base (raised ValueError since PD2 removed the hardcoded base).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
andrew
2026-07-14 11:16:55 -06:00
parent 91f3982eff
commit e5d9c7f043
18 changed files with 280 additions and 47 deletions
+1 -1
View File
@@ -63,7 +63,7 @@
<h2>Credential status</h2>
<ul class="chip-list auth-chip-list">
{% for a in auth_states %}
<li title="{{ a.scope }}: {{ 'configured' if a.configured else 'not configured' }}{% if a.days_left is defined %}, cookie expires in {{ a.days_left }}d{% endif %}"><span class="status-dot {{ 'status-dot-success' if a.configured else 'status-dot-muted' }}{% if a.days_left is defined and a.days_left < 14 %} status-dot-warning{% endif %}"></span>{{ a.scope }}{% if a.days_left is defined %}<span class="muted"> {{ a.days_left }}d</span>{% endif %}</li>
<li title="{{ a.scope }}: {{ 'configured' if a.configured else 'not configured' }}{% if a.days_left is defined %}, cookie expires in {{ a.days_left }}d{% endif %}{% if a.spotify_connected is defined %}, {{ 'OAuth connected' if a.spotify_connected else 'app-only (client-credentials)' }}{% endif %}"><span class="status-dot {{ 'status-dot-success' if a.configured else 'status-dot-muted' }}{% if a.days_left is defined and a.days_left < 14 %} status-dot-warning{% endif %}"></span>{{ a.scope }}{% if a.days_left is defined %}<span class="muted"> {{ a.days_left }}d</span>{% endif %}{% if a.spotify_connected %}<span class="muted"> (OAuth)</span>{% endif %}</li>
{% endfor %}
</ul>
<p><a href="/settings/credentials">Manage credentials &rarr;</a></p>