diff --git a/binary/node_test.go b/binary/node_test.go index 56f1d0a0..8debff7b 100644 --- a/binary/node_test.go +++ b/binary/node_test.go @@ -2,9 +2,10 @@ package binary import ( "fmt" - "github.com/Rhymen/go-whatsapp/binary/proto" "reflect" "testing" + + "github.com/Rhymen/go-whatsapp/binary/proto" ) func TestMarshal(t *testing.T) { @@ -19,7 +20,7 @@ func TestMarshal(t *testing.T) { } *msg.Message.Conversation = "Testnachricht." - msg.Status = new(proto.WebMessageInfo_WEB_MESSAGE_INFO_STATUS) + msg.Status = new(proto.WebMessageInfo_WebMessageInfoStatus) *msg.Status = proto.WebMessageInfo_ERROR msg.Key = &proto.MessageKey{ diff --git a/conn.go b/conn.go index 4b28cbe1..805c1baf 100644 --- a/conn.go +++ b/conn.go @@ -2,6 +2,7 @@ package whatsapp import ( + "log" "math/rand" "net/http" "net/url" @@ -94,6 +95,8 @@ type Conn struct { shortClientName string clientVersion string + autoUpdateTries int // counter for auto update retries + loginSessionLock sync.RWMutex Proxy func(*http.Request) (*url.URL, error) @@ -125,29 +128,30 @@ func NewConn(timeout time.Duration) (*Conn, error) { func NewConnWithProxy(timeout time.Duration, proxy func(*http.Request) (*url.URL, error)) (*Conn, error) { return NewConnWithOptions(&Options{ Timeout: timeout, - Proxy: proxy, + Proxy: proxy, }) } // NewConnWithOptions Create a new connect with a given options. type Options struct { - Proxy func(*http.Request) (*url.URL, error) - Timeout time.Duration - Handler []Handler - ShortClientName string - LongClientName string - ClientVersion string - Store *Store + Proxy func(*http.Request) (*url.URL, error) + Timeout time.Duration + Handler []Handler + ShortClientName string + LongClientName string + ClientVersion string + Store *Store } + func NewConnWithOptions(opt *Options) (*Conn, error) { if opt == nil { return nil, ErrOptionsNotProvided } wac := &Conn{ - handler: make([]Handler, 0), - msgCount: 0, - msgTimeout: opt.Timeout, - Store: newStore(), + handler: make([]Handler, 0), + msgCount: 0, + msgTimeout: opt.Timeout, + Store: newStore(), longClientName: "github.com/Rhymen/go-whatsapp", shortClientName: "go-whatsapp", clientVersion: "0.1.0", @@ -300,3 +304,33 @@ func (wac *Conn) IsLoggedIn() bool { func (wac *Conn) GetLoggedIn() bool { return wac.loggedIn } + +func (wac *Conn) autoUpdateMinorVersion() bool { + if !AutoUpdate { + return false + } + log.Println("received update command from the WhatsApp API") + if wac.autoUpdateTries > AutoUpdateMaxRetries { + log.Printf("auto update failed after %d tries", AutoUpdateMaxRetries) + return false + } + if AutoUpdateIncrement == 0 { + log.Println("will try to obtain the latest version automatically") + newv, err := CheckCurrentServerVersion() + if err == nil && len(newv) >= 3 { + waVersionLock.Lock() + waVersion = newv + waVersionLock.Unlock() + wac.autoUpdateTries++ + log.Printf("[auto update] latest server version: %v.%v.%v\n", newv[0], newv[1], newv[2]) + return true + } + log.Println("auto update failed:", err) + return false + } + waVersionLock.Lock() + waVersion[1] += AutoUpdateIncrement + waVersionLock.Unlock() + wac.autoUpdateTries++ + return true +} diff --git a/errors.go b/errors.go index 2f58a771..d24df192 100644 --- a/errors.go +++ b/errors.go @@ -22,6 +22,7 @@ var ( ErrInvalidWebsocket = errors.New("invalid websocket") ErrMessageTypeNotImplemented = errors.New("message type not implemented") ErrOptionsNotProvided = errors.New("new conn options not provided") + ErrUpdateRequired = errors.New("waVersion not accepted (must update)") ) type ErrConnectionFailed struct { diff --git a/session.go b/session.go index 215fb599..6abb9b42 100644 --- a/session.go +++ b/session.go @@ -9,6 +9,7 @@ import ( "fmt" "strconv" "strings" + "sync" "sync/atomic" "time" @@ -19,6 +20,24 @@ import ( //represents the WhatsAppWeb client version var waVersion = []int{2, 2142, 12} +var waVersionLock sync.RWMutex + +var ( + // AutoUpdate will automatically bump the client (minor) version if true + AutoUpdate = false + + // AutoUpdateIncrement will increment the client version if AutoUpdate is true and if the + // `["Cmd",{"type":"update"}]` data is received. + // It will use the CheckCurrentServerVersion() function to get the latest version if + // AutoUpdateIncrement == 0. + AutoUpdateIncrement = 5 + + // AutoUpdateMaxRetries will set the maximum number of retries to update the client version (prevents an infinite loop) + AutoUpdateMaxRetries = 100 + + // AutoUpdateRetryDelay will set the delay between retries to update the client version + AutoUpdateRetryDelay = time.Millisecond * 300 +) /* Session contains session individual information. To be able to resume the connection without scanning the qr code @@ -151,14 +170,18 @@ func (wac *Conn) SetClientName(long, short string, version string) error { /* SetClientVersion sets WhatsApp client version -Default value is 0.4.2080 +Default value is 0.4.2142 */ func (wac *Conn) SetClientVersion(major int, minor int, patch int) { + waVersionLock.Lock() + defer waVersionLock.Unlock() waVersion = []int{major, minor, patch} } // GetClientVersion returns WhatsApp client version func (wac *Conn) GetClientVersion() []int { + waVersionLock.RLock() + defer waVersionLock.RUnlock() return waVersion } @@ -213,7 +236,12 @@ func (wac *Conn) Login(qrChan chan<- string) (Session, error) { } session.ClientId = base64.StdEncoding.EncodeToString(clientId) - login := []interface{}{"admin", "init", waVersion, []string{wac.longClientName, wac.shortClientName, wac.clientVersion}, session.ClientId, true} + + waVersionLock.RLock() + version := waVersion + waVersionLock.RUnlock() + + login := []interface{}{"admin", "init", version, []string{wac.longClientName, wac.shortClientName, wac.clientVersion}, session.ClientId, true} loginChan, err := wac.writeJson(login) if err != nil { return session, fmt.Errorf("error writing login: %v\n", err) @@ -373,8 +401,12 @@ func (wac *Conn) Restore() error { wac.listener.m["s1"] = s1 wac.listener.Unlock() + waVersionLock.RLock() + version := waVersion + waVersionLock.RUnlock() + //admin init - init := []interface{}{"admin", "init", waVersion, []string{wac.longClientName, wac.shortClientName, wac.clientVersion}, wac.session.ClientId, true} + init := []interface{}{"admin", "init", version, []string{wac.longClientName, wac.shortClientName, wac.clientVersion}, wac.session.ClientId, true} initChan, err := wac.writeJson(init) if err != nil { return fmt.Errorf("error writing admin init: %v\n", err) @@ -391,6 +423,12 @@ func (wac *Conn) Restore() error { case r := <-initChan: var resp map[string]interface{} if err = json.Unmarshal([]byte(r), &resp); err != nil { + if isUpdateResponse(r) { + if wac.autoUpdateMinorVersion() { + return wac.Restore() + } + return ErrUpdateRequired + } return fmt.Errorf("error decoding login connResp: %v\n", err) } @@ -418,6 +456,12 @@ func (wac *Conn) Restore() error { case r := <-loginChan: var resp map[string]interface{} if err = json.Unmarshal([]byte(r), &resp); err != nil { + if isUpdateResponse(r) { + if wac.autoUpdateMinorVersion() { + return wac.Restore() + } + return ErrUpdateRequired + } return fmt.Errorf("error decoding login connResp: %v\n", err) } if int(resp["status"].(float64)) != 200 { @@ -459,6 +503,12 @@ func (wac *Conn) Restore() error { var resp map[string]interface{} if err = json.Unmarshal([]byte(r), &resp); err != nil { wac.timeTag = "" + if isUpdateResponse(r) { + if wac.autoUpdateMinorVersion() { + return wac.Restore() + } + return ErrUpdateRequired + } return fmt.Errorf("error decoding login connResp: %v\n", err) } @@ -530,3 +580,32 @@ func (wac *Conn) Logout() error { return nil } + +// isUpdateResponse tests if rawstr is `["Cmd",{"type":"update"}]` +func isUpdateResponse(rawstr string) bool { + v := []interface{}{} + err := json.Unmarshal([]byte(rawstr), &v) + if err != nil { + return false + } + if len(v) < 2 { + return false + } + if v[0] == nil || v[1] == nil { + return false + } + if vs, _ := v[0].(string); vs != "Cmd" { + return false + } + rawcmd, ok := v[1].(map[string]interface{}) + if !ok { + return false + } + if rawcmd["type"] == nil { + return false + } + if cmdtype, _ := rawcmd["type"]; cmdtype != "update" { + return false + } + return true +} diff --git a/session_test.go b/session_test.go new file mode 100644 index 00000000..591a8f03 --- /dev/null +++ b/session_test.go @@ -0,0 +1,9 @@ +package whatsapp + +import "testing" + +func TestIsUpdateResponse(t *testing.T) { + if !isUpdateResponse(`["Cmd",{"type":"update"}]`) { + t.Error("Update response not detected") + } +}