mirror of
https://github.com/42wim/matterbridge.git
synced 2024-11-27 05:02:02 -08:00
262 lines
8.8 KiB
Go
262 lines
8.8 KiB
Go
// 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)
|
|
}
|
|
}
|