Update whatsapp vendor and fix a panic (#1209)

* Fix another whatsapp panic

* Update whatsapp vendor
This commit is contained in:
Wim 2020-08-24 23:35:08 +02:00 committed by GitHub
parent e8167ee3d7
commit 2f506425c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 228 additions and 27 deletions

View File

@ -78,7 +78,7 @@ func (b *Bwhatsapp) HandleTextMessage(message whatsapp.TextMessage) {
senderJID := message.Info.SenderJid senderJID := message.Info.SenderJid
if len(senderJID) == 0 { if len(senderJID) == 0 {
// TODO workaround till https://github.com/Rhymen/go-whatsapp/issues/86 resolved // TODO workaround till https://github.com/Rhymen/go-whatsapp/issues/86 resolved
if message.Info.Source != nil { if message.Info.Source != nil && message.Info.Source.Participant != nil {
senderJID = *message.Info.Source.Participant senderJID = *message.Info.Source.Participant
} }
} }

2
go.mod
View File

@ -5,7 +5,7 @@ require (
github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f github.com/Baozisoftware/qrcode-terminal-go v0.0.0-20170407111555-c0650d8dff0f
github.com/Jeffail/gabs v1.1.1 // indirect github.com/Jeffail/gabs v1.1.1 // indirect
github.com/Philipp15b/go-steam v1.0.1-0.20190816133340-b04c5a83c1c0 github.com/Philipp15b/go-steam v1.0.1-0.20190816133340-b04c5a83c1c0
github.com/Rhymen/go-whatsapp v0.1.1-0.20200421062035-31e8111ac334 github.com/Rhymen/go-whatsapp v0.1.1-0.20200818115958-f07a700b9819
github.com/d5/tengo/v2 v2.6.0 github.com/d5/tengo/v2 v2.6.0
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/fsnotify/fsnotify v1.4.9 github.com/fsnotify/fsnotify v1.4.9

4
go.sum
View File

@ -43,8 +43,8 @@ github.com/PaulARoy/azurestoragecache v0.0.0-20170906084534-3c249a3ba788/go.mod
github.com/Philipp15b/go-steam v1.0.1-0.20190816133340-b04c5a83c1c0 h1:TO7d4rocnNFng6ZQrPe7U6WqHtK5eHEMrgrnnM/72IQ= github.com/Philipp15b/go-steam v1.0.1-0.20190816133340-b04c5a83c1c0 h1:TO7d4rocnNFng6ZQrPe7U6WqHtK5eHEMrgrnnM/72IQ=
github.com/Philipp15b/go-steam v1.0.1-0.20190816133340-b04c5a83c1c0/go.mod h1:HuVM+sZFzumUdKPWiz+IlCMb4RdsKdT3T+nQBKL+sYg= github.com/Philipp15b/go-steam v1.0.1-0.20190816133340-b04c5a83c1c0/go.mod h1:HuVM+sZFzumUdKPWiz+IlCMb4RdsKdT3T+nQBKL+sYg=
github.com/Rhymen/go-whatsapp v0.0.0/go.mod h1:rdQr95g2C1xcOfM7QGOhza58HeI3I+tZ/bbluv7VazA= github.com/Rhymen/go-whatsapp v0.0.0/go.mod h1:rdQr95g2C1xcOfM7QGOhza58HeI3I+tZ/bbluv7VazA=
github.com/Rhymen/go-whatsapp v0.1.1-0.20200421062035-31e8111ac334 h1:kb1zvD+xd+XbPUdQ0lMxnRaQ76N5C9vMAClLi8Dyw1Y= github.com/Rhymen/go-whatsapp v0.1.1-0.20200818115958-f07a700b9819 h1:LthbEFUDcL9ZSRIs9m9JjThBSKrW6aIj8YGIT7G/SSk=
github.com/Rhymen/go-whatsapp v0.1.1-0.20200421062035-31e8111ac334/go.mod h1:o7jjkvKnigfu432dMbQ/w4PH0Yp5u4Y6ysCNjUlcYCk= github.com/Rhymen/go-whatsapp v0.1.1-0.20200818115958-f07a700b9819/go.mod h1:o7jjkvKnigfu432dMbQ/w4PH0Yp5u4Y6ysCNjUlcYCk=
github.com/Rhymen/go-whatsapp/examples/echo v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:zgCiQtBtZ4P4gFWvwl9aashsdwOcbb/EHOGRmSzM8ME= github.com/Rhymen/go-whatsapp/examples/echo v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:zgCiQtBtZ4P4gFWvwl9aashsdwOcbb/EHOGRmSzM8ME=
github.com/Rhymen/go-whatsapp/examples/restoreSession v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:5sCUSpG616ZoSJhlt9iBNI/KXBqrVLcNUJqg7J9+8pU= github.com/Rhymen/go-whatsapp/examples/restoreSession v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:5sCUSpG616ZoSJhlt9iBNI/KXBqrVLcNUJqg7J9+8pU=
github.com/Rhymen/go-whatsapp/examples/sendImage v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:RdiyhanVEGXTam+mZ3k6Y3VDCCvXYCwReOoxGozqhHw= github.com/Rhymen/go-whatsapp/examples/sendImage v0.0.0-20190325075644-cc2581bbf24d/go.mod h1:RdiyhanVEGXTam+mZ3k6Y3VDCCvXYCwReOoxGozqhHw=

View File

@ -70,6 +70,10 @@ func (myHandler) HandleContactMessage(message whatsapp.ContactMessage) {
fmt.Println(message) fmt.Println(message)
} }
func (myHandler) HandleBatteryMessage(msg whatsapp.BatteryMessage) {
fmt.Println(message)
}
wac.AddHandler(myHandler{}) wac.AddHandler(myHandler{})
``` ```
The message handlers are all optional, you don't need to implement anything but the error handler to implement the interface. The ImageMessage, VideoMessage, AudioMessage and DocumentMessage provide a Download function to get the media data. The message handlers are all optional, you don't need to implement anything but the error handler to implement the interface. The ImageMessage, VideoMessage, AudioMessage and DocumentMessage provide a Download function to get the media data.

View File

@ -88,6 +88,8 @@ type Conn struct {
Store *Store Store *Store
ServerLastSeen time.Time ServerLastSeen time.Time
timeTag string // last 3 digits obtained after a successful login takeover
longClientName string longClientName string
shortClientName string shortClientName string
clientVersion string clientVersion string
@ -156,8 +158,8 @@ func (wac *Conn) connect() (err error) {
}() }()
dialer := &websocket.Dialer{ dialer := &websocket.Dialer{
ReadBufferSize: 25 * 1024 * 1024, ReadBufferSize: 0,
WriteBufferSize: 10 * 1024 * 1024, WriteBufferSize: 0,
HandshakeTimeout: wac.msgTimeout, HandshakeTimeout: wac.msgTimeout,
Proxy: wac.Proxy, Proxy: wac.Proxy,
} }
@ -246,3 +248,11 @@ func (wac *Conn) keepAlive(minIntervalMs int, maxIntervalMs int) {
} }
} }
} }
func (wac *Conn) GetConnected() bool {
return wac.connected
}
func (wac *Conn) GetLoggedIn() bool {
return wac.loggedIn
}

