Files
Bose-SoundTouch/pkg/service/bmx/utils.go
T
Tobias Gesellchen b95bdae751 feat: Add RadioBrowser integration alongside TuneIn support
- Refactors BMX service to support multiple radio providers
- Adds RadioBrowser.com API integration with search and browse
- Splits TuneIn logic into separate module for better organization
- Adds new web UI components for radio station discovery
- Includes new SVG icons for RadioBrowser branding
2026-05-18 22:34:26 +02:00

114 lines
2.6 KiB
Go

package bmx
import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
)
var defaultClient = &http.Client{Timeout: 10 * time.Second}
func fetchJSONMap(client *http.Client, fetchURL string, allowedHosts map[string]bool) (map[string]interface{}, error) {
result, err := fetchJSONGeneric(client, fetchURL, allowedHosts)
if err != nil {
return nil, err
}
m, ok := result.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("expected map[string]interface{}, got %T", result)
}
return m, nil
}
func fetchJSONGeneric(client *http.Client, fetchURL string, allowedHosts map[string]bool) (interface{}, error) {
if allowedHosts != nil {
if !isHostAllowed(fetchURL, allowedHosts) {
return nil, fmt.Errorf("URL host not in allowed list: %s", fetchURL)
}
}
resp, err := client.Get(fetchURL)
if err != nil {
return nil, err
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("fetch failed with status %d: %s", resp.StatusCode, fetchURL)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var result interface{}
if err := json.Unmarshal(body, &result); err != nil {
return nil, err
}
return result, nil
}
func isHostAllowed(rawURL string, allowedHosts map[string]bool) bool {
u, err := url.Parse(rawURL)
if err != nil {
return false
}
return allowedHosts[u.Hostname()]
}
func decodeBase64URI(encoded string) (string, error) {
// Clean up input for base64 decoding (remove potential whitespace or prefixes)
encoded = strings.TrimSpace(encoded)
// Attempt URL-safe decoding first
b, err := base64.URLEncoding.DecodeString(encoded)
if err != nil {
// Try again with padding if missing
padding := len(encoded) % 4
if padding > 0 {
padded := encoded + strings.Repeat("=", 4-padding)
b, err = base64.URLEncoding.DecodeString(padded)
}
}
if err != nil {
// Attempt standard decoding
b, err = base64.StdEncoding.DecodeString(encoded)
if err != nil {
padding := len(encoded) % 4
if padding > 0 {
padded := encoded + strings.Repeat("=", 4-padding)
b, err = base64.StdEncoding.DecodeString(padded)
}
}
}
if err != nil {
// Try raw (no padding) decoding specifically
b, err = base64.RawURLEncoding.DecodeString(encoded)
if err != nil {
b, err = base64.RawStdEncoding.DecodeString(encoded)
}
}
if err != nil {
// FINAL DESPERATE ATTEMPT: decode by hand or check if it's just plain text
// (though it shouldn't be). Some tests might be passing "illegal base64 data"
// on purpose to test error handling? No, the tests themselves are failing.
return "", err
}
return string(b), nil
}