summaryrefslogtreecommitdiffstats
path: root/vendor/go.mau.fi/whatsmeow/client.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/go.mau.fi/whatsmeow/client.go')
-rw-r--r--vendor/go.mau.fi/whatsmeow/client.go118
1 files changed, 98 insertions, 20 deletions
diff --git a/vendor/go.mau.fi/whatsmeow/client.go b/vendor/go.mau.fi/whatsmeow/client.go
index 70c82130..ea3a297c 100644
--- a/vendor/go.mau.fi/whatsmeow/client.go
+++ b/vendor/go.mau.fi/whatsmeow/client.go
@@ -10,7 +10,6 @@ package whatsmeow
import (
"context"
"crypto/rand"
- "encoding/base64"
"encoding/hex"
"errors"
"fmt"
@@ -52,6 +51,7 @@ type Client struct {
socket *socket.NoiseSocket
socketLock sync.RWMutex
+ socketWait chan struct{}
isLoggedIn uint32
expectedDisconnectVal uint32
@@ -88,6 +88,11 @@ type Client struct {
messageRetries map[string]int
messageRetriesLock sync.Mutex
+ appStateKeyRequests map[string]time.Time
+ appStateKeyRequestsLock sync.RWMutex
+
+ messageSendLock sync.Mutex
+
privacySettingsCache atomic.Value
groupParticipantsCache map[types.JID][]types.JID
@@ -99,22 +104,21 @@ type Client struct {
recentMessagesList [recentMessagesSize]recentMessageKey
recentMessagesPtr int
recentMessagesLock sync.RWMutex
+
+ sessionRecreateHistory map[types.JID]time.Time
+ sessionRecreateHistoryLock sync.Mutex
// GetMessageForRetry is used to find the source message for handling retry receipts
// when the message is not found in the recently sent message cache.
- GetMessageForRetry func(to types.JID, id types.MessageID) *waProto.Message
+ GetMessageForRetry func(requester, to types.JID, id types.MessageID) *waProto.Message
// PreRetryCallback is called before a retry receipt is accepted.
// If it returns false, the accepting will be cancelled and the retry receipt will be ignored.
- PreRetryCallback func(receipt *events.Receipt, retryCount int, msg *waProto.Message) bool
+ PreRetryCallback func(receipt *events.Receipt, id types.MessageID, retryCount int, msg *waProto.Message) bool
// Should untrusted identity errors be handled automatically? If true, the stored identity and existing signal
// sessions will be removed on untrusted identity errors, and an events.IdentityChange will be dispatched.
// If false, decrypting a message from untrusted devices will fail.
AutoTrustIdentity bool
- DebugDecodeBeforeSend bool
- OneMessageAtATime bool
- messageSendLock sync.Mutex
-
uniqueID string
idCounter uint32
@@ -162,14 +166,17 @@ func NewClient(deviceStore *store.Device, log waLog.Logger) *Client {
messageRetries: make(map[string]int),
handlerQueue: make(chan *waBinary.Node, handlerQueueSize),
appStateProc: appstate.NewProcessor(deviceStore, log.Sub("AppState")),
+ socketWait: make(chan struct{}),
historySyncNotifications: make(chan *waProto.HistorySyncNotification, 32),
groupParticipantsCache: make(map[types.JID][]types.JID),
userDevicesCache: make(map[types.JID][]types.JID),
- recentMessagesMap: make(map[recentMessageKey]*waProto.Message, recentMessagesSize),
- GetMessageForRetry: func(to types.JID, id types.MessageID) *waProto.Message { return nil },
+ recentMessagesMap: make(map[recentMessageKey]*waProto.Message, recentMessagesSize),
+ sessionRecreateHistory: make(map[types.JID]time.Time),
+ GetMessageForRetry: func(requester, to types.JID, id types.MessageID) *waProto.Message { return nil },
+ appStateKeyRequests: make(map[string]time.Time),
EnableAutoReconnect: true,
AutoTrustIdentity: true,
@@ -226,6 +233,37 @@ func (cli *Client) SetProxy(proxy socket.Proxy) {
cli.http.Transport.(*http.Transport).Proxy = proxy
}
+func (cli *Client) getSocketWaitChan() <-chan struct{} {
+ cli.socketLock.RLock()
+ ch := cli.socketWait
+ cli.socketLock.RUnlock()
+ return ch
+}
+
+func (cli *Client) closeSocketWaitChan() {
+ cli.socketLock.Lock()
+ close(cli.socketWait)
+ cli.socketWait = make(chan struct{})
+ cli.socketLock.Unlock()
+}
+
+func (cli *Client) WaitForConnection(timeout time.Duration) bool {
+ timeoutChan := time.After(timeout)
+ cli.socketLock.RLock()
+ for cli.socket == nil || !cli.socket.IsConnected() || !cli.IsLoggedIn() {
+ ch := cli.socketWait
+ cli.socketLock.RUnlock()
+ select {
+ case <-ch:
+ case <-timeoutChan:
+ return false
+ }
+ cli.socketLock.RLock()
+ }
+ cli.socketLock.RUnlock()
+ return true
+}
+
// Connect connects the client to the WhatsApp web websocket. After connection, it will either
// authenticate if there's data in the device store, or emit a QREvent to set up a new link.
func (cli *Client) Connect() error {
@@ -322,6 +360,9 @@ func (cli *Client) IsConnected() bool {
}
// Disconnect disconnects from the WhatsApp web websocket.
+//
+// This will not emit any events, the Disconnected event is only used when the
+// connection is closed by the server or a network error.
func (cli *Client) Disconnect() {
if cli.socket == nil {
return
@@ -336,6 +377,7 @@ func (cli *Client) unlockedDisconnect() {
if cli.socket != nil {
cli.socket.Stop(true)
cli.socket = nil
+ cli.clearResponseWaiters(xmlStreamEndNode)
}
}
@@ -343,6 +385,9 @@ func (cli *Client) unlockedDisconnect() {
//
// If the logout request fails, the disconnection and local data deletion will not happen either.
// If an error is returned, but you want to force disconnect/clear data, call Client.Disconnect() and Client.Store.Delete() manually.
+//
+// Note that this will not emit any events. The LoggedOut event is only used for external logouts
+// (triggered by the user from the main device or by WhatsApp servers).
func (cli *Client) Logout() error {
if cli.Store.ID == nil {
return ErrNotLoggedIn
@@ -491,7 +536,7 @@ func (cli *Client) handlerQueueLoop(ctx context.Context) {
}
}
-func (cli *Client) sendNodeDebug(node waBinary.Node) ([]byte, error) {
+func (cli *Client) sendNodeAndGetData(node waBinary.Node) ([]byte, error) {
cli.socketLock.RLock()
sock := cli.socket
cli.socketLock.RUnlock()
@@ -503,22 +548,13 @@ func (cli *Client) sendNodeDebug(node waBinary.Node) ([]byte, error) {
if err != nil {
return nil, fmt.Errorf("failed to marshal node: %w", err)
}
- if cli.DebugDecodeBeforeSend {
- var decoded *waBinary.Node
- decoded, err = waBinary.Unmarshal(payload[1:])
- if err != nil {
- cli.Log.Infof("Malformed payload: %s", base64.URLEncoding.EncodeToString(payload))
- return nil, fmt.Errorf("failed to decode the binary we just produced: %w", err)
- }
- node = *decoded
- }
cli.sendLog.Debugf("%s", node.XMLString())
return payload, sock.SendFrame(payload)
}
func (cli *Client) sendNode(node waBinary.Node) error {
- _, err := cli.sendNodeDebug(node)
+ _, err := cli.sendNodeAndGetData(node)
return err
}
@@ -535,3 +571,45 @@ func (cli *Client) dispatchEvent(evt interface{}) {
handler.fn(evt)
}
}
+
+// ParseWebMessage parses a WebMessageInfo object into *events.Message to match what real-time messages have.
+//
+// The chat JID can be found in the Conversation data:
+// chatJID, err := types.ParseJID(conv.GetId())
+// for _, historyMsg := range conv.GetMessages() {
+// evt, err := cli.ParseWebMessage(chatJID, historyMsg.GetMessage())
+// yourNormalEventHandler(evt)
+// }
+func (cli *Client) ParseWebMessage(chatJID types.JID, webMsg *waProto.WebMessageInfo) (*events.Message, error) {
+ info := types.MessageInfo{
+ MessageSource: types.MessageSource{
+ Chat: chatJID,
+ IsFromMe: webMsg.GetKey().GetFromMe(),
+ IsGroup: chatJID.Server == types.GroupServer,
+ },
+ ID: webMsg.GetKey().GetId(),
+ PushName: webMsg.GetPushName(),
+ Timestamp: time.Unix(int64(webMsg.GetMessageTimestamp()), 0),
+ }
+ var err error
+ if info.IsFromMe {
+ info.Sender = cli.Store.ID.ToNonAD()
+ } else if chatJID.Server == types.DefaultUserServer {
+ info.Sender = chatJID
+ } else if webMsg.GetParticipant() != "" {
+ info.Sender, err = types.ParseJID(webMsg.GetParticipant())
+ } else if webMsg.GetKey().GetParticipant() != "" {
+ info.Sender, err = types.ParseJID(webMsg.GetKey().GetParticipant())
+ } else {
+ return nil, fmt.Errorf("couldn't find sender of message %s", info.ID)
+ }
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse sender of message %s: %v", info.ID, err)
+ }
+ evt := &events.Message{
+ RawMessage: webMsg.GetMessage(),
+ Info: info,
+ }
+ evt.UnwrapRaw()
+ return evt, nil
+}