mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-18 16:46:17 +00:00
feat(bmx): SiriusXM live-adapter logging stub
bmx_services.json advertises SIRIUSXM_EVEREST at
`{BMX_SERVER}/core02/svc-bmx-adapter-siriusxm-everest-eco1/prod/live-adapter`
and bmx_services_availability.json lists it as available, so speakers
that try SiriusXM hit that path. Without a route we 404'd silently
and the call was invisible in our logs.
- HandleSiriusXMLiveAdapter at the bare base URL returns the
SIRIUSXM_EVEREST service descriptor (selected by id.name from
bmx_services.json, with {BMX_SERVER}/{MEDIA_SERVER} substitution).
Mirrors deborahgu/soundcork main.py:805 in shape.
- HandleSiriusXMLiveAdapterSubpath catches every sub-path advertised
by the descriptor's _links (/availability, /token, /navigate,
/logout) plus the playback URLs the speaker discovers via navigate.
Logs the request with method+path+UA+Authorization+RawQuery, then
404s — giving the next implementation pass concrete data about
what the speaker actually asks for.
Two helpers added to handlers_bmx.go (shared with any future
BMX-segment stub):
- extractBMXService(json, name) — find a service entry by id.name.
- (*Server).applyBMXTemplate(content) — {BMX_SERVER}/{MEDIA_SERVER}
substitution, identical to what HandleBMXRegistry does inline.
Routes registered next to Orion at the top level — same convention
(no /bmx/ prefix) because bmx_services.json advertises baseUrl without
that prefix and speakers reach the path verbatim under either
migration mode.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
b9c1cdad29
commit
2df0adf4e3
@@ -953,6 +953,16 @@ func setupRouter(server *handlers.Server, stockholmHandler *stockholm.Handler) *
|
||||
r.Post("/core02/svc-bmx-adapter-orion/prod/orion/token", server.HandleOrionToken)
|
||||
r.Get("/core02/svc-bmx-adapter-orion/prod/orion/station", server.HandleOrionPlayback)
|
||||
|
||||
// SiriusXM lives at the top level by the same convention. bmx_services.json
|
||||
// advertises baseUrl `{BMX_SERVER}/core02/svc-bmx-adapter-siriusxm-everest-eco1/prod/live-adapter`
|
||||
// (no /bmx/ prefix), so speakers reach this exact path under either
|
||||
// migration mode. The bare path returns the service descriptor (matches
|
||||
// soundcork main.py:805); sub-paths advertised by the descriptor's _links
|
||||
// (/availability, /navigate, /token, /logout) currently log + 404 so
|
||||
// future implementation work has visibility into real speaker calls.
|
||||
r.HandleFunc("/core02/svc-bmx-adapter-siriusxm-everest-eco1/prod/live-adapter", server.HandleSiriusXMLiveAdapter)
|
||||
r.HandleFunc("/core02/svc-bmx-adapter-siriusxm-everest-eco1/prod/live-adapter/*", server.HandleSiriusXMLiveAdapterSubpath)
|
||||
|
||||
r.Get("/custom/v1/playback/{encodedURL}", server.HandleCustomPlayback)
|
||||
|
||||
r.Route("/streaming", func(r chi.Router) {
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
@@ -43,6 +45,60 @@ func (s *Server) HandleBMXServicesAvailability(w http.ResponseWriter, _ *http.Re
|
||||
_, _ = w.Write(bmxServicesAvailabilityJSON)
|
||||
}
|
||||
|
||||
// extractBMXService finds a single service entry in bmx_services.json by
|
||||
// its `id.name` (e.g. "SIRIUSXM_EVEREST", "TUNEIN"). Returns the raw JSON
|
||||
// segment for that service so callers can apply {BMX_SERVER} / {MEDIA_SERVER}
|
||||
// substitution and write it back to the wire.
|
||||
func extractBMXService(bmxJSON []byte, name string) (json.RawMessage, error) {
|
||||
var wrapper struct {
|
||||
BMXServices []json.RawMessage `json:"bmx_services"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(bmxJSON, &wrapper); err != nil {
|
||||
return nil, fmt.Errorf("parse bmx_services.json: %w", err)
|
||||
}
|
||||
|
||||
for _, raw := range wrapper.BMXServices {
|
||||
var idOnly struct {
|
||||
ID struct {
|
||||
Name string `json:"name"`
|
||||
} `json:"id"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(raw, &idOnly); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if idOnly.ID.Name == name {
|
||||
return raw, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("service %q not found in bmx_services.json", name)
|
||||
}
|
||||
|
||||
// applyBMXTemplate runs the same {BMX_SERVER} / {MEDIA_SERVER} substitution
|
||||
// HandleBMXRegistry uses, so service-descriptor responses produced from
|
||||
// sub-segments of bmx_services.json land at the same hostnames the
|
||||
// registry advertises.
|
||||
func (s *Server) applyBMXTemplate(content string) string {
|
||||
baseURL := s.serverURL
|
||||
|
||||
s.mu.RLock()
|
||||
dnsEnabled := s.dnsEnabled
|
||||
s.mu.RUnlock()
|
||||
|
||||
bmxServer := baseURL
|
||||
if dnsEnabled {
|
||||
bmxServer = "https://content.api.bose.io"
|
||||
}
|
||||
|
||||
content = strings.ReplaceAll(content, "{BMX_SERVER}", bmxServer)
|
||||
content = strings.ReplaceAll(content, "{MEDIA_SERVER}", baseURL+"/media")
|
||||
|
||||
return content
|
||||
}
|
||||
|
||||
// writeBMXUnauthorized writes the canonical 401 used by every BMX adapter
|
||||
// handler that requires an Authorization header (TuneIn variants, Orion
|
||||
// playback). Currently unused because all gate sites are temporarily
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
// Package handlers — SiriusXM BMX adapter (logging stub).
|
||||
//
|
||||
// bmx_services.json declares SIRIUSXM_EVEREST at
|
||||
// `{BMX_SERVER}/core02/svc-bmx-adapter-siriusxm-everest-eco1/prod/live-adapter`,
|
||||
// and bmx_services_availability.json lists it as available — so speakers
|
||||
// that try SiriusXM hit this path. The bare URL returns the service
|
||||
// descriptor; sub-paths advertised by the descriptor's _links
|
||||
// (/availability, /token, /navigate, /logout, plus the playback paths
|
||||
// the speaker discovers via navigate) currently log + 404 so we have
|
||||
// visibility into real speaker calls for the next implementation pass.
|
||||
//
|
||||
// Reference: deborahgu/soundcork main.py:805 takes the same shape —
|
||||
// returns the SiriusXM service descriptor from the BMX services array
|
||||
// (hardcoded index 2). We select by id.name instead of array index.
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// HandleSiriusXMLiveAdapter returns the SIRIUSXM_EVEREST service descriptor
|
||||
// from bmx_services.json for the bare live-adapter base URL.
|
||||
func (s *Server) HandleSiriusXMLiveAdapter(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("[BMX SiriusXM] %s %s ua=%q auth=%q query=%q",
|
||||
r.Method, r.URL.Path, r.UserAgent(),
|
||||
r.Header.Get("Authorization"), r.URL.RawQuery)
|
||||
|
||||
svc, err := extractBMXService(bmxServicesJSON, "SIRIUSXM_EVEREST")
|
||||
if err != nil {
|
||||
log.Printf("[BMX SiriusXM] failed to extract service descriptor: %v", err)
|
||||
http.Error(w, "service descriptor unavailable", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
body := s.applyBMXTemplate(string(svc))
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(body))
|
||||
}
|
||||
|
||||
// HandleSiriusXMLiveAdapterSubpath logs and 404s any unimplemented sub-path
|
||||
// under the SiriusXM live-adapter. Visibility for the next implementation
|
||||
// pass — the _links in the descriptor publish /availability, /token,
|
||||
// /navigate, /logout; playback URLs come dynamically from navigate.
|
||||
func (s *Server) HandleSiriusXMLiveAdapterSubpath(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("[BMX SiriusXM] UNIMPLEMENTED %s %s ua=%q auth=%q query=%q",
|
||||
r.Method, r.URL.Path, r.UserAgent(),
|
||||
r.Header.Get("Authorization"), r.URL.RawQuery)
|
||||
|
||||
http.Error(w, "not implemented", http.StatusNotFound)
|
||||
}
|
||||
Reference in New Issue
Block a user