diff options
Diffstat (limited to 'vendor/go.mau.fi/whatsmeow/client.go')
-rw-r--r-- | vendor/go.mau.fi/whatsmeow/client.go | 118 |
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 +} |