Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto update (bump) minor version #632

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions binary/node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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{
Expand Down
58 changes: 46 additions & 12 deletions conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package whatsapp

import (
"log"
"math/rand"
"net/http"
"net/url"
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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
}
1 change: 1 addition & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
85 changes: 82 additions & 3 deletions session.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"fmt"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"

Expand All @@ -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
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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)
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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
}
9 changes: 9 additions & 0 deletions session_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package whatsapp

import "testing"

func TestIsUpdateResponse(t *testing.T) {
if !isUpdateResponse(`["Cmd",{"type":"update"}]`) {
t.Error("Update response not detected")
}
}