diff options
| author | Mistivia <i@mistivia.com> | 2025-11-02 15:27:18 +0800 |
|---|---|---|
| committer | Mistivia <i@mistivia.com> | 2025-11-02 15:27:18 +0800 |
| commit | e9c24f4af7ed56760f6db7941827d09f6db9020b (patch) | |
| tree | 62128c43b883ce5e3148113350978755779bb5de /teleirc/matterbridge/vendor/go.mau.fi/whatsmeow/appstate.go | |
| parent | 58d5e7cfda4781d8a57ec52aefd02983835c301a (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.go | 282 |
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) + } +} |
