summaryrefslogtreecommitdiff
path: root/teleirc/matterbridge/vendor/go.mau.fi/whatsmeow/appstate.go
diff options
context:
space:
mode:
authorMistivia <i@mistivia.com>2025-11-02 15:27:18 +0800
committerMistivia <i@mistivia.com>2025-11-02 15:27:18 +0800
commite9c24f4af7ed56760f6db7941827d09f6db9020b (patch)
tree62128c43b883ce5e3148113350978755779bb5de /teleirc/matterbridge/vendor/go.mau.fi/whatsmeow/appstate.go
parent58d5e7cfda4781d8a57ec52aefd02983835c301a (diff)
add matterbridge
Diffstat (limited to 'teleirc/matterbridge/vendor/go.mau.fi/whatsmeow/appstate.go')
-rw-r--r--teleirc/matterbridge/vendor/go.mau.fi/whatsmeow/appstate.go282
1 files changed, 282 insertions, 0 deletions
diff --git a/teleirc/matterbridge/vendor/go.mau.fi/whatsmeow/appstate.go b/teleirc/matterbridge/vendor/go.mau.fi/whatsmeow/appstate.go
new file mode 100644
index 0000000..7e99b51
--- /dev/null
+++ b/teleirc/matterbridge/vendor/go.mau.fi/whatsmeow/appstate.go
@@ -0,0 +1,282 @@
+// Copyright (c) 2022 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 (
+ "context"
+ "encoding/hex"
+ "errors"
+ "fmt"
+ "time"
+
+ "go.mau.fi/whatsmeow/appstate"
+ waBinary "go.mau.fi/whatsmeow/binary"
+ waProto "go.mau.fi/whatsmeow/binary/proto"
+ "go.mau.fi/whatsmeow/store"
+ "go.mau.fi/whatsmeow/types"
+ "go.mau.fi/whatsmeow/types/events"
+)
+
+// FetchAppState fetches updates to the given type of app state. If fullSync is true, the current
+// cached state will be removed and all app state patches will be re-fetched from the server.
+func (cli *Client) FetchAppState(name appstate.WAPatchName, fullSync, onlyIfNotSynced bool) error {
+ cli.appStateSyncLock.Lock()
+ defer cli.appStateSyncLock.Unlock()
+ if fullSync {
+ err := cli.Store.AppState.DeleteAppStateVersion(string(name))
+ if err != nil {
+ return fmt.Errorf("failed to reset app state %s version: %w", name, err)
+ }
+ }
+ version, hash, err := cli.Store.AppState.GetAppStateVersion(string(name))
+ if err != nil {
+ return fmt.Errorf("failed to get app state %s version: %w", name, err)
+ }
+ if version == 0 {
+ fullSync = true
+ } else if onlyIfNotSynced {
+ return nil
+ }
+
+ state := appstate.HashState{Version: version, Hash: hash}
+
+ hasMore := true
+ wantSnapshot := fullSync
+ for hasMore {
+ patches, err := cli.fetchAppStatePatches(name, state.Version, wantSnapshot)
+ wantSnapshot = false
+ if err != nil {
+ return fmt.Errorf("failed to fetch app state %s patches: %w", name, err)
+ }
+ hasMore = patches.HasMorePatches
+
+ mutations, newState, err := cli.appStateProc.DecodePatches(patches, state, true)
+ if err != nil {
+ if errors.Is(err, appstate.ErrKeyNotFound) {
+ go cli.requestMissingAppStateKeys(context.TODO(), patches)
+ }
+ return fmt.Errorf("failed to decode app state %s patches: %w", name, err)
+ }
+ wasFullSync := state.Version == 0 && patches.Snapshot != nil
+ state = newState
+ if name == appstate.WAPatchCriticalUnblockLow && wasFullSync && !cli.EmitAppStateEventsOnFullSync {
+ var contacts []store.ContactEntry
+ mutations, contacts = cli.filterContacts(mutations)
+ cli.Log.Debugf("Mass inserting app state snapshot with %d contacts into the store", len(contacts))
+ err = cli.Store.Contacts.PutAllContactNames(contacts)
+ if err != nil {
+ // This is a fairly serious failure, so just abort the whole thing
+ return fmt.Errorf("failed to update contact store with data from snapshot: %v", err)
+ }
+ }
+ for _, mutation := range mutations {
+ cli.dispatchAppState(mutation, !fullSync || cli.EmitAppStateEventsOnFullSync)
+ }
+ }
+ if fullSync {
+ cli.Log.Debugf("Full sync of app state %s completed. Current version: %d", name, state.Version)
+ cli.dispatchEvent(&events.AppStateSyncComplete{Name: name})
+ } else {
+ cli.Log.Debugf("Synced app state %s from version %d to %d", name, version, state.Version)
+ }
+ return nil
+}
+
+func (cli *Client) filterContacts(mutations []appstate.Mutation) ([]appstate.Mutation, []store.ContactEntry) {
+ filteredMutations := mutations[:0]
+ contacts := make([]store.ContactEntry, 0, len(mutations))
+ for _, mutation := range mutations {
+ if mutation.Index[0] == "contact" && len(mutation.Index) > 1 {
+ jid, _ := types.ParseJID(mutation.Index[1])
+ act := mutation.Action.GetContactAction()
+ contacts = append(contacts, store.ContactEntry{
+ JID: jid,
+ FirstName: act.GetFirstName(),
+ FullName: act.GetFullName(),
+ })
+ } else {
+ filteredMutations = append(filteredMutations, mutation)
+ }
+ }
+ return filteredMutations, contacts
+}
+
+func (cli *Client) dispatchAppState(mutation appstate.Mutation, dispatchEvts bool) {
+ if mutation.Operation != waProto.SyncdMutation_SET {
+ return
+ }
+
+ if dispatchEvts {
+ cli.dispatchEvent(&events.AppState{Index: mutation.Index, SyncActionValue: mutation.Action})
+ }
+
+ var jid types.JID
+ if len(mutation.Index) > 1 {
+ jid, _ = types.ParseJID(mutation.Index[1])
+ }
+ ts := time.Unix(mutation.Action.GetTimestamp(), 0)
+
+ var storeUpdateError error
+ var eventToDispatch interface{}
+ switch mutation.Index[0] {
+ case "mute":
+ act := mutation.Action.GetMuteAction()
+ eventToDispatch = &events.Mute{JID: jid, Timestamp: ts, Action: act}
+ var mutedUntil time.Time
+ if act.GetMuted() {
+ mutedUntil = time.Unix(act.GetMuteEndTimestamp(), 0)
+ }
+ if cli.Store.ChatSettings != nil {
+ storeUpdateError = cli.Store.ChatSettings.PutMutedUntil(jid, mutedUntil)
+ }
+ case "pin_v1":
+ act := mutation.Action.GetPinAction()
+ eventToDispatch = &events.Pin{JID: jid, Timestamp: ts, Action: act}
+ if cli.Store.ChatSettings != nil {
+ storeUpdateError = cli.Store.ChatSettings.PutPinned(jid, act.GetPinned())
+ }
+ case "archive":
+ act := mutation.Action.GetArchiveChatAction()
+ eventToDispatch = &events.Archive{JID: jid, Timestamp: ts, Action: act}
+ if cli.Store.ChatSettings != nil {
+ storeUpdateError = cli.Store.ChatSettings.PutArchived(jid, act.GetArchived())
+ }
+ case "contact":
+ act := mutation.Action.GetContactAction()
+ eventToDispatch = &events.Contact{JID: jid, Timestamp: ts, Action: act}
+ if cli.Store.Contacts != nil {
+ storeUpdateError = cli.Store.Contacts.PutContactName(jid, act.GetFirstName(), act.GetFullName())
+ }
+ case "deleteChat":
+ act := mutation.Action.GetDeleteChatAction()
+ eventToDispatch = &events.DeleteChat{JID: jid, Timestamp: ts, Action: act}
+ case "star":
+ if len(mutation.Index) < 5 {
+ return
+ }
+ evt := events.Star{
+ ChatJID: jid,
+ MessageID: mutation.Index[2],
+ Timestamp: ts,
+ Action: mutation.Action.GetStarAction(),
+ IsFromMe: mutation.Index[3] == "1",
+ }
+ if mutation.Index[4] != "0" {
+ evt.SenderJID, _ = types.ParseJID(mutation.Index[4])
+ }
+ eventToDispatch = &evt
+ case "deleteMessageForMe":
+ if len(mutation.Index) < 5 {
+ return
+ }
+ evt := events.DeleteForMe{
+ ChatJID: jid,
+ MessageID: mutation.Index[2],
+ Timestamp: ts,
+ Action: mutation.Action.GetDeleteMessageForMeAction(),
+ IsFromMe: mutation.Index[3] == "1",
+ }
+ if mutation.Index[4] != "0" {
+ evt.SenderJID, _ = types.ParseJID(mutation.Index[4])
+ }
+ eventToDispatch = &evt
+ case "markChatAsRead":
+ eventToDispatch = &events.MarkChatAsRead{
+ JID: jid,
+ Timestamp: ts,
+ Action: mutation.Action.GetMarkChatAsReadAction(),
+ }
+ case "setting_pushName":
+ eventToDispatch = &events.PushNameSetting{Timestamp: ts, Action: mutation.Action.GetPushNameSetting()}
+ cli.Store.PushName = mutation.Action.GetPushNameSetting().GetName()
+ err := cli.Store.Save()
+ if err != nil {
+ cli.Log.Errorf("Failed to save device store after updating push name: %v", err)
+ }
+ case "setting_unarchiveChats":
+ eventToDispatch = &events.UnarchiveChatsSetting{Timestamp: ts, Action: mutation.Action.GetUnarchiveChatsSetting()}
+ }
+ if storeUpdateError != nil {
+ cli.Log.Errorf("Failed to update device store after app state mutation: %v", storeUpdateError)
+ }
+ if dispatchEvts && eventToDispatch != nil {
+ cli.dispatchEvent(eventToDispatch)
+ }
+}
+
+func (cli *Client) downloadExternalAppStateBlob(ref *waProto.ExternalBlobReference) ([]byte, error) {
+ return cli.Download(ref)
+}
+
+func (cli *Client) fetchAppStatePatches(name appstate.WAPatchName, fromVersion uint64, snapshot bool) (*appstate.PatchList, error) {
+ attrs := waBinary.Attrs{
+ "name": string(name),
+ "return_snapshot": snapshot,
+ }
+ if !snapshot {
+ attrs["version"] = fromVersion
+ }
+ resp, err := cli.sendIQ(infoQuery{
+ Namespace: "w:sync:app:state",
+ Type: "set",
+ To: types.ServerJID,
+ Content: []waBinary.Node{{
+ Tag: "sync",
+ Content: []waBinary.Node{{
+ Tag: "collection",
+ Attrs: attrs,
+ }},
+ }},
+ })
+ if err != nil {
+ return nil, err
+ }
+ return appstate.ParsePatchList(resp, cli.downloadExternalAppStateBlob)
+}
+
+func (cli *Client) requestMissingAppStateKeys(ctx context.Context, patches *appstate.PatchList) {
+ cli.appStateKeyRequestsLock.Lock()
+ rawKeyIDs := cli.appStateProc.GetMissingKeyIDs(patches)
+ filteredKeyIDs := make([][]byte, 0, len(rawKeyIDs))
+ now := time.Now()
+ for _, keyID := range rawKeyIDs {
+ stringKeyID := hex.EncodeToString(keyID)
+ lastRequestTime := cli.appStateKeyRequests[stringKeyID]
+ if lastRequestTime.IsZero() || lastRequestTime.Add(24*time.Hour).Before(now) {
+ cli.appStateKeyRequests[stringKeyID] = now
+ filteredKeyIDs = append(filteredKeyIDs, keyID)
+ }
+ }
+ cli.appStateKeyRequestsLock.Unlock()
+ cli.requestAppStateKeys(ctx, filteredKeyIDs)
+}
+
+func (cli *Client) requestAppStateKeys(ctx context.Context, rawKeyIDs [][]byte) {
+ keyIDs := make([]*waProto.AppStateSyncKeyId, len(rawKeyIDs))
+ debugKeyIDs := make([]string, len(rawKeyIDs))
+ for i, keyID := range rawKeyIDs {
+ keyIDs[i] = &waProto.AppStateSyncKeyId{KeyId: keyID}
+ debugKeyIDs[i] = hex.EncodeToString(keyID)
+ }
+ msg := &waProto.Message{
+ ProtocolMessage: &waProto.ProtocolMessage{
+ Type: waProto.ProtocolMessage_APP_STATE_SYNC_KEY_REQUEST.Enum(),
+ AppStateSyncKeyRequest: &waProto.AppStateSyncKeyRequest{
+ KeyIds: keyIDs,
+ },
+ },
+ }
+ ownID := cli.getOwnID().ToNonAD()
+ if ownID.IsEmpty() {
+ return
+ }
+ cli.Log.Infof("Sending key request for app state keys %+v", debugKeyIDs)
+ _, err := cli.SendMessage(ctx, ownID, msg, SendRequestExtra{Peer: true})
+ if err != nil {
+ cli.Log.Warnf("Failed to send app state key request: %v", err)
+ }
+}