mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-15 23:26:13 +00:00
lint
This commit is contained in:
@@ -71,7 +71,6 @@ The only native installable phone app in the ecosystem. Pairs with the Überbös
|
||||
|
||||
### 4. SoundTouch Hybrid 2026
|
||||
**[github.com/TJGigs/Bose-SoundTouch-Hybrid-2026](https://github.com/TJGigs/Bose-SoundTouch-Hybrid-2026)**
|
||||
(V3 variant: [github.com/TJGigs/Bose-SoundTouch-Hybrid-2026-V3](https://github.com/TJGigs/Bose-SoundTouch-Hybrid-2026-V3))
|
||||
| | |
|
||||
|---|---|
|
||||
| Language | Node.js (JavaScript) |
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
@@ -23,6 +24,9 @@ import (
|
||||
"github.com/gesellix/bose-soundtouch/pkg/service/constants"
|
||||
)
|
||||
|
||||
// ErrGroupNotFound is returned when no group is found for a given device.
|
||||
var ErrGroupNotFound = errors.New("group not found")
|
||||
|
||||
func exists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return err == nil
|
||||
@@ -1933,7 +1937,7 @@ func (ds *DataStore) GetGroupForDevice(account, deviceID string) (*models.Group,
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil
|
||||
return nil, ErrGroupNotFound
|
||||
}
|
||||
|
||||
return nil, err
|
||||
@@ -1961,7 +1965,7 @@ func (ds *DataStore) GetGroupForDevice(account, deviceID string) (*models.Group,
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
return nil, ErrGroupNotFound
|
||||
}
|
||||
|
||||
// AddGroup saves a new group to disk and returns its generated ID.
|
||||
@@ -2002,8 +2006,8 @@ func (ds *DataStore) ModifyGroup(account, groupID, newName string) (*models.Grou
|
||||
}
|
||||
|
||||
var g models.Group
|
||||
if err := xml.Unmarshal(data, &g); err != nil {
|
||||
return nil, err
|
||||
if xmlErr := xml.Unmarshal(data, &g); xmlErr != nil {
|
||||
return nil, xmlErr
|
||||
}
|
||||
|
||||
g.Name = newName
|
||||
|
||||
@@ -687,7 +687,7 @@ func (s *Server) HandleMargeDeviceGroup(w http.ResponseWriter, r *http.Request)
|
||||
w.Header().Set("Content-Type", "application/vnd.bose.streaming-v1.2+xml")
|
||||
|
||||
group, err := s.ds.GetGroupForDevice(account, device)
|
||||
if err != nil || group == nil {
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte(constants.XMLHeader + `<group/>`))
|
||||
|
||||
@@ -732,7 +732,7 @@ func (s *Server) HandleMargeAddGroup(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
var group models.Group
|
||||
if err := xml.Unmarshal(body, &group); err != nil {
|
||||
if xmlErr := xml.Unmarshal(body, &group); xmlErr != nil {
|
||||
http.Error(w, "Invalid XML", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
@@ -773,7 +773,7 @@ func (s *Server) HandleMargeModifyGroup(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
var req models.Group
|
||||
if err := xml.Unmarshal(body, &req); err != nil {
|
||||
if xmlErr := xml.Unmarshal(body, &req); xmlErr != nil {
|
||||
http.Error(w, "Invalid XML", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -113,6 +113,7 @@ func (s *Server) HandleNotFound(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
preview := body
|
||||
truncated := ""
|
||||
|
||||
if len(preview) > 512 {
|
||||
preview = preview[:512]
|
||||
truncated = "…"
|
||||
|
||||
@@ -470,6 +470,7 @@ func (s *Server) pushSpotifyTokenToDevice(deviceIP, username, accessToken string
|
||||
} else {
|
||||
zcURL = fmt.Sprintf("http://%s:8200/zc", deviceIP)
|
||||
}
|
||||
|
||||
return spotify.PushSpotifyCredentials(zcURL, username, accessToken)
|
||||
}
|
||||
|
||||
|
||||
@@ -51,9 +51,11 @@ func generateDHKeyPair() (privateKey *big.Int, publicKeyBytes []byte, err error)
|
||||
if _, err = rand.Read(privBytes); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
privateKey = new(big.Int).SetBytes(privBytes)
|
||||
pub := new(big.Int).Exp(dhGenerator, privateKey, dhPrime)
|
||||
publicKeyBytes = padBigInt(pub, dhKeySize)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -61,6 +63,7 @@ func generateDHKeyPair() (privateKey *big.Int, publicKeyBytes []byte, err error)
|
||||
func computeSharedSecret(privateKey *big.Int, remotePublicKeyBytes []byte) []byte {
|
||||
remote := new(big.Int).SetBytes(remotePublicKeyBytes)
|
||||
shared := new(big.Int).Exp(remote, privateKey, dhPrime)
|
||||
|
||||
return padBigInt(shared, dhKeySize)
|
||||
}
|
||||
|
||||
@@ -76,6 +79,7 @@ func deriveKeys(sharedSecret []byte) (encKey, macKey []byte) {
|
||||
hMac := hmac.New(sha1.New, baseKey)
|
||||
hMac.Write([]byte("checksum"))
|
||||
macKey = hMac.Sum(nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -125,6 +129,7 @@ func encryptBlob(encKey, macKey, plaintext []byte) ([]byte, error) {
|
||||
out = append(out, iv...)
|
||||
out = append(out, ciphertext...)
|
||||
out = append(out, mac.Sum(nil)...)
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -141,6 +146,7 @@ func decryptBlob(encKey, macKey, blob []byte) ([]byte, error) {
|
||||
|
||||
mac := hmac.New(sha1.New, macKey)
|
||||
mac.Write(ciphertext)
|
||||
|
||||
if !hmac.Equal(mac.Sum(nil), gotMAC) {
|
||||
return nil, fmt.Errorf("blob HMAC verification failed")
|
||||
}
|
||||
@@ -152,16 +158,19 @@ func decryptBlob(encKey, macKey, blob []byte) ([]byte, error) {
|
||||
|
||||
plaintext := make([]byte, len(ciphertext))
|
||||
cipher.NewCTR(block, iv).XORKeyStream(plaintext, ciphertext)
|
||||
|
||||
return plaintext, nil
|
||||
}
|
||||
|
||||
// ZeroConfGetInfo fetches the speaker's DH public key via GET ?action=getInfo.
|
||||
func ZeroConfGetInfo(zcBaseURL string) ([]byte, error) {
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
|
||||
resp, err := client.Get(zcBaseURL + "?action=getInfo")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getInfo: %w", err)
|
||||
}
|
||||
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
@@ -169,9 +178,10 @@ func ZeroConfGetInfo(zcBaseURL string) ([]byte, error) {
|
||||
}
|
||||
|
||||
var info zcGetInfoResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
|
||||
return nil, fmt.Errorf("getInfo: decode: %w", err)
|
||||
if decodeErr := json.NewDecoder(resp.Body).Decode(&info); decodeErr != nil {
|
||||
return nil, fmt.Errorf("getInfo: decode: %w", decodeErr)
|
||||
}
|
||||
|
||||
if info.PublicKey == "" {
|
||||
return nil, fmt.Errorf("getInfo: empty publicKey")
|
||||
}
|
||||
@@ -184,6 +194,7 @@ func ZeroConfGetInfo(zcBaseURL string) ([]byte, error) {
|
||||
return nil, fmt.Errorf("getInfo: invalid base64 publicKey: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return pubKey, nil
|
||||
}
|
||||
|
||||
@@ -208,6 +219,7 @@ func PushSpotifyCredentials(zcBaseURL, username, accessToken string) error {
|
||||
encKey, macKey := deriveKeys(sharedSecret)
|
||||
|
||||
plaintext := buildCredentialsBlob(username, accessToken)
|
||||
|
||||
encryptedBlob, err := encryptBlob(encKey, macKey, plaintext)
|
||||
if err != nil {
|
||||
return fmt.Errorf("pushSpotifyCredentials: encrypt: %w", err)
|
||||
@@ -219,16 +231,19 @@ func PushSpotifyCredentials(zcBaseURL, username, accessToken string) error {
|
||||
data.Set("clientKey", base64.StdEncoding.EncodeToString(ourPublicKeyBytes))
|
||||
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
|
||||
resp, err := client.PostForm(zcBaseURL+"?action=addUser", data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("pushSpotifyCredentials: addUser: %w", err)
|
||||
}
|
||||
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return fmt.Errorf("pushSpotifyCredentials: addUser status %d: %s", resp.StatusCode, body)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -243,16 +258,19 @@ func pushSimplifiedToken(zcBaseURL, username, accessToken string) error {
|
||||
data.Set("tokenType", "accesstoken")
|
||||
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
|
||||
resp, err := client.PostForm(zcBaseURL+"?action=addUser", data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("pushSimplifiedToken: %w", err)
|
||||
}
|
||||
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return fmt.Errorf("pushSimplifiedToken: status %d: %s", resp.StatusCode, body)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -261,8 +279,10 @@ func padBigInt(n *big.Int, size int) []byte {
|
||||
if len(b) >= size {
|
||||
return b
|
||||
}
|
||||
|
||||
out := make([]byte, size)
|
||||
copy(out[size-len(b):], b)
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -271,5 +291,6 @@ func writeVarint(buf *bytes.Buffer, v uint64) {
|
||||
buf.WriteByte(byte(v) | 0x80)
|
||||
v >>= 7
|
||||
}
|
||||
|
||||
buf.WriteByte(byte(v))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,9 +152,9 @@ func TestPushSpotifyCredentials_FullRoundTrip(t *testing.T) {
|
||||
case "getInfo":
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": 101,
|
||||
"status": 101,
|
||||
"statusString": "OK",
|
||||
"publicKey": base64.StdEncoding.EncodeToString(speakerPublicBytes),
|
||||
"publicKey": base64.StdEncoding.EncodeToString(speakerPublicBytes),
|
||||
})
|
||||
|
||||
case "addUser":
|
||||
@@ -311,7 +311,6 @@ func parseCredentialsBlob(data []byte) (*parsedCredentials, error) {
|
||||
return &r, nil
|
||||
}
|
||||
|
||||
|
||||
func readProtoVarint(data []byte) (uint64, int) {
|
||||
var val uint64
|
||||
for i, b := range data {
|
||||
@@ -321,4 +320,4 @@ func readProtoVarint(data []byte) (uint64, int) {
|
||||
}
|
||||
}
|
||||
return 0, len(data)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user