diff options
Diffstat (limited to 'teleirc/matterbridge/vendor/go.mau.fi/whatsmeow/notification.go')
| -rw-r--r-- | teleirc/matterbridge/vendor/go.mau.fi/whatsmeow/notification.go | 261 |
1 files changed, 261 insertions, 0 deletions
diff --git a/teleirc/matterbridge/vendor/go.mau.fi/whatsmeow/notification.go b/teleirc/matterbridge/vendor/go.mau.fi/whatsmeow/notification.go new file mode 100644 index 0000000..567722b --- /dev/null +++ b/teleirc/matterbridge/vendor/go.mau.fi/whatsmeow/notification.go @@ -0,0 +1,261 @@ +// 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" + + "go.mau.fi/whatsmeow/appstate" + waBinary "go.mau.fi/whatsmeow/binary" + "go.mau.fi/whatsmeow/store" + "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 := node.AttrGetter().UnixTime("t") + 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 := node.AttrGetter().UnixTime("t") + 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() + ownID := cli.getOwnID().ToNonAD() + if ownID.IsEmpty() { + cli.Log.Debugf("Ignoring own device change notification, session was deleted") + return + } + cached, ok := cli.userDevicesCache[ownID] + 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, ownID) + } else { + cli.Log.Debugf("Received own device list change notification %s -> %s", oldHash, newHash) + cli.userDevicesCache[ownID] = 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) handlePrivacyTokenNotification(node *waBinary.Node) { + ownID := cli.getOwnID().ToNonAD() + if ownID.IsEmpty() { + cli.Log.Debugf("Ignoring privacy token notification, session was deleted") + return + } + tokens := node.GetChildByTag("tokens") + if tokens.Tag != "tokens" { + cli.Log.Warnf("privacy_token notification didn't contain <tokens> tag") + return + } + parentAG := node.AttrGetter() + sender := parentAG.JID("from") + if !parentAG.OK() { + cli.Log.Warnf("privacy_token notification didn't have a sender (%v)", parentAG.Error()) + return + } + for _, child := range tokens.GetChildren() { + ag := child.AttrGetter() + if child.Tag != "token" { + cli.Log.Warnf("privacy_token notification contained unexpected <%s> tag", child.Tag) + } else if targetUser := ag.JID("jid"); targetUser != ownID { + cli.Log.Warnf("privacy_token notification contained token for different user %s", targetUser) + } else if tokenType := ag.String("type"); tokenType != "trusted_contact" { + cli.Log.Warnf("privacy_token notification contained unexpected token type %s", tokenType) + } else if token, ok := child.Content.([]byte); !ok { + cli.Log.Warnf("privacy_token notification contained non-binary token") + } else { + timestamp := ag.UnixTime("t") + if !ag.OK() { + cli.Log.Warnf("privacy_token notification is missing some fields: %v", ag.Error()) + } + err := cli.Store.PrivacyTokens.PutPrivacyTokens(store.PrivacyToken{ + User: sender, + Token: token, + Timestamp: timestamp, + }) + if err != nil { + cli.Log.Errorf("Failed to save privacy token from %s: %v", sender, err) + } else { + cli.Log.Debugf("Stored privacy token from %s (ts: %v)", sender, timestamp) + } + } + } +} + +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) + case "mediaretry": + go cli.handleMediaRetryNotification(node) + case "privacy_token": + go cli.handlePrivacyTokenNotification(node) + // Other types: business, disappearing_mode, server, status, pay, psa + default: + cli.Log.Debugf("Unhandled notification with type %s", notifType) + } +} |
