diff options
Diffstat (limited to 'vendor/go.mau.fi/whatsmeow/notification.go')
-rw-r--r-- | vendor/go.mau.fi/whatsmeow/notification.go | 205 |
1 files changed, 205 insertions, 0 deletions
diff --git a/vendor/go.mau.fi/whatsmeow/notification.go b/vendor/go.mau.fi/whatsmeow/notification.go new file mode 100644 index 00000000..43ab69ce --- /dev/null +++ b/vendor/go.mau.fi/whatsmeow/notification.go @@ -0,0 +1,205 @@ +// Copyright (c) 2021 Tulir Asokan +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package whatsmeow + +import ( + "errors" + "time" + + "go.mau.fi/whatsmeow/appstate" + waBinary "go.mau.fi/whatsmeow/binary" + "go.mau.fi/whatsmeow/types" + "go.mau.fi/whatsmeow/types/events" +) + +func (cli *Client) handleEncryptNotification(node *waBinary.Node) { + from := node.AttrGetter().JID("from") + if from == types.ServerJID { + count := node.GetChildByTag("count") + ag := count.AttrGetter() + otksLeft := ag.Int("value") + if !ag.OK() { + cli.Log.Warnf("Didn't get number of OTKs left in encryption notification %s", node.XMLString()) + return + } + cli.Log.Infof("Got prekey count from server: %s", node.XMLString()) + if otksLeft < MinPreKeyCount { + cli.uploadPreKeys() + } + } else if _, ok := node.GetOptionalChildByTag("identity"); ok { + cli.Log.Debugf("Got identity change for %s: %s, deleting all identities/sessions for that number", from, node.XMLString()) + err := cli.Store.Identities.DeleteAllIdentities(from.User) + if err != nil { + cli.Log.Warnf("Failed to delete all identities of %s from store after identity change: %v", from, err) + } + err = cli.Store.Sessions.DeleteAllSessions(from.User) + if err != nil { + cli.Log.Warnf("Failed to delete all sessions of %s from store after identity change: %v", from, err) + } + ts := time.Unix(node.AttrGetter().Int64("t"), 0) + cli.dispatchEvent(&events.IdentityChange{JID: from, Timestamp: ts}) + } else { + cli.Log.Debugf("Got unknown encryption notification from server: %s", node.XMLString()) + } +} + +func (cli *Client) handleAppStateNotification(node *waBinary.Node) { + for _, collection := range node.GetChildrenByTag("collection") { + ag := collection.AttrGetter() + name := appstate.WAPatchName(ag.String("name")) + version := ag.Uint64("version") + cli.Log.Debugf("Got server sync notification that app state %s has updated to version %d", name, version) + err := cli.FetchAppState(name, false, false) + if errors.Is(err, ErrIQDisconnected) || errors.Is(err, ErrNotConnected) { + // There are some app state changes right before a remote logout, so stop syncing if we're disconnected. + cli.Log.Debugf("Failed to sync app state after notification: %v, not trying to sync other states", err) + return + } else if err != nil { + cli.Log.Errorf("Failed to sync app state after notification: %v", err) + } + } +} + +func (cli *Client) handlePictureNotification(node *waBinary.Node) { + ts := time.Unix(node.AttrGetter().Int64("t"), 0) + for _, child := range node.GetChildren() { + ag := child.AttrGetter() + var evt events.Picture + evt.Timestamp = ts + evt.JID = ag.JID("jid") + evt.Author = ag.OptionalJIDOrEmpty("author") + if child.Tag == "delete" { + evt.Remove = true + } else if child.Tag == "add" { + evt.PictureID = ag.String("id") + } else if child.Tag == "set" { + // TODO sometimes there's a hash and no ID? + evt.PictureID = ag.String("id") + } else { + continue + } + if !ag.OK() { + cli.Log.Debugf("Ignoring picture change notification with unexpected attributes: %v", ag.Error()) + continue + } + cli.dispatchEvent(&evt) + } +} + +func (cli *Client) handleDeviceNotification(node *waBinary.Node) { + cli.userDevicesCacheLock.Lock() + defer cli.userDevicesCacheLock.Unlock() + ag := node.AttrGetter() + from := ag.JID("from") + cached, ok := cli.userDevicesCache[from] + if !ok { + cli.Log.Debugf("No device list cached for %s, ignoring device list notification", from) + return + } + cachedParticipantHash := participantListHashV2(cached) + for _, child := range node.GetChildren() { + if child.Tag != "add" && child.Tag != "remove" { + cli.Log.Debugf("Unknown device list change tag %s", child.Tag) + continue + } + cag := child.AttrGetter() + deviceHash := cag.String("device_hash") + deviceChild, _ := child.GetOptionalChildByTag("device") + changedDeviceJID := deviceChild.AttrGetter().JID("jid") + switch child.Tag { + case "add": + cached = append(cached, changedDeviceJID) + case "remove": + for i, jid := range cached { + if jid == changedDeviceJID { + cached = append(cached[:i], cached[i+1:]...) + } + } + case "update": + // ??? + } + newParticipantHash := participantListHashV2(cached) + if newParticipantHash == deviceHash { + cli.Log.Debugf("%s's device list hash changed from %s to %s (%s). New hash matches", from, cachedParticipantHash, deviceHash, child.Tag) + cli.userDevicesCache[from] = cached + } else { + cli.Log.Warnf("%s's device list hash changed from %s to %s (%s). New hash doesn't match (%s)", from, cachedParticipantHash, deviceHash, child.Tag, newParticipantHash) + delete(cli.userDevicesCache, from) + } + } +} + +func (cli *Client) handleOwnDevicesNotification(node *waBinary.Node) { + cli.userDevicesCacheLock.Lock() + defer cli.userDevicesCacheLock.Unlock() + cached, ok := cli.userDevicesCache[cli.Store.ID.ToNonAD()] + if !ok { + cli.Log.Debugf("Ignoring own device change notification, device list not cached") + return + } + oldHash := participantListHashV2(cached) + expectedNewHash := node.AttrGetter().String("dhash") + var newDeviceList []types.JID + for _, child := range node.GetChildren() { + jid := child.AttrGetter().JID("jid") + if child.Tag == "device" && !jid.IsEmpty() { + jid.AD = true + newDeviceList = append(newDeviceList, jid) + } + } + newHash := participantListHashV2(newDeviceList) + if newHash != expectedNewHash { + cli.Log.Debugf("Received own device list change notification %s -> %s, but expected hash was %s", oldHash, newHash, expectedNewHash) + delete(cli.userDevicesCache, cli.Store.ID.ToNonAD()) + } else { + cli.Log.Debugf("Received own device list change notification %s -> %s", oldHash, newHash) + cli.userDevicesCache[cli.Store.ID.ToNonAD()] = newDeviceList + } +} + +func (cli *Client) handleAccountSyncNotification(node *waBinary.Node) { + for _, child := range node.GetChildren() { + switch child.Tag { + case "privacy": + cli.handlePrivacySettingsNotification(&child) + case "devices": + cli.handleOwnDevicesNotification(&child) + default: + cli.Log.Debugf("Unhandled account sync item %s", child.Tag) + } + } +} + +func (cli *Client) handleNotification(node *waBinary.Node) { + ag := node.AttrGetter() + notifType := ag.String("type") + if !ag.OK() { + return + } + go cli.sendAck(node) + switch notifType { + case "encrypt": + go cli.handleEncryptNotification(node) + case "server_sync": + go cli.handleAppStateNotification(node) + case "account_sync": + go cli.handleAccountSyncNotification(node) + case "devices": + go cli.handleDeviceNotification(node) + case "w:gp2": + evt, err := cli.parseGroupNotification(node) + if err != nil { + cli.Log.Errorf("Failed to parse group notification: %v", err) + } else { + go cli.dispatchEvent(evt) + } + case "picture": + go cli.handlePictureNotification(node) + default: + cli.Log.Debugf("Unhandled notification with type %s", notifType) + } +} |