mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-24 14:47:23 +00:00
Add /v1/report (#145)
https://github.com/gesellix/Bose-SoundTouch/issues/135
This commit is contained in:
@@ -720,6 +720,7 @@ func setupRouter(server *handlers.Server) *chi.Mux {
|
||||
r.Get("/v1/playback/episodes/{podcastID}", server.HandleTuneInPodcastInfo)
|
||||
r.Get("/v1/playback/episode/{podcastID}", server.HandleTuneInPlaybackPodcast)
|
||||
r.Post("/v1/token", server.HandleTuneInToken)
|
||||
r.Post("/v1/report", server.HandleTuneInReport)
|
||||
})
|
||||
|
||||
r.Post("/orion/v1/playback/station/{data}", server.HandleOrionPlayback)
|
||||
|
||||
@@ -74,6 +74,7 @@ POST /accounts/{account}/devices handlers.(
|
||||
POST /accounts/{account}/devices/{device}/presets/{presetNumber} handlers.(*Server).HandleMargeUpdatePreset-fm
|
||||
POST /accounts/{account}/devices/{device}/recents handlers.(*Server).HandleMargeAddRecent-fm
|
||||
POST /bmx/orion/v1/playback/station/{data} handlers.(*Server).HandleOrionPlayback-fm
|
||||
POST /bmx/tunein/v1/report handlers.(*Server).HandleTuneInReport-fm
|
||||
POST /bmx/tunein/v1/token handlers.(*Server).HandleTuneInToken-fm
|
||||
POST /customer/account/{account} handlers.(*Server).HandleMargeUpdateAccountProfile-fm
|
||||
POST /customer/account/{account}/password handlers.(*Server).HandleMargeChangePassword-fm
|
||||
|
||||
@@ -201,3 +201,42 @@ func (s *Server) HandleCustomPlayback(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// HandleTuneInReport handles TuneIn playback reporting.
|
||||
func (s *Server) HandleTuneInReport(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Header.Get("Authorization") == "" {
|
||||
s.writeBMXUnauthorized(w)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
EventType string `json:"eventType"`
|
||||
}
|
||||
|
||||
// We don't strictly need the body to determine the response,
|
||||
// but we decode it to see the eventType.
|
||||
_ = json.NewDecoder(r.Body).Decode(&req)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
if req.EventType == "START" {
|
||||
// Mirroring the response from 0196-20260329-233306.072-POST.http
|
||||
resp := map[string]interface{}{
|
||||
"_links": map[string]interface{}{
|
||||
"self": map[string]interface{}{
|
||||
"href": "/v1/report?" + r.URL.RawQuery,
|
||||
},
|
||||
},
|
||||
"nextReportIn": 1800,
|
||||
}
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// For STOP and other events, return an empty object
|
||||
_, _ = w.Write([]byte("{}"))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHandleTuneInReport(t *testing.T) {
|
||||
r, s := setupRouter("http://localhost:8001", nil)
|
||||
s.SetMirrorSettings(false, nil, nil, "")
|
||||
|
||||
ts := httptest.NewServer(r)
|
||||
defer ts.Close()
|
||||
|
||||
t.Run("START event", func(t *testing.T) {
|
||||
payload := `{"timeStamp":"2026-03-29T21:33:04+0000","eventType":"START","reason":"USER_SELECT_PLAYABLE","timeIntoTrack":0,"playbackDelay":7419}`
|
||||
req, _ := http.NewRequest("POST", ts.URL+"/bmx/tunein/v1/report?stream_id=e536753726&guide_id=s166521&listen_id=1774819980&stream_type=liveRadio", strings.NewReader(payload))
|
||||
req.Header.Set("Authorization", "Bearer mock-token")
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
t.Errorf("Expected status 200, got %v", res.Status)
|
||||
}
|
||||
|
||||
var resp map[string]interface{}
|
||||
if err := json.NewDecoder(res.Body).Decode(&resp); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if resp["nextReportIn"] != float64(1800) {
|
||||
t.Errorf("Expected nextReportIn 1800, got %v", resp["nextReportIn"])
|
||||
}
|
||||
links := resp["_links"].(map[string]interface{})
|
||||
self := links["self"].(map[string]interface{})
|
||||
if !strings.Contains(self["href"].(string), "/v1/report") {
|
||||
t.Errorf("Expected href to contain /v1/report, got %v", self["href"])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("STOP event", func(t *testing.T) {
|
||||
payload := `{"timeStamp":"2026-03-29T21:33:44+0000","eventType":"STOP","reason":"USER_STOP","timeIntoTrack":39,"playbackDelay":0}`
|
||||
req, _ := http.NewRequest("POST", ts.URL+"/bmx/tunein/v1/report?stream_id=e536753726&guide_id=s166521&listen_id=1774819980&last_titt=0&duration_balance=0&stream_type=liveRadio", strings.NewReader(payload))
|
||||
req.Header.Set("Authorization", "Bearer mock-token")
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
t.Errorf("Expected status 200, got %v", res.Status)
|
||||
}
|
||||
|
||||
var resp map[string]interface{}
|
||||
if err := json.NewDecoder(res.Body).Decode(&resp); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(resp) != 0 {
|
||||
t.Errorf("Expected empty response object, got %v", resp)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Unauthorized", func(t *testing.T) {
|
||||
req, _ := http.NewRequest("POST", ts.URL+"/bmx/tunein/v1/report", nil)
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusUnauthorized {
|
||||
t.Errorf("Expected status 401, got %v", res.Status)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -27,6 +27,7 @@ func setupRouter(targetURL string, ds *datastore.DataStore) (*chi.Mux, *Server)
|
||||
r.Get("/tunein/v1/playback/episodes/{podcastID}", server.HandleTuneInPodcastInfo)
|
||||
r.Get("/tunein/v1/playback/episode/{podcastID}", server.HandleTuneInPlaybackPodcast)
|
||||
r.Post("/tunein/v1/token", server.HandleTuneInToken)
|
||||
r.Post("/tunein/v1/report", server.HandleTuneInReport)
|
||||
r.Post("/orion/v1/playback/station/{data}", server.HandleOrionPlayback)
|
||||
})
|
||||
|
||||
|
||||
@@ -197,9 +197,9 @@ Interactions for `20260328-103522-477978/`:
|
||||
| 0193 | mirror | ☑ | POST /v1/scmudc/{{device_id}} | 200 OK | ./mirror/v1/scmudc/{device_id}/0193-20260329-233304.685-POST.http |
|
||||
| 0194 | self | ☑ | POST /v1/scmudc/{{device_id}} | 200 OK | ./self/v1/scmudc/{device_id}/0194-20260329-233305.629-POST.http |
|
||||
| 0195 | mirror | ☑ | POST /v1/scmudc/{{device_id}} | 200 OK | ./mirror/v1/scmudc/{device_id}/0195-20260329-233306.040-POST.http |
|
||||
| 0196 | mirror | | POST /bmx/tunein/v1/report?stream_id=e536753726&guide_id=s166521&listen_id=1774819980&stream_type=liveRadio | 200 OK | ./mirror/bmx/tunein/v1/report/0196-20260329-233306.072-POST.http |
|
||||
| 0197 | upstream | | POST https://content.api.bose.io/bmx/tunein/v1/report?stream_id=e536753726&guide_id=s166521&listen_id=1774819980&stream_type=liveRadio | 200 OK | ./upstream/bmx/tunein/v1/report/0197-20260329-233306.182-POST.http |
|
||||
| 0198 | self | | POST /bmx/tunein/v1/report?stream_id=e536753726&guide_id=s166521&listen_id=1774819980&stream_type=liveRadio | 200 OK | ./self/bmx/tunein/v1/report/0198-20260329-233306.184-POST.http |
|
||||
| 0196 | mirror | ☑ | POST /bmx/tunein/v1/report?stream_id=e536753726&guide_id=s166521&listen_id=1774819980&stream_type=liveRadio | 200 OK | ./mirror/bmx/tunein/v1/report/0196-20260329-233306.072-POST.http |
|
||||
| 0197 | upstream | ☑ | POST https://content.api.bose.io/bmx/tunein/v1/report?stream_id=e536753726&guide_id=s166521&listen_id=1774819980&stream_type=liveRadio | 200 OK | ./upstream/bmx/tunein/v1/report/0197-20260329-233306.182-POST.http |
|
||||
| 0198 | self | ☑ | POST /bmx/tunein/v1/report?stream_id=e536753726&guide_id=s166521&listen_id=1774819980&stream_type=liveRadio | 200 OK | ./self/bmx/tunein/v1/report/0198-20260329-233306.184-POST.http |
|
||||
| 0199 | self | ☑ | GET /streaming/software/update/account/{{accountId}} | 200 OK | ./self/streaming/software/update/account/{accountId}/0199-20260329-233317.196-GET.http |
|
||||
| 0200 | self | ☑ | GET /streaming/account/{{accountId}}/device/{{device_id}}/presets | 200 OK | ./self/streaming/account/{accountId}/device/{device_id}/presets/0200-20260329-233317.206-GET.http |
|
||||
| 0201 | mirror | ☑ | GET /streaming/account/{{accountId}}/device/{{device_id}}/presets | 200 OK | ./mirror/streaming/account/{accountId}/device/{device_id}/presets/0201-20260329-233317.394-GET.http |
|
||||
@@ -218,9 +218,9 @@ Interactions for `20260328-103522-477978/`:
|
||||
| 0214 | self | ☑ | POST /v1/scmudc/{{device_id}} | 200 OK | ./self/v1/scmudc/{device_id}/0214-20260329-233345.001-POST.http |
|
||||
| 0215 | mirror | ☑ | POST /v1/scmudc/{{device_id}} | 200 OK | ./mirror/v1/scmudc/{device_id}/0215-20260329-233345.427-POST.http |
|
||||
| 0216 | mirror | ☑ | POST /v1/scmudc/{{device_id}} | 200 OK | ./mirror/v1/scmudc/{device_id}/0216-20260329-233345.430-POST.http |
|
||||
| 0217 | mirror | | POST /bmx/tunein/v1/report?stream_id=e536753726&guide_id=s166521&listen_id=1774819980&last_titt=0&duration_balance=0&stream_type=liveRadio | 200 OK | ./mirror/bmx/tunein/v1/report/0217-20260329-233345.547-POST.http |
|
||||
| 0218 | upstream | | POST https://content.api.bose.io/bmx/tunein/v1/report?stream_id=e536753726&guide_id=s166521&listen_id=1774819980&last_titt=0&duration_balance=0&stream_type=liveRadio | 200 OK | ./upstream/bmx/tunein/v1/report/0218-20260329-233345.651-POST.http |
|
||||
| 0219 | self | | POST /bmx/tunein/v1/report?stream_id=e536753726&guide_id=s166521&listen_id=1774819980&last_titt=0&duration_balance=0&stream_type=liveRadio | 200 OK | ./self/bmx/tunein/v1/report/0219-20260329-233345.651-POST.http |
|
||||
| 0217 | mirror | ☑ | POST /bmx/tunein/v1/report?stream_id=e536753726&guide_id=s166521&listen_id=1774819980&last_titt=0&duration_balance=0&stream_type=liveRadio | 200 OK | ./mirror/bmx/tunein/v1/report/0217-20260329-233345.547-POST.http |
|
||||
| 0218 | upstream | ☑ | POST https://content.api.bose.io/bmx/tunein/v1/report?stream_id=e536753726&guide_id=s166521&listen_id=1774819980&last_titt=0&duration_balance=0&stream_type=liveRadio | 200 OK | ./upstream/bmx/tunein/v1/report/0218-20260329-233345.651-POST.http |
|
||||
| 0219 | self | ☑ | POST /bmx/tunein/v1/report?stream_id=e536753726&guide_id=s166521&listen_id=1774819980&last_titt=0&duration_balance=0&stream_type=liveRadio | 200 OK | ./self/bmx/tunein/v1/report/0219-20260329-233345.651-POST.http |
|
||||
| 0220 | self | ☑ | GET /bmx/registry/v1/services | 200 OK | ./self/bmx/registry/v1/services/0220-20260329-233504.445-GET.http |
|
||||
| 0221 | self | ☑ | POST /v1/scmudc/{{device_id}} | 200 OK | ./self/v1/scmudc/{device_id}/0221-20260329-233504.467-POST.http |
|
||||
| 0222 | self | ☑ | POST /streaming/support/power_on | 200 OK | ./self/streaming/support/power_on/0222-20260329-233504.475-POST.http |
|
||||
|
||||
Reference in New Issue
Block a user