Update Rhymen/go-whatsapp vendor (#876)

This commit is contained in:
Wim 2019-08-26 23:22:34 +02:00 committed by GitHub
parent 921f2dfcdf
commit d525f1c9e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 509 additions and 40 deletions

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.20180818081528-681bd9573329 github.com/Philipp15b/go-steam v1.0.1-0.20180818081528-681bd9573329
github.com/Rhymen/go-whatsapp v0.0.2 github.com/Rhymen/go-whatsapp v0.0.3-0.20190729104911-5c79b2cf277a
github.com/bwmarrin/discordgo v0.19.0 github.com/bwmarrin/discordgo v0.19.0
github.com/d5/tengo v1.24.1 github.com/d5/tengo v1.24.1
github.com/dfordsoft/golib v0.0.0-20180902042739-76ee6ab99bec github.com/dfordsoft/golib v0.0.0-20180902042739-76ee6ab99bec

4
go.sum
View File

@ -11,8 +11,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
github.com/Philipp15b/go-steam v1.0.1-0.20180818081528-681bd9573329 h1:xZBoq249G9MSt+XuY7sVQzcfONJ6IQuwpCK+KAaOpnY= github.com/Philipp15b/go-steam v1.0.1-0.20180818081528-681bd9573329 h1:xZBoq249G9MSt+XuY7sVQzcfONJ6IQuwpCK+KAaOpnY=
github.com/Philipp15b/go-steam v1.0.1-0.20180818081528-681bd9573329/go.mod h1:HuVM+sZFzumUdKPWiz+IlCMb4RdsKdT3T+nQBKL+sYg= github.com/Philipp15b/go-steam v1.0.1-0.20180818081528-681bd9573329/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.0.2 h1:MelwdquHuuNObBGV7CpXbky2aVdilx/CwiXMwZvS74U= github.com/Rhymen/go-whatsapp v0.0.3-0.20190729104911-5c79b2cf277a h1:umvfZW+YE+ynhYwsyheyunB/3xRK68kNFMRNUMQxzJI=
github.com/Rhymen/go-whatsapp v0.0.2/go.mod h1:qf/2PQi82Okxw/igghu/oMGzTeUYuKBq1JNo3tdQyNg= github.com/Rhymen/go-whatsapp v0.0.3-0.20190729104911-5c79b2cf277a/go.mod h1:qf/2PQi82Okxw/igghu/oMGzTeUYuKBq1JNo3tdQyNg=
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

@ -50,17 +50,25 @@ func (myHandler) HandleImageMessage(message whatsapp.ImageMessage) {
fmt.Println(message) fmt.Println(message)
} }
func (myHandler) HandleDocumentMessage(message whatsapp.DocumentMessage) {
fmt.Println(message)
}
func (myHandler) HandleVideoMessage(message whatsapp.VideoMessage) { func (myHandler) HandleVideoMessage(message whatsapp.VideoMessage) {
fmt.Println(message) fmt.Println(message)
} }
func (myHandler) HandleAudioMessage(message whatsapp.AudioMessage){
fmt.Println(message)
}
func (myHandler) HandleJsonMessage(message string) { func (myHandler) HandleJsonMessage(message string) {
fmt.Println(message) 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 and VideoMessage 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.
### Sending text messages ### Sending text messages
```go ```go

183
vendor/github.com/Rhymen/go-whatsapp/chat_history.go generated vendored Normal file
View File

@ -0,0 +1,183 @@
package whatsapp
import (
"github.com/Rhymen/go-whatsapp/binary"
"github.com/Rhymen/go-whatsapp/binary/proto"
"log"
"strconv"
"time"
)
type MessageOffsetInfo struct {
FirstMessageId string
FirstMessageOwner bool
}
func decodeMessages(n *binary.Node) []*proto.WebMessageInfo {
var messages = make([]*proto.WebMessageInfo, 0)
if n == nil || n.Attributes == nil || n.Content == nil {
return messages
}
for _, msg := range n.Content.([]interface{}) {
switch msg.(type) {
case *proto.WebMessageInfo:
messages = append(messages, msg.(*proto.WebMessageInfo))
default:
log.Println("decodeMessages: Non WebMessage encountered")
}
}
return messages
}
// LoadChatMessages is useful to "scroll" messages, loading by count at a time
// if handlers == nil the func will use default handlers
// if after == true LoadChatMessages will load messages after the specified messageId, otherwise it will return
// message before the messageId
func (wac *Conn) LoadChatMessages(jid string, count int, messageId string, owner bool, after bool, handlers ...Handler) error {
if count <= 0 {
return nil
}
if handlers == nil {
handlers = wac.handler
}
kind := "before"
if after {
kind = "after"
}
node, err := wac.query("message", jid, messageId, kind,
strconv.FormatBool(owner), "", count, 0)
if err != nil {
wac.handleWithCustomHandlers(err, handlers)
return err
}
for _, msg := range decodeMessages(node) {
wac.handleWithCustomHandlers(ParseProtoMessage(msg), handlers)
wac.handleWithCustomHandlers(msg, handlers)
}
return nil
}
// LoadFullChatHistory loads full chat history for the given jid
// chunkSize = how many messages to load with one query; if handlers == nil the func will use default handlers;
// pauseBetweenQueries = how much time to sleep between queries
func (wac *Conn) LoadFullChatHistory(jid string, chunkSize int,
pauseBetweenQueries time.Duration, handlers ...Handler) {
if chunkSize <= 0 {
return
}
if handlers == nil {
handlers = wac.handler
}
beforeMsg := ""
beforeMsgIsOwner := true
for {
node, err := wac.query("message", jid, beforeMsg, "before",
strconv.FormatBool(beforeMsgIsOwner), "", chunkSize, 0)
if err != nil {
wac.handleWithCustomHandlers(err, handlers)
} else {
msgs := decodeMessages(node)
for _, msg := range msgs {
wac.handleWithCustomHandlers(ParseProtoMessage(msg), handlers)
wac.handleWithCustomHandlers(msg, handlers)
}
if len(msgs) == 0 {
break
}
beforeMsg = *msgs[0].Key.Id
beforeMsgIsOwner = msgs[0].Key.FromMe != nil && *msgs[0].Key.FromMe
}
<-time.After(pauseBetweenQueries)
}
}
// LoadFullChatHistoryAfter loads all messages after the specified messageId
// useful to "catch up" with the message history after some specified message
func (wac *Conn) LoadFullChatHistoryAfter(jid string, messageId string, chunkSize int,
pauseBetweenQueries time.Duration, handlers ...Handler) {
if chunkSize <= 0 {
return
}
if handlers == nil {
handlers = wac.handler
}
msgOwner := true
prevNotFound := false
for {
node, err := wac.query("message", jid, messageId, "after",
strconv.FormatBool(msgOwner), "", chunkSize, 0)
if err != nil {
// Whatsapp will return 404 status when there is wrong owner flag on the requested message id
if err == ErrServerRespondedWith404 {
// this will detect two consecutive "not found" errors.
// this is done to prevent infinite loop when wrong message id supplied
if prevNotFound {
log.Println("LoadFullChatHistoryAfter: could not retrieve any messages, wrong message id?")
return
}
prevNotFound = true
// try to reverse the owner flag and retry
if msgOwner {
// reverse initial msgOwner value and retry
msgOwner = false
<-time.After(time.Second)
continue
}
}
// if the error isn't a 404 error, pass it to the error handler
wac.handleWithCustomHandlers(err, handlers)
} else {
msgs := decodeMessages(node)
for _, msg := range msgs {
wac.handleWithCustomHandlers(ParseProtoMessage(msg), handlers)
wac.handleWithCustomHandlers(msg, handlers)
}
if len(msgs) != chunkSize {
break
}
messageId = *msgs[0].Key.Id
msgOwner = msgs[0].Key.FromMe != nil && *msgs[0].Key.FromMe
}
// message was found
prevNotFound = false
<-time.After(pauseBetweenQueries)
}
}

View File

@ -89,6 +89,8 @@ type Conn struct {
longClientName string longClientName string
shortClientName string shortClientName string
loginSessionLock sync.RWMutex
} }
type websocketWrapper struct { type websocketWrapper struct {
@ -191,6 +193,19 @@ func (wac *Conn) Disconnect() (Session, error) {
return *wac.session, err return *wac.session, err
} }
func (wac *Conn) AdminTest() (bool, error) {
if !wac.connected {
return false, ErrNotConnected
}
if !wac.loggedIn {
return false, ErrInvalidSession
}
result, err := wac.sendAdminTest()
return result, err
}
func (wac *Conn) keepAlive(minIntervalMs int, maxIntervalMs int) { func (wac *Conn) keepAlive(minIntervalMs int, maxIntervalMs int) {
defer wac.wg.Done() defer wac.wg.Done()

View File

@ -12,9 +12,12 @@ var (
ErrLoginInProgress = errors.New("login or restore already running") ErrLoginInProgress = errors.New("login or restore already running")
ErrNotConnected = errors.New("not connected") ErrNotConnected = errors.New("not connected")
ErrInvalidWsData = errors.New("received invalid data") ErrInvalidWsData = errors.New("received invalid data")
ErrInvalidWsState = errors.New("can't handle binary data when not logged in")
ErrConnectionTimeout = errors.New("connection timed out") ErrConnectionTimeout = errors.New("connection timed out")
ErrMissingMessageTag = errors.New("no messageTag specified or to short") ErrMissingMessageTag = errors.New("no messageTag specified or to short")
ErrInvalidHmac = errors.New("invalid hmac") ErrInvalidHmac = errors.New("invalid hmac")
ErrInvalidServerResponse = errors.New("invalid response received from server")
ErrServerRespondedWith404 = errors.New("server responded with status 404")
) )
type ErrConnectionFailed struct { type ErrConnectionFailed struct {

View File

@ -20,6 +20,11 @@ type Handler interface {
HandleError(err error) HandleError(err error)
} }
type SyncHandler interface {
Handler
ShouldCallSynchronously() bool
}
/* /*
The TextMessageHandler interface needs to be implemented to receive text messages dispatched by the dispatcher. The TextMessageHandler interface needs to be implemented to receive text messages dispatched by the dispatcher.
*/ */
@ -60,6 +65,22 @@ type DocumentMessageHandler interface {
HandleDocumentMessage(message DocumentMessage) HandleDocumentMessage(message DocumentMessage)
} }
/*
The LiveLocationMessageHandler interface needs to be implemented to receive live location messages dispatched by the dispatcher.
*/
type LiveLocationMessageHandler interface {
Handler
HandleLiveLocationMessage(message LiveLocationMessage)
}
/*
The LocationMessageHandler interface needs to be implemented to receive location messages dispatched by the dispatcher.
*/
type LocationMessageHandler interface {
Handler
HandleLocationMessage(message LocationMessage)
}
/* /*
The JsonMessageHandler interface needs to be implemented to receive json messages dispatched by the dispatcher. The JsonMessageHandler interface needs to be implemented to receive json messages dispatched by the dispatcher.
These json messages contain status updates of every kind sent by WhatsAppWeb servers. WhatsAppWeb uses these messages These json messages contain status updates of every kind sent by WhatsAppWeb servers. WhatsAppWeb uses these messages
@ -127,55 +148,116 @@ func (wac *Conn) RemoveHandlers() {
wac.handler = make([]Handler, 0) wac.handler = make([]Handler, 0)
} }
func (wac *Conn) shouldCallSynchronously(handler Handler) bool {
sh, ok := handler.(SyncHandler)
return ok && sh.ShouldCallSynchronously()
}
func (wac *Conn) handle(message interface{}) { func (wac *Conn) handle(message interface{}) {
wac.handleWithCustomHandlers(message, wac.handler)
}
func (wac *Conn) handleWithCustomHandlers(message interface{}, handlers []Handler) {
switch m := message.(type) { switch m := message.(type) {
case error: case error:
for _, h := range wac.handler { for _, h := range handlers {
if wac.shouldCallSynchronously(h) {
h.HandleError(m)
} else {
go h.HandleError(m) go h.HandleError(m)
} }
}
case string: case string:
for _, h := range wac.handler { for _, h := range handlers {
if x, ok := h.(JsonMessageHandler); ok { if x, ok := h.(JsonMessageHandler); ok {
if wac.shouldCallSynchronously(h) {
x.HandleJsonMessage(m)
} else {
go x.HandleJsonMessage(m) go x.HandleJsonMessage(m)
} }
} }
}
case TextMessage: case TextMessage:
for _, h := range wac.handler { for _, h := range handlers {
if x, ok := h.(TextMessageHandler); ok { if x, ok := h.(TextMessageHandler); ok {
if wac.shouldCallSynchronously(h) {
x.HandleTextMessage(m)
} else {
go x.HandleTextMessage(m) go x.HandleTextMessage(m)
} }
} }
}
case ImageMessage: case ImageMessage:
for _, h := range wac.handler { for _, h := range handlers {
if x, ok := h.(ImageMessageHandler); ok { if x, ok := h.(ImageMessageHandler); ok {
if wac.shouldCallSynchronously(h) {
x.HandleImageMessage(m)
} else {
go x.HandleImageMessage(m) go x.HandleImageMessage(m)
} }
} }
}
case VideoMessage: case VideoMessage:
for _, h := range wac.handler { for _, h := range handlers {
if x, ok := h.(VideoMessageHandler); ok { if x, ok := h.(VideoMessageHandler); ok {
if wac.shouldCallSynchronously(h) {
x.HandleVideoMessage(m)
} else {
go x.HandleVideoMessage(m) go x.HandleVideoMessage(m)
} }
} }
}
case AudioMessage: case AudioMessage:
for _, h := range wac.handler { for _, h := range handlers {
if x, ok := h.(AudioMessageHandler); ok { if x, ok := h.(AudioMessageHandler); ok {
if wac.shouldCallSynchronously(h) {
x.HandleAudioMessage(m)
} else {
go x.HandleAudioMessage(m) go x.HandleAudioMessage(m)
} }
} }
}
case DocumentMessage: case DocumentMessage:
for _, h := range wac.handler { for _, h := range handlers {
if x, ok := h.(DocumentMessageHandler); ok { if x, ok := h.(DocumentMessageHandler); ok {
if wac.shouldCallSynchronously(h) {
x.HandleDocumentMessage(m)
} else {
go x.HandleDocumentMessage(m) go x.HandleDocumentMessage(m)
} }
} }
}
case LocationMessage:
for _, h := range handlers {
if x, ok := h.(LocationMessageHandler); ok {
if wac.shouldCallSynchronously(h) {
x.HandleLocationMessage(m)
} else {
go x.HandleLocationMessage(m)
}
}
}
case LiveLocationMessage:
for _, h := range handlers {
if x, ok := h.(LiveLocationMessageHandler); ok {
if wac.shouldCallSynchronously(h) {
x.HandleLiveLocationMessage(m)
} else {
go x.HandleLiveLocationMessage(m)
}
}
}
case *proto.WebMessageInfo: case *proto.WebMessageInfo:
for _, h := range wac.handler { for _, h := range handlers {
if x, ok := h.(RawMessageHandler); ok { if x, ok := h.(RawMessageHandler); ok {
if wac.shouldCallSynchronously(h) {
x.HandleRawMessage(m)
} else {
go x.HandleRawMessage(m) go x.HandleRawMessage(m)
} }
} }
} }
}
} }
@ -201,9 +283,13 @@ func (wac *Conn) handleContacts(contacts interface{}) {
} }
for _, h := range wac.handler { for _, h := range wac.handler {
if x, ok := h.(ContactListHandler); ok { if x, ok := h.(ContactListHandler); ok {
if wac.shouldCallSynchronously(h) {
x.HandleContactList(contactList)
} else {
go x.HandleContactList(contactList) go x.HandleContactList(contactList)
} }
} }
}
} }
func (wac *Conn) handleChats(chats interface{}) { func (wac *Conn) handleChats(chats interface{}) {
@ -230,9 +316,13 @@ func (wac *Conn) handleChats(chats interface{}) {
} }
for _, h := range wac.handler { for _, h := range wac.handler {
if x, ok := h.(ChatListHandler); ok { if x, ok := h.(ChatListHandler); ok {
if wac.shouldCallSynchronously(h) {
x.HandleChatList(chatList)
} else {
go x.HandleChatList(chatList) go x.HandleChatList(chatList)
} }
} }
}
} }
func (wac *Conn) dispatch(msg interface{}) { func (wac *Conn) dispatch(msg interface{}) {
@ -247,7 +337,7 @@ func (wac *Conn) dispatch(msg interface{}) {
for a := range con { for a := range con {
if v, ok := con[a].(*proto.WebMessageInfo); ok { if v, ok := con[a].(*proto.WebMessageInfo); ok {
wac.handle(v) wac.handle(v)
wac.handle(parseProtoMessage(v)) wac.handle(ParseProtoMessage(v))
} }
} }
} }

View File

@ -8,8 +8,6 @@ import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/Rhymen/go-whatsapp/crypto/cbc"
"github.com/Rhymen/go-whatsapp/crypto/hkdf"
"io" "io"
"io/ioutil" "io/ioutil"
"mime/multipart" "mime/multipart"
@ -17,6 +15,9 @@ import (
"os" "os"
"strings" "strings"
"time" "time"
"github.com/Rhymen/go-whatsapp/crypto/cbc"
"github.com/Rhymen/go-whatsapp/crypto/hkdf"
) )
func Download(url string, mediaKey []byte, appInfo MediaType, fileLength int) ([]byte, error) { func Download(url string, mediaKey []byte, appInfo MediaType, fileLength int) ([]byte, error) {
@ -73,17 +74,18 @@ func downloadMedia(url string) (file []byte, mac []byte, err error) {
return nil, nil, err return nil, nil, err
} }
if resp.StatusCode != 200 { if resp.StatusCode != 200 {
return nil, nil, fmt.Errorf("download failed") return nil, nil, fmt.Errorf("download failed with status code %d", resp.StatusCode)
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.ContentLength <= 10 { if resp.ContentLength <= 10 {
return nil, nil, fmt.Errorf("file to short") return nil, nil, fmt.Errorf("file to short")
} }
data, err := ioutil.ReadAll(resp.Body) data, err := ioutil.ReadAll(resp.Body)
n := len(data)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
n := len(data)
return data[:n-10], data[n-10 : n], nil return data[:n-10], data[n-10 : n], nil
} }
@ -142,7 +144,7 @@ func (wac *Conn) Upload(reader io.Reader, appInfo MediaType) (url string, mediaK
select { select {
case r := <-ch: case r := <-ch:
if err = json.Unmarshal([]byte(r), &resp); err != nil { if err = json.Unmarshal([]byte(r), &resp); err != nil {
return "", nil, nil, nil, 0, fmt.Errorf("error decoding upload response: %v\n", err) return "", nil, nil, nil, 0, fmt.Errorf("error decoding upload response: %v", err)
} }
case <-time.After(wac.msgTimeout): case <-time.After(wac.msgTimeout):
return "", nil, nil, nil, 0, fmt.Errorf("restore session init timed out") return "", nil, nil, nil, 0, fmt.Errorf("restore session init timed out")

View File

@ -68,6 +68,14 @@ func (wac *Conn) Send(msg interface{}) (string, error) {
msgProto = getAudioProto(m) msgProto = getAudioProto(m)
msgInfo = getMessageInfo(msgProto) msgInfo = getMessageInfo(msgProto)
ch, err = wac.sendProto(msgProto) ch, err = wac.sendProto(msgProto)
case LocationMessage:
msgProto = GetLocationProto(m)
msgInfo = getMessageInfo(msgProto)
ch, err = wac.sendProto(msgProto)
case LiveLocationMessage:
msgProto = GetLiveLocationProto(m)
msgInfo = getMessageInfo(msgProto)
ch, err = wac.sendProto(msgProto)
default: default:
return "ERROR", fmt.Errorf("cannot match type %T, use message types declared in the package", msg) return "ERROR", fmt.Errorf("cannot match type %T, use message types declared in the package", msg)
} }
@ -269,6 +277,7 @@ type VideoMessage struct {
Length uint32 Length uint32
Type string Type string
Content io.Reader Content io.Reader
GifPlayback bool
url string url string
mediaKey []byte mediaKey []byte
fileEncSha256 []byte fileEncSha256 []byte
@ -282,6 +291,7 @@ func getVideoMessage(msg *proto.WebMessageInfo) VideoMessage {
Info: getMessageInfo(msg), Info: getMessageInfo(msg),
Caption: vid.GetCaption(), Caption: vid.GetCaption(),
Thumbnail: vid.GetJpegThumbnail(), Thumbnail: vid.GetJpegThumbnail(),
GifPlayback: vid.GetGifPlayback(),
url: vid.GetUrl(), url: vid.GetUrl(),
mediaKey: vid.GetMediaKey(), mediaKey: vid.GetMediaKey(),
Length: vid.GetSeconds(), Length: vid.GetSeconds(),
@ -299,6 +309,7 @@ func getVideoProto(msg VideoMessage) *proto.WebMessageInfo {
Caption: &msg.Caption, Caption: &msg.Caption,
JpegThumbnail: msg.Thumbnail, JpegThumbnail: msg.Thumbnail,
Url: &msg.url, Url: &msg.url,
GifPlayback: &msg.GifPlayback,
MediaKey: msg.mediaKey, MediaKey: msg.mediaKey,
Seconds: &msg.Length, Seconds: &msg.Length,
FileEncSha256: msg.fileEncSha256, FileEncSha256: msg.fileEncSha256,
@ -431,7 +442,95 @@ func (m *DocumentMessage) Download() ([]byte, error) {
return Download(m.url, m.mediaKey, MediaDocument, int(m.fileLength)) return Download(m.url, m.mediaKey, MediaDocument, int(m.fileLength))
} }
func parseProtoMessage(msg *proto.WebMessageInfo) interface{} { /*
LocationMessage represents a location message
*/
type LocationMessage struct {
Info MessageInfo
DegreesLatitude float64
DegreesLongitude float64
Name string
Address string
Url string
JpegThumbnail []byte
}
func GetLocationMessage(msg *proto.WebMessageInfo) LocationMessage {
loc := msg.GetMessage().GetLocationMessage()
return LocationMessage{
Info: getMessageInfo(msg),
DegreesLatitude: loc.GetDegreesLatitude(),
DegreesLongitude: loc.GetDegreesLongitude(),
Name: loc.GetName(),
Address: loc.GetAddress(),
Url: loc.GetUrl(),
JpegThumbnail: loc.GetJpegThumbnail(),
}
}
func GetLocationProto(msg LocationMessage) *proto.WebMessageInfo {
p := getInfoProto(&msg.Info)
p.Message = &proto.Message{
LocationMessage: &proto.LocationMessage{
DegreesLatitude: &msg.DegreesLatitude,
DegreesLongitude: &msg.DegreesLongitude,
Name: &msg.Name,
Address: &msg.Address,
Url: &msg.Url,
JpegThumbnail: msg.JpegThumbnail,
},
}
return p
}
/*
LiveLocationMessage represents a live location message
*/
type LiveLocationMessage struct {
Info MessageInfo
DegreesLatitude float64
DegreesLongitude float64
AccuracyInMeters uint32
SpeedInMps float32
DegreesClockwiseFromMagneticNorth uint32
Caption string
SequenceNumber int64
JpegThumbnail []byte
}
func GetLiveLocationMessage(msg *proto.WebMessageInfo) LiveLocationMessage {
loc := msg.GetMessage().GetLiveLocationMessage()
return LiveLocationMessage{
Info: getMessageInfo(msg),
DegreesLatitude: loc.GetDegreesLatitude(),
DegreesLongitude: loc.GetDegreesLongitude(),
AccuracyInMeters: loc.GetAccuracyInMeters(),
SpeedInMps: loc.GetSpeedInMps(),
DegreesClockwiseFromMagneticNorth: loc.GetDegreesClockwiseFromMagneticNorth(),
Caption: loc.GetCaption(),
SequenceNumber: loc.GetSequenceNumber(),
JpegThumbnail: loc.GetJpegThumbnail(),
}
}
func GetLiveLocationProto(msg LiveLocationMessage) *proto.WebMessageInfo {
p := getInfoProto(&msg.Info)
p.Message = &proto.Message{
LiveLocationMessage: &proto.LiveLocationMessage{
DegreesLatitude: &msg.DegreesLatitude,
DegreesLongitude: &msg.DegreesLongitude,
AccuracyInMeters: &msg.AccuracyInMeters,
SpeedInMps: &msg.SpeedInMps,
DegreesClockwiseFromMagneticNorth: &msg.DegreesClockwiseFromMagneticNorth,
Caption: &msg.Caption,
SequenceNumber: &msg.SequenceNumber,
JpegThumbnail: msg.JpegThumbnail,
},
}
return p
}
func ParseProtoMessage(msg *proto.WebMessageInfo) interface{} {
switch { switch {
case msg.GetMessage().GetAudioMessage() != nil: case msg.GetMessage().GetAudioMessage() != nil:
@ -452,6 +551,12 @@ func parseProtoMessage(msg *proto.WebMessageInfo) interface{} {
case msg.GetMessage().GetExtendedTextMessage() != nil: case msg.GetMessage().GetExtendedTextMessage() != nil:
return getTextMessage(msg) return getTextMessage(msg)
case msg.GetMessage().GetLocationMessage() != nil:
return GetLocationMessage(msg)
case msg.GetMessage().GetLiveLocationMessage() != nil:
return GetLiveLocationMessage(msg)
default: default:
//cannot match message //cannot match message
} }

View File

@ -3,6 +3,8 @@ package whatsapp
import ( import (
"crypto/hmac" "crypto/hmac"
"crypto/sha256" "crypto/sha256"
"encoding/json"
"fmt"
"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"
@ -75,7 +77,13 @@ func (wac *Conn) processReadData(msgType int, msg []byte) error {
wac.listener.Lock() wac.listener.Lock()
delete(wac.listener.m, data[0]) delete(wac.listener.m, data[0])
wac.listener.Unlock() wac.listener.Unlock()
} else if msgType == websocket.BinaryMessage && wac.loggedIn { } else if msgType == websocket.BinaryMessage {
wac.loginSessionLock.RLock()
sess := wac.session
wac.loginSessionLock.RUnlock()
if sess == nil || sess.MacKey == nil || sess.EncKey == nil {
return ErrInvalidWsState
}
message, err := wac.decryptBinaryMessage([]byte(data[1])) message, err := wac.decryptBinaryMessage([]byte(data[1]))
if err != nil { if err != nil {
return errors.Wrap(err, "error decoding binary") return errors.Wrap(err, "error decoding binary")
@ -90,6 +98,21 @@ func (wac *Conn) processReadData(msgType int, msg []byte) error {
func (wac *Conn) decryptBinaryMessage(msg []byte) (*binary.Node, error) { func (wac *Conn) decryptBinaryMessage(msg []byte) (*binary.Node, error) {
//message validation //message validation
h2 := hmac.New(sha256.New, wac.session.MacKey) h2 := hmac.New(sha256.New, wac.session.MacKey)
if len(msg) < 33 {
var response struct {
Status int `json:"status"`
}
err := json.Unmarshal(msg, &response)
if err == nil {
if response.Status == 404 {
return nil, ErrServerRespondedWith404
}
return nil, errors.New(fmt.Sprintf("server responded with %d", response.Status))
} else {
return nil, ErrInvalidServerResponse
}
}
h2.Write([]byte(msg[32:])) h2.Write([]byte(msg[32:]))
if !hmac.Equal(h2.Sum(nil), msg[:32]) { if !hmac.Equal(h2.Sum(nil), msg[:32]) {
return nil, ErrInvalidHmac return nil, ErrInvalidHmac

View File

@ -100,6 +100,14 @@ func (wac *Conn) SetClientName(long, short string) error {
return nil return nil
} }
/*
SetClientVersion sets WhatsApp client version
Default value is 0.3.3324
*/
func (wac *Conn) SetClientVersion(major int, minor int, patch int) {
waVersion = []int{major, minor, patch}
}
/* /*
Login is the function that creates a new whatsapp session and logs you in. If you do not want to scan the qr code Login is the function that creates a new whatsapp session and logs you in. If you do not want to scan the qr code
every time, you should save the returned session and use RestoreWithSession the next time. Login takes a writable channel every time, you should save the returned session and use RestoreWithSession the next time. Login takes a writable channel
@ -187,6 +195,8 @@ func (wac *Conn) Login(qrChan chan<- string) (Session, error) {
var resp2 []interface{} var resp2 []interface{}
select { select {
case r1 := <-s1: case r1 := <-s1:
wac.loginSessionLock.Lock()
defer wac.loginSessionLock.Unlock()
if err := json.Unmarshal([]byte(r1), &resp2); err != nil { if err := json.Unmarshal([]byte(r1), &resp2); err != nil {
return session, fmt.Errorf("error decoding qr code resp: %v", err) return session, fmt.Errorf("error decoding qr code resp: %v", err)
} }

View File

@ -78,6 +78,36 @@ func (wac *Conn) sendKeepAlive() error {
return nil return nil
} }
/*
When phone is unreachable, WhatsAppWeb sends ["admin","test"] time after time to try a successful contact.
Tested with Airplane mode and no connection at all.
*/
func (wac *Conn) sendAdminTest() (bool, error) {
data := []interface{}{"admin", "test"}
r, err := wac.writeJson(data)
if err != nil {
return false, errors.Wrap(err, "error sending admin test")
}
var response []interface{}
select {
case resp := <-r:
if err := json.Unmarshal([]byte(resp), &response); err != nil {
return false, fmt.Errorf("error decoding response message: %v\n", err)
}
case <-time.After(wac.msgTimeout):
return false, ErrConnectionTimeout
}
if len(response) == 2 && response[0].(string) == "Pong" && response[1].(bool) == true {
return true, nil
} else{
return false, nil
}
}
func (wac *Conn) write(messageType int, answerMessageTag string, data []byte) (<-chan string, error) { func (wac *Conn) write(messageType int, answerMessageTag string, data []byte) (<-chan string, error) {
var ch chan string var ch chan string
if answerMessageTag != "" { if answerMessageTag != "" {

2
vendor/modules.txt vendored
View File

@ -15,7 +15,7 @@ github.com/Philipp15b/go-steam/protocol/gamecoordinator
github.com/Philipp15b/go-steam/protocol/protobuf github.com/Philipp15b/go-steam/protocol/protobuf
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/Rhymen/go-whatsapp v0.0.2 # github.com/Rhymen/go-whatsapp v0.0.3-0.20190729104911-5c79b2cf277a
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