From 58d8a86a92b11b0d47983d85e8afb0fc8f27a500 Mon Sep 17 00:00:00 2001 From: seydx Date: Sat, 10 Jan 2026 10:46:26 +0100 Subject: [PATCH] cleanup --- internal/wyze/wyze.go | 84 +++++++---------- pkg/wyze/client.go | 6 -- pkg/wyze/cloud.go | 204 +++++++++++++++--------------------------- 3 files changed, 106 insertions(+), 188 deletions(-) diff --git a/internal/wyze/wyze.go b/internal/wyze/wyze.go index aad01d76..85d4c19c 100644 --- a/internal/wyze/wyze.go +++ b/internal/wyze/wyze.go @@ -13,6 +13,14 @@ import ( "github.com/AlexxIT/go2rtc/pkg/wyze" ) +type AccountConfig struct { + APIKey string `yaml:"api_key"` + APIID string `yaml:"api_id"` + Password string `yaml:"password"` +} + +var accounts map[string]AccountConfig + func Init() { var v struct { Cfg map[string]AccountConfig `yaml:"wyze"` @@ -31,27 +39,18 @@ func Init() { api.HandleFunc("api/wyze", apiWyze) } -type AccountConfig struct { - APIKey string `yaml:"api_key"` - APIID string `yaml:"api_id"` - Password string `yaml:"password"` -} - -var accounts map[string]AccountConfig - func getCloud(email string) (*wyze.Cloud, error) { cfg, ok := accounts[email] if !ok { return nil, fmt.Errorf("wyze: account not found: %s", email) } - var cloud *wyze.Cloud - if cfg.APIKey != "" && cfg.APIID != "" { - cloud = wyze.NewCloudWithAPIKey(cfg.APIKey, cfg.APIID) - } else { - cloud = wyze.NewCloud() + if cfg.APIKey == "" || cfg.APIID == "" { + return nil, fmt.Errorf("wyze: api_key and api_id required for account: %s", email) } + cloud := wyze.NewCloud(cfg.APIKey, cfg.APIID) + if err := cloud.Login(email, cfg.Password); err != nil { return nil, err } @@ -73,7 +72,6 @@ func apiDeviceList(w http.ResponseWriter, r *http.Request) { email := query.Get("id") if email == "" { - // Return list of configured accounts accountList := make([]string, 0, len(accounts)) for id := range accounts { accountList = append(accountList, id) @@ -99,7 +97,7 @@ func apiDeviceList(w http.ResponseWriter, r *http.Request) { items = append(items, &api.Source{ Name: cam.Nickname, - Info: fmt.Sprintf("%s | %s | %s", cam.ModelName(), cam.MAC, cam.IP), + Info: fmt.Sprintf("%s | %s | %s", cam.ProductModel, cam.MAC, cam.IP), URL: streamURL, }) } @@ -113,25 +111,6 @@ func apiDeviceList(w http.ResponseWriter, r *http.Request) { } } -func buildStreamURL(cam *wyze.Camera) string { - // Use IP if available, otherwise use P2P_ID as host - host := cam.IP - if host == "" { - host = cam.P2PID - } - - query := url.Values{} - query.Set("uid", cam.P2PID) - query.Set("enr", cam.ENR) - query.Set("mac", cam.MAC) - - if cam.DTLS == 1 { - query.Set("dtls", "true") - } - - return fmt.Sprintf("wyze://%s?%s", host, query.Encode()) -} - func apiAuth(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) @@ -143,18 +122,13 @@ func apiAuth(w http.ResponseWriter, r *http.Request) { apiKey := r.Form.Get("api_key") apiID := r.Form.Get("api_id") - if email == "" || password == "" { - http.Error(w, "email and password required", http.StatusBadRequest) + if email == "" || password == "" || apiKey == "" || apiID == "" { + http.Error(w, "email, password, api_key and api_id required", http.StatusBadRequest) return } // Try to login - var cloud *wyze.Cloud - if apiKey != "" && apiID != "" { - cloud = wyze.NewCloudWithAPIKey(apiKey, apiID) - } else { - cloud = wyze.NewCloud() - } + cloud := wyze.NewCloud(apiKey, apiID) if err := cloud.Login(email, password); err != nil { // Check for MFA error @@ -169,15 +143,10 @@ func apiAuth(w http.ResponseWriter, r *http.Request) { return } - // Save credentials to config (not tokens!) cfg := map[string]string{ "password": password, - } - if apiKey != "" { - cfg["api_key"] = apiKey - } - if apiID != "" { - cfg["api_id"] = apiID + "api_key": apiKey, + "api_id": apiID, } if err := app.PatchConfig([]string{"wyze", email}, cfg); err != nil { @@ -185,7 +154,6 @@ func apiAuth(w http.ResponseWriter, r *http.Request) { return } - // Update in-memory config if accounts == nil { accounts = make(map[string]AccountConfig) } @@ -195,7 +163,6 @@ func apiAuth(w http.ResponseWriter, r *http.Request) { Password: password, } - // Return camera list with direct URLs cameras, err := cloud.GetCameraList() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -208,7 +175,7 @@ func apiAuth(w http.ResponseWriter, r *http.Request) { items = append(items, &api.Source{ Name: cam.Nickname, - Info: fmt.Sprintf("%s | %s | %s", cam.ModelName(), cam.MAC, cam.IP), + Info: fmt.Sprintf("%s | %s | %s", cam.ProductModel, cam.MAC, cam.IP), URL: streamURL, }) } @@ -216,6 +183,19 @@ func apiAuth(w http.ResponseWriter, r *http.Request) { api.ResponseSources(w, items) } +func buildStreamURL(cam *wyze.Camera) string { + query := url.Values{} + query.Set("uid", cam.P2PID) + query.Set("enr", cam.ENR) + query.Set("mac", cam.MAC) + + if cam.DTLS == 1 { + query.Set("dtls", "true") + } + + return fmt.Sprintf("wyze://%s?%s", cam.IP, query.Encode()) +} + func isAuthError(err error, target **wyze.AuthError) bool { if e, ok := err.(*wyze.AuthError); ok { *target = e diff --git a/pkg/wyze/client.go b/pkg/wyze/client.go index 69514d0f..7f59be58 100644 --- a/pkg/wyze/client.go +++ b/pkg/wyze/client.go @@ -438,9 +438,6 @@ func (c *Client) buildK10002(challenge []byte, status byte) []byte { } func (c *Client) buildK10010(mediaType byte, enabled bool) []byte { - // SDK format: 18 bytes total - // Header: 16 bytes, Payload: 2 bytes (media_type + enabled) - // TX K10010: 48 4c 05 00 1a 27 02 00 00 00 00 00 00 00 00 00 01 01 buf := make([]byte, 18) buf[0] = 'H' buf[1] = 'L' @@ -457,9 +454,6 @@ func (c *Client) buildK10010(mediaType byte, enabled bool) []byte { } func (c *Client) buildK10056(frameSize uint8, bitrate uint16) []byte { - // SDK format: 21 bytes total - // Header: 16 bytes, Payload: 5 bytes - // TX K10056: 48 4c 05 00 48 27 05 00 00 00 00 00 00 00 00 00 04 f0 00 00 00 buf := make([]byte, 21) buf[0] = 'H' buf[1] = 'L' diff --git a/pkg/wyze/cloud.go b/pkg/wyze/cloud.go index f10268cf..7034b141 100644 --- a/pkg/wyze/cloud.go +++ b/pkg/wyze/cloud.go @@ -22,14 +22,12 @@ const ( ) type Cloud struct { - client *http.Client - apiKey string - keyID string - accessToken string - refreshToken string - phoneID string - openUserID string - cameras []*Camera + client *http.Client + apiKey string + keyID string + accessToken string + phoneID string + cameras []*Camera } type Camera struct { @@ -45,46 +43,36 @@ type Camera struct { IsOnline bool `json:"is_online"` } -func (c *Camera) ModelName() string { - models := map[string]string{ - "WYZEC1": "Wyze Cam v1", - "WYZEC1-JZ": "Wyze Cam v2", - "WYZE_CAKP2JFUS": "Wyze Cam v3", - "HL_CAM3P": "Wyze Cam v3 Pro", - "HL_CAM4": "Wyze Cam v4", - "WYZECP1_JEF": "Wyze Cam Pan", - "HL_PANP": "Wyze Cam Pan v2", - "HL_PAN3": "Wyze Cam Pan v3", - "WVOD1": "Wyze Video Doorbell", - "WVOD2": "Wyze Video Doorbell v2", - "AN_RSCW": "Wyze Video Doorbell Pro", - "GW_BE1": "Wyze Cam Floodlight", - "HL_WCO2": "Wyze Cam Outdoor", - "HL_CFL2": "Wyze Cam Floodlight v2", - "LD_CFP": "Wyze Battery Cam Pro", - } - if name, ok := models[c.ProductModel]; ok { - return name - } - return c.ProductModel +type deviceListResponse struct { + Code string `json:"code"` + Msg string `json:"msg"` + Data struct { + DeviceList []deviceInfo `json:"device_list"` + } `json:"data"` } -func NewCloud() *Cloud { - return &Cloud{ - client: &http.Client{Timeout: 30 * time.Second}, - phoneID: generatePhoneID(), - } +type deviceInfo struct { + MAC string `json:"mac"` + ENR string `json:"enr"` + Nickname string `json:"nickname"` + ProductModel string `json:"product_model"` + ProductType string `json:"product_type"` + FirmwareVer string `json:"firmware_ver"` + ConnState int `json:"conn_state"` + DeviceParams deviceParams `json:"device_params"` } -func NewCloudWithAPIKey(apiKey, keyID string) *Cloud { - c := NewCloud() - c.apiKey = apiKey - c.keyID = keyID - return c +type deviceParams struct { + P2PID string `json:"p2p_id"` + P2PType int `json:"p2p_type"` + IP string `json:"ip"` + DTLS int `json:"dtls"` } -func generatePhoneID() string { - return core.RandString(16, 16) // 16 hex chars +type p2pInfoResponse struct { + Code string `json:"code"` + Msg string `json:"msg"` + Data map[string]any `json:"data"` } type loginResponse struct { @@ -96,35 +84,13 @@ type loginResponse struct { EmailSessionID string `json:"email_session_id"` } -type apiError struct { - Code string `json:"code"` - ErrorCode int `json:"errorCode"` - Msg string `json:"msg"` - Description string `json:"description"` -} - -func (e *apiError) hasError() bool { - if e.Code == "1" || e.Code == "0" { - return false +func NewCloud(apiKey, keyID string) *Cloud { + return &Cloud{ + client: &http.Client{Timeout: 30 * time.Second}, + phoneID: generatePhoneID(), + apiKey: apiKey, + keyID: keyID, } - if e.Code == "" && e.ErrorCode == 0 { - return false - } - return e.Code != "" || e.ErrorCode != 0 -} - -func (e *apiError) message() string { - if e.Msg != "" { - return e.Msg - } - return e.Description -} - -func (e *apiError) code() string { - if e.Code != "" { - return e.Code - } - return fmt.Sprintf("%d", e.ErrorCode) } func (c *Cloud) Login(email, password string) error { @@ -141,15 +107,9 @@ func (c *Cloud) Login(email, password string) error { } req.Header.Set("Content-Type", "application/json") - if c.apiKey != "" && c.keyID != "" { - req.Header.Set("Apikey", c.apiKey) - req.Header.Set("Keyid", c.keyID) - req.Header.Set("User-Agent", "go2rtc") - } else { - req.Header.Set("X-API-Key", "WMXHYf79Nr5gIlt3r0r7p9Tcw5bvs6BB4U8O8nGJ") - req.Header.Set("Phone-Id", c.phoneID) - req.Header.Set("User-Agent", "wyze_ios_"+appVersion) - } + req.Header.Set("Apikey", c.apiKey) + req.Header.Set("Keyid", c.keyID) + req.Header.Set("User-Agent", "go2rtc") resp, err := c.client.Do(req) if err != nil { @@ -186,55 +146,10 @@ func (c *Cloud) Login(email, password string) error { } c.accessToken = result.AccessToken - c.refreshToken = result.RefreshToken - c.openUserID = result.UserID return nil } -func (c *Cloud) LoginWithToken(accessToken, phoneID string) error { - c.accessToken = accessToken - if phoneID != "" { - c.phoneID = phoneID - } - _, err := c.GetCameraList() - return err -} - -func (c *Cloud) Credentials() (phoneID, openUserID string) { - return c.phoneID, c.openUserID -} - -func (c *Cloud) AccessToken() string { - return c.accessToken -} - -type deviceListResponse struct { - Code string `json:"code"` - Msg string `json:"msg"` - Data struct { - DeviceList []deviceInfo `json:"device_list"` - } `json:"data"` -} - -type deviceInfo struct { - MAC string `json:"mac"` - ENR string `json:"enr"` - Nickname string `json:"nickname"` - ProductModel string `json:"product_model"` - ProductType string `json:"product_type"` - FirmwareVer string `json:"firmware_ver"` - ConnState int `json:"conn_state"` - DeviceParams deviceParams `json:"device_params"` -} - -type deviceParams struct { - P2PID string `json:"p2p_id"` - P2PType int `json:"p2p_type"` - IP string `json:"ip"` - DTLS int `json:"dtls"` -} - func (c *Cloud) GetCameraList() ([]*Camera, error) { payload := map[string]any{ "access_token": c.accessToken, @@ -316,12 +231,6 @@ func (c *Cloud) GetCamera(id string) (*Camera, error) { return nil, fmt.Errorf("wyze: camera not found: %s", id) } -type p2pInfoResponse struct { - Code string `json:"code"` - Msg string `json:"msg"` - Data map[string]any `json:"data"` -} - func (c *Cloud) GetP2PInfo(mac string) (map[string]any, error) { payload := map[string]any{ "access_token": c.accessToken, @@ -367,6 +276,37 @@ func (c *Cloud) GetP2PInfo(mac string) (map[string]any, error) { return result.Data, nil } +type apiError struct { + Code string `json:"code"` + ErrorCode int `json:"errorCode"` + Msg string `json:"msg"` + Description string `json:"description"` +} + +func (e *apiError) hasError() bool { + if e.Code == "1" || e.Code == "0" { + return false + } + if e.Code == "" && e.ErrorCode == 0 { + return false + } + return e.Code != "" || e.ErrorCode != 0 +} + +func (e *apiError) message() string { + if e.Msg != "" { + return e.Msg + } + return e.Description +} + +func (e *apiError) code() string { + if e.Code != "" { + return e.Code + } + return fmt.Sprintf("%d", e.ErrorCode) +} + type AuthError struct { Message string `json:"message"` NeedsMFA bool `json:"needs_mfa,omitempty"` @@ -377,6 +317,10 @@ func (e *AuthError) Error() string { return e.Message } +func generatePhoneID() string { + return core.RandString(16, 16) // 16 hex chars +} + func hashPassword(password string) string { encoded := strings.TrimSpace(password) if strings.HasPrefix(strings.ToLower(encoded), "md5:") {