View File

@ -2,6 +2,7 @@ package whatsapp
import ( import (
"fmt" "fmt"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -20,6 +21,7 @@ var (
ErrServerRespondedWith404 = errors.New("server responded with status 404") ErrServerRespondedWith404 = errors.New("server responded with status 404")
ErrMediaDownloadFailedWith404 = errors.New("download failed with status code 404") ErrMediaDownloadFailedWith404 = errors.New("download failed with status code 404")
ErrMediaDownloadFailedWith410 = errors.New("download failed with status code 410") ErrMediaDownloadFailedWith410 = errors.New("download failed with status code 410")
ErrInvalidWebsocket = errors.New("invalid websocket")
) )
type ErrConnectionFailed struct { type ErrConnectionFailed struct {

View File

@ -133,6 +133,14 @@ type ChatListHandler interface {
HandleChatList(contacts []Chat) HandleChatList(contacts []Chat)
} }
/**
The BatteryMessageHandler interface needs to be implemented to receive percentage the device connected dispatched by the dispatcher.
*/
type BatteryMessageHandler interface {
Handler
HandleBatteryMessage(battery BatteryMessage)
}
/* /*
AddHandler adds an handler to the list of handler that receive dispatched messages. AddHandler adds an handler to the list of handler that receive dispatched messages.
The provided handler must at least implement the Handler interface. Additionally implemented The provided handler must at least implement the Handler interface. Additionally implemented
@ -286,6 +294,17 @@ func (wac *Conn) handleWithCustomHandlers(message interface{}, handlers []Handle
} }
} }
case BatteryMessage:
for _, h := range handlers {
if x, ok := h.(BatteryMessageHandler); ok {
if wac.shouldCallSynchronously(h) {
x.HandleBatteryMessage(m)
} else {
go x.HandleBatteryMessage(m)
}
}
}
case *proto.WebMessageInfo: case *proto.WebMessageInfo:
for _, h := range handlers { for _, h := range handlers {
if x, ok := h.(RawMessageHandler); ok { if x, ok := h.(RawMessageHandler); ok {
@ -379,6 +398,10 @@ func (wac *Conn) dispatch(msg interface{}) {
wac.handle(ParseProtoMessage(v)) wac.handle(ParseProtoMessage(v))
} }
} }
} else if con, ok := message.Content.([]binary.Node); ok {
for a := range con {
wac.handle(ParseNodeMessage(con[a]))
}
} }
} else if message.Description == "response" && message.Attributes["type"] == "contacts" { } else if message.Description == "response" && message.Attributes["type"] == "contacts" {
wac.updateContacts(message.Content) wac.updateContacts(message.Content)

View File

@ -93,18 +93,21 @@ func downloadMedia(url string) (file []byte, mac []byte, err error) {
return data[:n-10], data[n-10 : n], nil return data[:n-10], data[n-10 : n], nil
} }
type MediaConn struct { type MediaConn struct {
Status int `json:"status"` Status int `json:"status"`
MediaConn struct { MediaConn struct {
Auth string `json:"auth"` Auth string `json:"auth"`
TTL int `json:"ttl"` TTL int `json:"ttl"`
Hosts []struct { Hosts []struct {
Hostname string `json:"hostname"` Hostname string `json:"hostname"`
IPs []string `json:"ips"` IPs []interface{} `json:"ips"`
} `json:"hosts"` } `json:"hosts"`
} `json:"media_conn"` } `json:"media_conn"`
} }
func (wac *Conn) queryMediaConn() (hostname, auth string, ttl int, err error) { func (wac *Conn) queryMediaConn() (hostname, auth string, ttl int, err error) {
queryReq := []interface{}{"query", "mediaConn"} queryReq := []interface{}{"query", "mediaConn"}
ch, err := wac.writeJson(queryReq) ch, err := wac.writeJson(queryReq)

View File

@ -81,7 +81,7 @@ func (wac *Conn) Send(msg interface{}) (string, error) {
return "ERROR", fmt.Errorf("error decoding sending response: %v\n", err) return "ERROR", fmt.Errorf("error decoding sending response: %v\n", err)
} }
if int(resp["status"].(float64)) != 200 { if int(resp["status"].(float64)) != 200 {
return "ERROR", fmt.Errorf("message sending responded with %d", resp["status"]) return "ERROR", fmt.Errorf("message sending responded with %v", resp["status"])
} }
if int(resp["status"].(float64)) == 200 { if int(resp["status"].(float64)) == 200 {
return getMessageInfo(msgProto).Id, nil return getMessageInfo(msgProto).Id, nil
@ -105,6 +105,105 @@ func (wac *Conn) sendProto(p *proto.WebMessageInfo) (<-chan string, error) {
return wac.writeBinary(n, message, ignore, p.Key.GetId()) return wac.writeBinary(n, message, ignore, p.Key.GetId())
} }
// RevokeMessage revokes a message (marks as "message removed") for everyone
func (wac *Conn) RevokeMessage(remotejid, msgid string, fromme bool) (revokeid string, err error) {
// create a revocation ID (required)
rawrevocationID := make([]byte, 10)
rand.Read(rawrevocationID)
revocationID := strings.ToUpper(hex.EncodeToString(rawrevocationID))
//
ts := uint64(time.Now().Unix())
status := proto.WebMessageInfo_PENDING
mtype := proto.ProtocolMessage_REVOKE
revoker := &proto.WebMessageInfo{
Key: &proto.MessageKey{
FromMe: &fromme,
Id: &revocationID,
RemoteJid: &remotejid,
},
MessageTimestamp: &ts,
Message: &proto.Message{
ProtocolMessage: &proto.ProtocolMessage{
Type: &mtype,
Key: &proto.MessageKey{
FromMe: &fromme,
Id: &msgid,
RemoteJid: &remotejid,
},
},
},
Status: &status,
}
if _, err := wac.Send(revoker); err != nil {
return revocationID, err
}
return revocationID, nil
}
// DeleteMessage deletes a single message for the user (removes the msgbox). To
// delete the message for everyone, use RevokeMessage
func (wac *Conn) DeleteMessage(remotejid, msgid string, fromMe bool) error {
ch, err := wac.deleteChatProto(remotejid, msgid, fromMe)
if err != nil {
return fmt.Errorf("could not send proto: %v", err)
}
select {
case response := <-ch:
var resp map[string]interface{}
if err = json.Unmarshal([]byte(response), &resp); err != nil {
return fmt.Errorf("error decoding deletion response: %v", err)
}
if int(resp["status"].(float64)) != 200 {
return fmt.Errorf("message deletion responded with %v", resp["status"])
}
if int(resp["status"].(float64)) == 200 {
return nil
}
case <-time.After(wac.msgTimeout):
return fmt.Errorf("deleting message timed out")
}
return nil
}
func (wac *Conn) deleteChatProto(remotejid, msgid string, fromMe bool) (<-chan string, error) {
tag := fmt.Sprintf("%s.--%d", wac.timeTag, wac.msgCount)
owner := "true"
if !fromMe {
owner = "false"
}
n := binary.Node{
Description: "action",
Attributes: map[string]string{
"epoch": strconv.Itoa(wac.msgCount),
"type": "set",
},
Content: []interface{}{
binary.Node{
Description: "chat",
Attributes: map[string]string{
"type": "clear",
"jid": remotejid,
"media": "true",
},
Content: []binary.Node{
{
Description: "item",
Attributes: map[string]string{
"owner": owner,
"index": msgid,
},
},
},
},
},
}
return wac.writeBinary(n, chat, expires|skipOffline, tag)
}
func init() { func init() {
rand.Seed(time.Now().UTC().UnixNano()) rand.Seed(time.Now().UTC().UnixNano())
} }
@ -744,3 +843,38 @@ func ParseProtoMessage(msg *proto.WebMessageInfo) interface{} {
return nil return nil
} }
/*
BatteryMessage represents a battery level and charging state.
*/
type BatteryMessage struct {
Plugged bool
Powersave bool
Percentage int
}
func getBatteryMessage(msg map[string]string) BatteryMessage {
plugged, _ := strconv.ParseBool(msg["live"])
powersave, _ := strconv.ParseBool(msg["powersave"])
percentage, _ := strconv.Atoi(msg["value"])
batteryMessage := BatteryMessage{
Plugged: plugged,
Powersave: powersave,
Percentage: percentage,
}
return batteryMessage
}
func ParseNodeMessage(msg binary.Node) interface{} {
switch msg.Description {
case "battery":
return getBatteryMessage(msg.Attributes)
default:
//cannot match message
}
return nil
}

View File

@ -5,13 +5,14 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"io/ioutil"
"strings"
"github.com/Rhymen/go-whatsapp/binary" "github.com/Rhymen/go-whatsapp/binary"
"github.com/Rhymen/go-whatsapp/crypto/cbc" "github.com/Rhymen/go-whatsapp/crypto/cbc"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/pkg/errors" "github.com/pkg/errors"
"io"
"io/ioutil"
"strings"
) )
func (wac *Conn) readPump() { func (wac *Conn) readPump() {
@ -27,7 +28,9 @@ func (wac *Conn) readPump() {
for { for {
readerFound := make(chan struct{}) readerFound := make(chan struct{})
go func() { go func() {
msgType, reader, readErr = wac.ws.conn.NextReader() if wac.ws != nil {
msgType, reader, readErr = wac.ws.conn.NextReader()
}
close(readerFound) close(readerFound)
}() }()
select { select {

View File

@ -18,7 +18,7 @@ import (
) )
//represents the WhatsAppWeb client version //represents the WhatsAppWeb client version
var waVersion = []int{0, 4, 2080} var waVersion = []int{2, 2033, 7}
/* /*
Session contains session individual information. To be able to resume the connection without scanning the qr code Session contains session individual information. To be able to resume the connection without scanning the qr code
@ -141,11 +141,11 @@ func CheckCurrentServerVersion() ([]int, error) {
SetClientName sets the long and short client names that are sent to WhatsApp when logging in and displayed in the SetClientName sets the long and short client names that are sent to WhatsApp when logging in and displayed in the
WhatsApp Web device list. As the values are only sent when logging in, changing them after logging in is not possible. WhatsApp Web device list. As the values are only sent when logging in, changing them after logging in is not possible.
*/ */
func (wac *Conn) SetClientName(long, short, version string) error { func (wac *Conn) SetClientName(long, short string) error {
if wac.session != nil && (wac.session.EncKey != nil || wac.session.MacKey != nil) { if wac.session != nil && (wac.session.EncKey != nil || wac.session.MacKey != nil) {
return fmt.Errorf("cannot change client name after logging in") return fmt.Errorf("cannot change client name after logging in")
} }
wac.longClientName, wac.shortClientName, wac.clientVersion = long, short, version wac.longClientName, wac.shortClientName = long, short
return nil return nil
} }
@ -231,7 +231,12 @@ func (wac *Conn) Login(qrChan chan<- string) (Session, error) {
return session, fmt.Errorf("error decoding login resp: %v\n", err) return session, fmt.Errorf("error decoding login resp: %v\n", err)
} }
ref := resp["ref"].(string) var ref string
if rref, ok := resp["ref"].(string); ok {
ref = rref
} else {
return session, fmt.Errorf("error decoding login resp: invalid resp['ref']\n")
}
priv, pub, err := curve25519.GenerateKey() priv, pub, err := curve25519.GenerateKey()
if err != nil { if err != nil {
@ -390,9 +395,11 @@ func (wac *Conn) Restore() error {
} }
if int(resp["status"].(float64)) != 200 { if int(resp["status"].(float64)) != 200 {
wac.timeTag = ""
return fmt.Errorf("init responded with %d", resp["status"]) return fmt.Errorf("init responded with %d", resp["status"])
} }
case <-time.After(wac.msgTimeout): case <-time.After(wac.msgTimeout):
wac.timeTag = ""
return fmt.Errorf("restore session init timed out") return fmt.Errorf("restore session init timed out")
} }
@ -401,10 +408,11 @@ func (wac *Conn) Restore() error {
select { select {
case r1 := <-s1: case r1 := <-s1:
if err := json.Unmarshal([]byte(r1), &connResp); err != nil { if err := json.Unmarshal([]byte(r1), &connResp); err != nil {
wac.timeTag = ""
return fmt.Errorf("error decoding s1 message: %v\n", err) return fmt.Errorf("error decoding s1 message: %v\n", err)
} }
case <-time.After(wac.msgTimeout): case <-time.After(wac.msgTimeout):
wac.timeTag = ""
//check for an error message //check for an error message
select { select {
case r := <-loginChan: case r := <-loginChan:
@ -429,15 +437,18 @@ func (wac *Conn) Restore() error {
wac.listener.Unlock() wac.listener.Unlock()
if err := wac.resolveChallenge(connResp[1].(map[string]interface{})["challenge"].(string)); err != nil { if err := wac.resolveChallenge(connResp[1].(map[string]interface{})["challenge"].(string)); err != nil {
wac.timeTag = ""
return fmt.Errorf("error resolving challenge: %v\n", err) return fmt.Errorf("error resolving challenge: %v\n", err)
} }
select { select {
case r := <-s2: case r := <-s2:
if err := json.Unmarshal([]byte(r), &connResp); err != nil { if err := json.Unmarshal([]byte(r), &connResp); err != nil {
wac.timeTag = ""
return fmt.Errorf("error decoding s2 message: %v\n", err) return fmt.Errorf("error decoding s2 message: %v\n", err)
} }
case <-time.After(wac.msgTimeout): case <-time.After(wac.msgTimeout):
wac.timeTag = ""
return fmt.Errorf("restore session challenge timed out") return fmt.Errorf("restore session challenge timed out")
} }
} }
@ -447,13 +458,16 @@ func (wac *Conn) Restore() error {
case r := <-loginChan: case r := <-loginChan:
var resp map[string]interface{} var resp map[string]interface{}
if err = json.Unmarshal([]byte(r), &resp); err != nil { if err = json.Unmarshal([]byte(r), &resp); err != nil {
wac.timeTag = ""
return fmt.Errorf("error decoding login connResp: %v\n", err) return fmt.Errorf("error decoding login connResp: %v\n", err)
} }
if int(resp["status"].(float64)) != 200 { if int(resp["status"].(float64)) != 200 {
wac.timeTag = ""
return fmt.Errorf("admin login responded with %d", resp["status"]) return fmt.Errorf("admin login responded with %d", resp["status"])
} }
case <-time.After(wac.msgTimeout): case <-time.After(wac.msgTimeout):
wac.timeTag = ""
return fmt.Errorf("restore session login timed out") return fmt.Errorf("restore session login timed out")
} }

View File

@ -30,6 +30,11 @@ func (wac *Conn) writeJson(data []interface{}) (<-chan string, error) {
messageTag := fmt.Sprintf("%d.--%d", ts, wac.msgCount) messageTag := fmt.Sprintf("%d.--%d", ts, wac.msgCount)
bytes := []byte(fmt.Sprintf("%s,%s", messageTag, d)) bytes := []byte(fmt.Sprintf("%s,%s", messageTag, d))
if wac.timeTag == "" {
tss := fmt.Sprintf("%d", ts)
wac.timeTag = tss[len(tss)-3:]
}
ch, err := wac.write(websocket.TextMessage, messageTag, bytes) ch, err := wac.write(websocket.TextMessage, messageTag, bytes)
if err != nil { if err != nil {
return nil, err return nil, err
@ -127,6 +132,9 @@ func (wac *Conn) write(messageType int, answerMessageTag string, data []byte) (<
wac.listener.Unlock() wac.listener.Unlock()
} }
if wac == nil || wac.ws == nil {
return nil, ErrInvalidWebsocket
}
wac.ws.Lock() wac.ws.Lock()
err := wac.ws.conn.WriteMessage(messageType, data) err := wac.ws.conn.WriteMessage(messageType, data)
wac.ws.Unlock() wac.ws.Unlock()

2
vendor/modules.txt vendored
View File

@ -15,7 +15,7 @@ github.com/Philipp15b/go-steam/protocol/steamlang
github.com/Philipp15b/go-steam/rwu github.com/Philipp15b/go-steam/rwu
github.com/Philipp15b/go-steam/socialcache github.com/Philipp15b/go-steam/socialcache
github.com/Philipp15b/go-steam/steamid github.com/Philipp15b/go-steam/steamid
# github.com/Rhymen/go-whatsapp v0.1.1-0.20200421062035-31e8111ac334 # github.com/Rhymen/go-whatsapp v0.1.1-0.20200818115958-f07a700b9819
github.com/Rhymen/go-whatsapp github.com/Rhymen/go-whatsapp
github.com/Rhymen/go-whatsapp/binary github.com/Rhymen/go-whatsapp/binary
github.com/Rhymen/go-whatsapp/binary/proto github.com/Rhymen/go-whatsapp/binary/proto