Commit Graph
35 Commits
Author SHA1 Message Date
Tobias GesellchenandClaude Sonnet 4.6 9172072601 feat: add source removal — health check, API endpoint, and CLI commands
Health check (checks_stale_internet_radio.go): detects stub INTERNET_RADIO
sources (empty credentials) left on devices initialised before the stub was
removed from the default source list. Quick-fix removes by ID; skips any
INTERNET_RADIO source that has real credentials.

Datastore: DeleteSourceByID and DeleteSourceByType (uniqueness-guarded).

API: DELETE /setup/sources/{account}/{device}/{sourceID}

CLI — two new commands:
  soundtouch-cli cloud source remove --service-url ... --account ... --device ... [--id 10002 | --type INTERNET_RADIO]
    Talks to AfterTouch (service side). --type resolves to canonical ID
    locally; fails for unknown types.
  soundtouch-cli source notify-updated --host <speaker-ip>
    Talks to the speaker directly. Fetches device ID from /info, then
    POSTs sourcesUpdated to :8090/notification so the speaker re-fetches
    its source list immediately.

CloudCommonFlags (--service-url / AFTERTOUCH_URL) mirrors CommonFlags
(--host) for AfterTouch-facing command groups.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 22:45:15 +02:00
Tobias GesellchenandClaude Sonnet 4.6 11a6515f4d feat(tunein): add section-grouped results and load-more pagination
TuneIn's profiles API caps initial results at ~10 per container (Stations,
Shows, etc.) and exposes a Pivots.More.Url cursor for the remainder. This
change wires that cursor through the stack so users can load additional
results without leaving the search view.

- tuneInSearchSection now extracts Pivots.More.Url as bmx_next when
  itemToken is present; absent for containers already at their limit
- TuneInSearchNext fetches the cursor URL, which returns a flat Items[]
  (not nested containers), and maps Station/Program/Topic items using
  the existing play/profile builders
- New GET /v1/search/next and /api/tunein/search/next endpoints with
  matching handlers in both service paths
- TuneInBrowser: flat items state replaced with per-section sections
  state; each section shows a header label and a Load more button when
  a cursor is available; browse/navigate mode is unaffected

Relates to #336.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 14:25:01 +02:00
Tobias GesellchenandClaude Sonnet 4.6 3cfb3da498 feat(export): encrypted diagnostic report for issue reporting
Adds a "Download diagnostic report" button on the Health tab that
produces an age-encrypted .age file the user can attach to a GitHub
issue without exposing sensitive data.

Archive contents (tar.gz, then age-encrypted with the maintainer's
SSH ed25519 public key):
- diagnostic.json         structured health/device summary (no secrets)
- datastore/…/*.xml       raw on-disk XML verbatim for diff vs HTTP
- http/service/…          live service HTTP responses per account/device
- http/speaker/…          live speaker API responses (port 8090)
- ssh/speaker/…           CA bundles + logread (last 20 min, 127.0.0.1
                          filtered) + dmesg fetched via SSH
- system/ca.pem           service CA cert
- system/resolv.conf      host DNS resolver config
- settings.json           service settings (OAuth secrets redacted)
- env.txt                 filtered process environment
- logs/service.txt        in-memory service log buffer

Supporting tooling:
- scripts/setup-diagnostic-key.sh  one-time SSH key-pair generation
- scripts/decrypt-diagnostic.go    go run helper for maintainer decryption
- keys/public/diagnostic.pub       committed public key (matches github.com/gesellix.keys)
- docs/DIAGNOSTIC-EXPORT.md        maintainer setup + user workflow guide
- docs/concepts/ENCRYPTED-EXPORT.md  research notes and architecture rationale

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 20:02:57 +02:00
Tobias GesellchenandClaude Opus 4.7 29d611f9c1 feat(health): aggregate device-summary panel on Devices tab
Audit item #1 (11+ recurrences in issues / discussions): pull
speaker /info + /sources + /presets, plus service-side state
and pairing inference, into one view per device.

Backend: GET /setup/device-summary/{deviceId} probes the three
speaker endpoints concurrently (sync.WaitGroup, 3 s per probe)
and merges the result with what the datastore knows for the
same device. Partial failures don't break the response — each
sub-section carries its own reachability + error + curl_command
so the UI can render copy-paste fallbacks when the service host
can't reach the speaker.

JSON shape covers four panels:
  - device      identity + firmware
  - speaker     {info, sources, presets} with raw outcomes
  - service     server URL, expected hosts, Sources.xml /
                Presets.xml presence and counts
  - pairing     paired flag, marge host, host match

UI: new "Inspect" button per row on the Devices tab. Clicking
expands a sibling row with five summary cards (info / sources /
presets / service / pairing). Each unreachable card renders the
matching curl command with a Copy button — same dual-mode
pattern as Health findings. Closes the gap operators were
filling by manually concatenating curl output across the three
speaker endpoints when filing bug reports.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 23:20:40 +02:00
Tobias GesellchenandClaude Opus 4.7 147a69d1c3 refactor(ding): synthesise on demand instead of vendoring the WAV
Move the ding renderer into pkg/service/ding so it can run both
at request time (from the new HandleDing handler) and offline
(from the existing scripts/gen-aftertouch-ding CLI, now a thin
wrapper around the same package).

- GET /media/aftertouch-ding.wav synthesises on first call,
  caches the default-options bytes via sync.Once, and accepts
  query-string overrides for every knob (pitch-{high,mid,low},
  chirp-ms, gap-ms, attack-ms, release-ms, sample-rate, peak).
  Invalid / out-of-range values silently fall back to defaults.
- Embedded WAV is gone from VCS — no 52 KB binary in the
  repo, and tweaking the sound is now a query-param away rather
  than a regenerate-and-commit cycle.
- Health-tab playback_test check is unchanged: the URL it
  references (/media/aftertouch-ding.wav) keeps the same shape,
  the handler just produces the bytes dynamically now.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 23:20:40 +02:00
Tobias GesellchenandClaude Opus 4.7 c3723dc0e6 feat(service): add Logs tab streaming the live stderr trace
Cloud-deploy operators on Discussion #295 needed to leave the
admin UI for docker logs / journalctl to see what the service was
doing. Mirror log.Default() output into an in-memory ring buffer
and expose it under /setup/logs so the admin UI can show a live
trace alongside the existing tabs.

The buffer is a second sink under log.SetOutput(io.MultiWriter(
os.Stderr, buf)) — stderr keeps receiving every line verbatim,
so docker logs / journalctl are unaffected. Default capacity
2000 lines (~400 KB), tunable via SOUNDTOUCH_LOG_BUFFER_LINES.

- pkg/service/logbuf: io.Writer ring with \n splitting,
  partial-line buffering, monotonic Seq, Since(since, limit)
  reporting dropped count when the caller falls behind.
- New /setup/logs (GET) returns {entries, nextSince, dropped,
  capacity}. Polls at 1.5s while the tab is active; paused on
  document.hidden.
- "8. Logs" tab with substring filter, tail-follow toggle
  (auto-disables when the user scrolls up), monospace dark view.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 20:12:25 +02:00
Tobias GesellchenandClaude Opus 4.7 f791145976 feat(service): add Health tab with datastore checks and quick fixes
Discussion #295 surfaced that a paired device without Sources.xml
silently breaks playback — /full omits TUNEIN and selection fails
with 1005. initializeDefaultSources only runs at startup over
existing devices, so a device that checks in later is never
seeded.

Add a Health tab to the admin UI that runs registered checks
against the datastore and offers one-click remediations. The
first check flags missing Sources.xml per device; its quick fix
writes the canonical defaults via SaveConfiguredSources. The
check/fix registry is designed so adding Presets.xml,
Recents.xml, or future reachability probes is a one-file diff.

- New /setup/health (GET) and /setup/health/fix (POST) routes
- pkg/service/health: Registry, Check, Finding, QuickFix types
- Sources.xml-present check + create_default_sources fix
- "7. Health" tab in pkg/service/handlers/web/

Inspired by issue #327's MAINTENANCE tab proposal; curl/URL
helper content from that issue can slot into the same tab in
a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 20:00:16 +02:00
Tobias GesellchenandClaude Opus 4.7 f8108b0dd9 refactor: rename /setup/proxy-settings → /setup/logging-settings
After the proxy/mirror removal there is no proxy left in the service,
but the parallel partial-update endpoint /setup/proxy-settings stuck
around with its legacy name. It serves a legitimate purpose distinct
from the bulk /setup/settings POST: the three checkboxes
(Redact / Log Bodies / Record) use onchange-triggered live save,
while /setup/settings drives a Save-button form for dozens of fields.
Folding the two endpoints together would either lose the live-toggle
UX or send half-edited draft form data on every toggle, so the
partial-update endpoint earns its keep — it just needed the right
name.

Renamed symbols (no behaviour change):

  Go handler funcs:
    HandleGetProxySettings      → HandleGetLoggingSettings
    HandleUpdateProxySettings   → HandleUpdateLoggingSettings
    GetProxySettings            → GetLoggingSettings

  Route:
    /setup/proxy-settings       → /setup/logging-settings

  JS:
    fetchProxySettings()        → fetchLoggingSettings()
    updateProxySettings()       → updateLoggingSettings()

  HTML element IDs (cosmetic, kept consistent):
    proxy-redact / proxy-log-body / proxy-record
                                → logging-redact / logging-log-body / logging-record

  HTML heading:
    "Proxy Logging:"            → "Logging:"

JSON payload shapes (request + response keys) are UNCHANGED: the
endpoint still emits / accepts {"redact", "log_body", "record"}.
Persisted Settings on disk are UNCHANGED. CLI flags are UNCHANGED.
Server struct fields redactLogs / logBodies / recordEnabled
(renamed earlier this session) are UNCHANGED.

testdata/router_routes.txt regenerated. go build clean. go test
./... clean except pre-existing TestDocsConsistency (untracked-file
issue, unrelated). golangci-lint 0 issues.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 21:53:33 +02:00
Marcin Mennemann 2747d95a8f remove: proxy forwarding to Bose upstream 2026-05-17 21:53:33 +02:00
Marcin Mennemann 0f0a96c0ce remove: mirror middleware and parity comparison with Bose cloud 2026-05-17 21:53:33 +02:00
Tobias GesellchenandClaude Opus 4.7 0038db35d3 test(service): regenerate router_routes golden after SiriusXM routes
The new HandleSiriusXMLiveAdapter and HandleSiriusXMLiveAdapterSubpath
routes were registered via r.HandleFunc (every HTTP method) at the top
level in main.go. The router-shape golden file gets one entry per
(method, path) pair, so SiriusXM adds 14 lines across CONNECT / DELETE
/ GET / HEAD / OPTIONS / PATCH / POST / PUT / TRACE.

Pure regeneration — no behaviour change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 15:05:39 +02:00
Tobias GesellchenandClaude Opus 4.7 ff96430f53 fix(router): consolidate /device subrouter so PUT and DELETE actually resolve
Issue #285's first fix (5f31616) registered the rename PUT inside a
chi subrouter at `/streaming/account/{account}/device`, alongside the
existing POST handlers. A *second* subrouter was already declared at
`/streaming/account/{account}/device/{device}` for the per-device
sub-resources (presets, recent, group, …). chi's radix tree treats
those two registrations as overlapping prefixes and at request time
prefers the more-specific `/device/{device}` subrouter — which had
no root-level method handlers. A PUT to /device/X fell through to
the [UNHANDLED] catch-all, got proxied to streaming.bose.com, came
back as 401 from CloudFront. Speakers retried in a loop.

The handlers-package regression test passed because the test router
in `pkg/service/handlers/main_test.go` is flatter (one subrouter for
device, no `/device/{device}` nested block). The route snapshot
test passed because `chi.Walk` enumerates each subrouter's
registrations independently — it doesn't simulate how the radix tree
will resolve a runtime request when subrouters overlap.

Reproduced against the actual production setupRouter in
TestPUTRenameRoutesToLocalHandler (new in router_test.go). Before
this commit: 404 / [UNHANDLED] / 401 proxy. After: 200 from
HandleMargeUpdateDevice.

Fix: collapse the two subrouters into one. All `/device` routes —
the POST/PUT/DELETE on the device resource itself plus the GET/POST
sub-resources — share a single `r.Route("/device", ...)` block with
explicit `/{device}/...` paths inside. No radix-tree ambiguity.

Knock-on: the `r.Delete("/device/{device}", server.HandleMargeRemoveDevice)`
that lived at the outer `/account/{account}` level moves into the
unified `/device` subrouter for symmetry. Its prior placement was
also being shadowed by the radix overlap, which is why the route
snapshot's first regeneration after this fix grew by exactly one
DELETE line — that route was never resolvable at runtime under the
old structure either.

Refs #285.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 18:08:52 +02:00
Tobias GesellchenandClaude Opus 4.7 554fa78c0b fix(marge): handle the rename PUT speakers fire at /streaming/account/.../device/{id}
Closes issue #285. When the user renames an ST10 via the Bose App or
via `soundtouch-cli name set`, the speaker fires:

  PUT http://<aftertouch>:8000/streaming/account/{accountID}/device/{deviceID}
  Content-Type: application/xml
  <device deviceid="…"><name>NEW</name><macaddress>…</macaddress></device>

The router only had POST registered for that path; PUT fell through
to chi's default handling and the speaker observed HTTP 502 (captured
verbatim in _/i285/Rename.log:38: "SimpleURLFetcher: retry needed,
Curl 0, http 502, retries remaining 0"). The speaker's SimpleURLFetcher
retried the PUT on a 15-second timer, the Bose App showed the rename
spinning indefinitely, and the device's display name never updated on
the AfterTouch side.

Implementation reuses marge.AddDeviceToAccount, which is already an
upsert via ds.SaveDeviceInfo — there's no semantic difference between
"add" and "update" at the persistence layer. The new handler
HandleMargeUpdateDevice differs from HandleMargeAddDevice only in the
HTTP envelope:

  - 200 OK (not 201 Created — this is an update, not a fresh resource)
  - no Location header (the resource already lives at the URL the
    speaker is PUT-ing to)
  - deviceID in the body must match the URL's {device} segment;
    mismatch is a 400 rather than a silent re-key

Registered as `r.Put("/{device}", server.HandleMargeUpdateDevice)`
inside the existing `/streaming/account/{account}/device/` route
group in both cmd/soundtouch-service/main.go and the handlers-package
test router. Router-routes snapshot regenerated.

Test coverage in pkg/service/handlers/issue285_regression_test.go:

  - TestIssue285_RenamePutAcceptedAndPersisted seeds the datastore
    with a device under its original name, replays the literal log
    payload from _/i285/Rename.log:36 against the real router, and
    asserts 200 OK + new name in response body + new name persisted
    on disk. testdata/issue285/rename_request.xml is the captured
    payload byte-for-byte (accountID 3981561, deviceID 884AEAEEBD27,
    rename to "Wohnzimmer SB" — same as the reporter).

  - TestIssue285_RenamePutRejectsMismatchedDeviceID pins the safety
    check: body deviceid != URL {device} → 400.

Closes #285.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 16:48:06 +02:00
Tobias GesellchenandClaude Opus 4.7 098b4f59dd fix(bmx): serve orion at the registry-advertised path, drop the /bmx/ prefix
The BMX registry advertises orion at
`{BMX_SERVER}/core02/svc-bmx-adapter-orion/prod/orion` — no `/bmx/`
prefix. That matches the upstream Bose capture in
pkg/service/handlers/static/bmx_services_ustream.json. But our router
nested both orion routes inside the `/bmx/` chi group, so the speaker
asked `/core02/.../prod/orion/token` and our service routed
`/bmx/core02/.../prod/orion/token` — pure path mismatch. The legacy
preset URLs in issue #218 (LOCAL_INTERNET_RADIO presets pointing at
`https://content.api.bose.io/core02/svc-bmx-adapter-orion/prod/orion/station?data=...`)
also dead-ended for the same reason.

Three changes:

- Move `POST /core02/svc-bmx-adapter-orion/prod/orion/token` from the
  `/bmx/` group to top level so it matches what the registry hands the
  speaker.
- Add the missing `GET /core02/svc-bmx-adapter-orion/prod/orion/station`
  that takes `data` as a query string. The handler reuses
  bmx.PlayCustomStream — base64-decode the JSON blob (streamUrl/
  imageUrl/name) and rewrap it into the standard BmxPlaybackResponse
  shape, exactly the way soundcork's reference impl handles it
  (soundcork main.py:786, bmx.py:720). No auth check on this endpoint:
  `data` is the speaker's own preset payload, there's nothing
  privileged to gate, and the upstream behaviour treats it the same way.
- Drop the local-invention `POST /bmx/orion/v1/playback/station/{data}`
  route. Nothing advertised it, nothing real-world called it, and
  keeping it as a "convenience alias" would have left a misleading
  duplicate next to the canonical path.

TuneIn's `/bmx/tunein/...` routes stay where they are — TuneIn's
upstream baseUrl genuinely is `{BMX_SERVER}/bmx/tunein`, so the chi
group prefix is correct for that one.

Router snapshot regenerated; TestOrionPlayback flipped from
POST `/bmx/orion/v1/playback/station/{data}` to GET
`/core02/...station?data=...` (no auth header); the orion entry in
TestBMXUnauthorized's table is removed (the endpoint isn't authed
anymore, by design).

Refs #218.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 14:13:24 +02:00
Tobias GesellchenandClaude Opus 4.7 c3422ed0d5 fix(marge): accept trailing slash on POST /streaming/account/{id}/group/
SoundTouch 10 firmware 27.x posts the addGroup payload to the Marge
URL with a trailing slash ("/streaming/account/<id>/group/") when the
master is forming a stereo pair. AfterTouch only registered the no-
slash form, so chi returned 404, the master's MargeClient retried
every 15 s, the slave kept connecting to the master's audio transport
but was rejected with "Group STP NOT FOUND" because the master never
finished AddingMaster, and the group eventually reverted -- the symptom
reported in #252.

Register POST /group/ alongside POST /group in both Marge route trees
(the /marge/streaming/... mount and the bare /streaming/... mount that
serves direct device traffic). The GET device-group routes already had
both forms; this brings the POST in line.

Add TestMargeAddGroup_FromSpeakerCapture, which replays the exact
request captured live from BirdyBA's master log: URL with trailing
slash, Authorization Bearer header, vendor Content-Type, and the
minimal XML body (no <senderIPAddress>, no per-role <ipAddress>, no
<status>, no numeric group id). The test failed with 404 before this
change and now returns 201 Created with the proper Location header,
pinning the exact wire contract so future refactors fail loudly.

Refs #252

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 09:05:14 +02:00
Tobias GesellchenandClaude Opus 4.7 0e8ab1cd89 test(service): update routes snapshot after round-trip probe removal
TestPrintRoutes compares the live router against
testdata/router_routes.txt; the deletion commit (ba69fc0) changed the
route set but didn't regenerate the golden file. Drops
/probe/{token}[/*] and /setup/telnet-probe/{deviceId}; adds
/setup/peer-probe/{deviceId}.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 20:31:24 +02:00
Tobias Gesellchen ae04ac3128 fix/update routes test 2026-05-11 00:37:11 +02:00
Tobias GesellchenandClaude Opus 4.7 fb47807f70 feat(telnet): add port-17000 migration method and account pairing
Adds an SSH-free third migration path that drives the SoundTouch device's
diagnostic shell on TCP port 17000, plus a hardened replacement for the
fragile /setMargeAccount HTTP pairing call.

* `pkg/telnet` — new reusable, dependency-free client (sibling of `pkg/ssh`)
  with deadline-driven Dial / Probe / SendCommand / Close. Mock-server tests
  cover happy path, command-not-found, mid-stream close, and the wedged-device
  read-timeout scenario.

* `setup.MigrationMethodTelnet` — runs `sys configuration` for all four URLs
  plus the parallel `envswitch boseurls set` persistence layer that otherwise
  wins on reboot, then verifies with `getpdo CurrentSystemConfiguration`.
  Aborts on the first non-OK response so configuration is never half-written.
  No SSH backup or rw pre-flight (the path is SSH-free by design).

* `setup.PairAccount` — probes :8090/supportedURLs first, time-bounds
  POST /setMargeAccount aggressively (5s connect / 12s total) to avoid the
  hangs reported in #236, and falls back to `envswitch accountid set <id>`
  over telnet when the HTTP endpoint is missing or wedged. Returns a
  PairAccountResult breadcrumb so the UI can show which path actually
  succeeded.

* `setup.Reboot(deviceIP, method)` — gains a RebootMethod selector;
  RebootMethodSSH stays the default (preserving prior behavior),
  RebootMethodTelnet sends `sys reboot` over a fresh telnet session and
  treats the inevitable socket-close as success.

* New endpoints on `/setup`:
  - GET  /account-id-suggestions/{deviceId} — returns the device's current
    margeAccountUUID (from :8090/info) plus known account IDs from the
    datastore, so the UI can offer reuse.
  - POST /pair-account/{deviceId}?account_id=NNNNNNN — invokes PairAccount;
    the existing reboot endpoint reads ?method=ssh|telnet from the query
    string.

* Helpers `IsValidAccountID` (exactly 7 digits) and `GenerateAccountID`
  (crypto/rand, retries on collision against a known-IDs list).

Documentation in docs/analysis/TELNET-MIGRATION-METHOD.md is updated to match
the implementation: bare-URL convention for `soundtouch-service`, no automatic
`sys reboot` (user-initiated via the existing button with a method selector),
and the realised package layout. The /etc/hosts method is intentionally not
exposed in the new flow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 12:43:36 +02:00
Tobias GesellchenandClaude Sonnet 4.6 16ab9dbba1 feat(alexa): stub POST /alexa/certificate with 501 and add voice.api.bose.io to DNS (#200)
Registers HandleAlexaCertificate on POST /alexa/certificate. The handler
logs the device MAC from the request body and returns 501 Not
Implemented with a JSON error explaining that AWS IoT integration is
required to provision Alexa device certificates.

Adds voice.api.bose.io to both /etc/hosts domain lists in setup.go (DNS
intercept was already covered by the bose.io wildcard entry in dns.go).

Relates to https://github.com/gesellix/Bose-SoundTouch/discussions/84

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 21:37:30 +02:00
Tobias GesellchenandClaude Sonnet 4.6 e99c04888c feat: implement missing endpoints and serve static resources from downloads/media hosts (#199)
Endpoints:

- POST /streaming/music/musicprovider/{id}/trial/is_eligible (reuses
is_eligible handler)
- POST /bmx/tunein/v1/favorite/{stationID} with datastore persistence
(SaveTuneInFavorite)
- DELETE /bmx/tunein/v1/favorite/{stationID} (DeleteTuneInFavorite)
- POST /bmx/core02/svc-bmx-adapter-orion/prod/orion/token (anonymous
Orion token)
- GET /bmx-icons/* serving embedded static/media assets (media.bose.io)
- GET /ced/* serving embedded firmware index, release notes, and 10
app-help XMLs (downloads.bose.com)

Add media.bose.io and downloads.bose.com to DNS redirect lists (setup.go
both domain slices, dns.go shouldIntercept list, main.go getDomains
map). Document implemented endpoints in
tests/interactions_20260502_missing_external.md; mark rows 0246–0247 as
self/☑ in the interactions table.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 21:22:36 +02:00
Tobias GesellchenandClaude Sonnet 4.6 cb037df831 feat: add Amazon Music management handlers and CLI wiring
- Add HandleMgmtAmazonInit/Callback/Confirm/Accounts/Token/PrimeDeviceAmazon
- Add bridgeAmazonToMarge (AmazonSecret JSON envelope, Marge registration, speaker notification with OAuth/sync/legacy fallbacks)
- Add PrimeDeviceWithAmazon and pushAmazonTokenToDevice to Server
- Wire --amazon-client-id/secret/redirect-uri/token-url/profile-url CLI flags
- Initialize Amazon service on startup alongside Spotify
- Register /mgmt/amazon/* routes (callback unauthenticated, rest Basic Auth)
- Update router_routes.txt snapshot with 6 new Amazon routes
- Fix errchkjson lint: use typed amazon.Account in test fixtures
- Fix gocyclo lint: extract initMusicServices helper from main action

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 20:30:06 +02:00
Tobias Gesellchen 1bf083ae0d Add endpoint for handling Amazon token exchange 2026-04-29 20:30:06 +02:00
Tobias GesellchenandClaude Sonnet 4.6 9412b5ffa0 feat: add group CRUD endpoints (POST add, POST modify, DELETE delete) (#191)
Groups (stereo pairs of ST10 speakers) were read-only — the GET endpoint
always returned an empty <group/>. Add POST /account/{account}/group,
POST /account/{account}/group/{groupId}, and DELETE
/account/{account}/group/{groupId} with datastore persistence, matching
the API shape observed in soundcork. The GET endpoint now reads live
group state from the datastore.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 15:28:38 +02:00
Tobias Gesellchen 382567d67d Add .../api_versions.xml and .../musicprovider/{providerID}/is_eligible (#150) 2026-04-05 23:25:30 +02:00
Tobias Gesellchen de96b1f119 Add /streaming/account/{account}/presets/all (#149) 2026-04-05 23:10:53 +02:00
Tobias Gesellchen d22dc99c9e Add /streaming/account/{account}/devices (#148) 2026-04-05 10:16:22 +02:00
Tobias Gesellchen bd0e3d64a3 Add /streaming/account/{account}/sources (#147) 2026-04-05 01:09:02 +02:00
Tobias Gesellchen 379ac758f6 Add /bmx/tunein/v1/navigate and /bmx/tunein/v1/search (dummy) 2026-04-05 00:55:40 +02:00
Tobias Gesellchen 6d0b5f2c78 Add /bmx/registry/v1/servicesAvailability 2026-04-05 00:55:40 +02:00
Tobias Gesellchen f354c63bac Add /v1/report (#145)
https://github.com/gesellix/Bose-SoundTouch/issues/135
2026-04-04 23:23:04 +02:00
Tobias Gesellchen 50e45ab5f2 Add/improve e2e test cases (#144)
https://github.com/gesellix/Bose-SoundTouch/issues/135
2026-04-04 21:05:34 +02:00
Tobias Gesellchen cc92430e69 Add /blacklist handler 2026-04-04 18:53:59 +02:00
aa7b2c28ab feat: improve Bose SoundTouch parity, Spotify integration, and data reliability (#138)
feat: improve Bose SoundTouch parity, Spotify integration, and data
reliability

- Update XML marshaling for ServicePreset and ServiceRecent to match
Bose parity requirements.
- Add support for adding music sources via
`/streaming/account/{account}/source`.
- Implement HandleBoseAccountToken for Spotify OAuth code exchange and
token persistence.
- Implement atomic file writes in the datastore to prevent data
corruption.
- Add startup logic to initialize default sources for existing devices.
- Expand test coverage with new parity regression and Spotify
integration tests.

---------

Co-authored-by: Junie <junie@jetbrains.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-04-01 21:55:52 +02:00
Tobias Gesellchen 71e3260823 Extend TuneIn support, add e2e tests 2026-03-30 00:52:00 +02:00
Tobias Gesellchen 766671f02b Cleanup, snapshot all routes 2026-03-29 19:14:48 +02:00