kage-baileys 3.0.6 → 3.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. package/Example/connection.cjs +4 -0
  2. package/README.MD +4726 -48
  3. package/README.md +5941 -0
  4. package/WAProto/WAProto.proto +3395 -415
  5. package/WAProto/compat.cjs +12 -0
  6. package/WAProto/index.d.ts +47104 -19888
  7. package/WAProto/index.js +131979 -57361
  8. package/lib/Addon/http-error.js +43 -0
  9. package/lib/Addon/index.js +32 -0
  10. package/lib/Addon/keyed-db.js +250 -0
  11. package/lib/Addon/link-preview.js +82 -0
  12. package/lib/Defaults/baileys-version.json +3 -0
  13. package/lib/Defaults/connection.js +110 -0
  14. package/lib/Defaults/constants.js +156 -0
  15. package/lib/Defaults/history.js +46 -0
  16. package/lib/Defaults/index.js +230 -0
  17. package/lib/Defaults/media.js +104 -0
  18. package/lib/Defaults/phonenumber-mcc.json +223 -0
  19. package/lib/Defaults/prefix.js +44 -0
  20. package/lib/Extensions/business-advanced.js +382 -0
  21. package/lib/Extensions/business.js +159 -0
  22. package/lib/Extensions/chats-advanced.js +1106 -0
  23. package/lib/Extensions/chats.js +349 -0
  24. package/lib/Extensions/communities-advanced.js +430 -0
  25. package/lib/Extensions/community.js +94 -0
  26. package/lib/Extensions/groups-advanced.js +343 -0
  27. package/lib/Extensions/index.js +30 -0
  28. package/lib/Extensions/messages-recv-advanced.js +1690 -0
  29. package/lib/Extensions/messages-send-advanced.js +622 -0
  30. package/lib/Extensions/messages-send.js +1380 -0
  31. package/lib/Extensions/newsletter-advanced.js +228 -0
  32. package/lib/Extensions/newsletter.js +72 -0
  33. package/lib/Extensions/socket.js +389 -0
  34. package/lib/Modded/message_builder.js +2084 -4
  35. package/lib/Signal/Group/ciphertext-message.js +54 -0
  36. package/lib/Signal/Group/group-session-builder.js +124 -0
  37. package/lib/Signal/Group/group_cipher.js +216 -0
  38. package/lib/Signal/Group/index.js +124 -0
  39. package/lib/Signal/Group/keyhelper.js +108 -0
  40. package/lib/Signal/Group/sender-chain-key.js +83 -0
  41. package/lib/Signal/Group/sender-key-distribution-message.js +157 -0
  42. package/lib/Signal/Group/sender-key-message.js +166 -0
  43. package/lib/Signal/Group/sender-key-name.js +130 -0
  44. package/lib/Signal/Group/sender-key-record.js +113 -0
  45. package/lib/Signal/Group/sender-key-state.js +208 -0
  46. package/lib/Signal/Group/sender-message-key.js +82 -0
  47. package/lib/Signal/libsignal.js +935 -0
  48. package/lib/Signal/lid-mapping.js +545 -1
  49. package/lib/Socket/Client/index.js +60 -0
  50. package/lib/Socket/Client/types.js +51 -0
  51. package/lib/Socket/Client/websocket.js +152 -0
  52. package/lib/Socket/business.js +831 -0
  53. package/lib/Socket/chats.js +2535 -49
  54. package/lib/Socket/communities.js +450 -1
  55. package/lib/Socket/community.js +1022 -0
  56. package/lib/Socket/dugong.js +1 -1
  57. package/lib/Socket/groups.js +747 -0
  58. package/lib/Socket/index.js +60 -2
  59. package/lib/Socket/messages-recv.js +3450 -5
  60. package/lib/Socket/messages-send.js +3123 -0
  61. package/lib/Socket/mex.js +126 -1
  62. package/lib/Socket/newsletter.js +465 -0
  63. package/lib/Socket/socket.js +2217 -1
  64. package/lib/Store/index.js +79 -0
  65. package/lib/Store/make-cache-manager-store.js +186 -0
  66. package/lib/Store/make-in-memory-store.js +878 -0
  67. package/lib/Store/make-ordered-dictionary.js +187 -0
  68. package/lib/Store/object-repository.js +78 -0
  69. package/lib/Types/Auth.js +25 -0
  70. package/lib/Types/Bussines.js +25 -1
  71. package/lib/Types/Call.js +25 -0
  72. package/lib/Types/Chat.js +39 -0
  73. package/lib/Types/Contact.js +25 -0
  74. package/lib/Types/Events.js +25 -0
  75. package/lib/Types/GroupMetadata.js +25 -0
  76. package/lib/Types/Label.js +76 -0
  77. package/lib/Types/LabelAssociation.js +40 -0
  78. package/lib/Types/Message.js +57 -0
  79. package/lib/Types/Mex.js +56 -1
  80. package/lib/Types/MexUpdates.js +46 -0
  81. package/lib/Types/Newsletter.js +56 -0
  82. package/lib/Types/Product.js +25 -0
  83. package/lib/Types/RichType.js +23 -0
  84. package/lib/Types/Signal.js +25 -0
  85. package/lib/Types/Socket.js +25 -0
  86. package/lib/Types/State.js +92 -0
  87. package/lib/Types/USync.js +25 -0
  88. package/lib/Types/index.js +133 -0
  89. package/lib/Utils/auth-utils.js +703 -0
  90. package/lib/Utils/automation-tools.js +51 -16
  91. package/lib/Utils/baileys-event-stream.js +146 -0
  92. package/lib/Utils/browser-utils.js +95 -0
  93. package/lib/Utils/business.js +525 -0
  94. package/lib/Utils/chat-utils.js +1861 -0
  95. package/lib/Utils/companion-reg-client-utils.js +55 -1
  96. package/lib/Utils/crypto.js +355 -0
  97. package/lib/Utils/decode-wa-message.js +709 -0
  98. package/lib/Utils/event-buffer.js +1323 -8
  99. package/lib/Utils/generics.js +945 -1
  100. package/lib/Utils/history.js +266 -0
  101. package/lib/Utils/identity-change-handler.js +62 -1
  102. package/lib/Utils/index.js +114 -0
  103. package/lib/Utils/link-preview.js +179 -0
  104. package/lib/Utils/logger.js +42 -0
  105. package/lib/Utils/lt-hash.js +73 -0
  106. package/lib/Utils/make-mutex.js +104 -0
  107. package/lib/Utils/message-inspector.js +110 -0
  108. package/lib/Utils/message-retry-manager.js +507 -2
  109. package/lib/Utils/messages-media.js +1931 -1
  110. package/lib/Utils/messages.js +3748 -0
  111. package/lib/Utils/noise-handler.js +423 -0
  112. package/lib/Utils/offline-node-processor.js +53 -1
  113. package/lib/Utils/pre-key-manager.js +255 -1
  114. package/lib/Utils/process-message.js +1592 -0
  115. package/lib/Utils/reporting-utils.js +272 -1
  116. package/lib/Utils/rich-message-utils.js +377 -0
  117. package/lib/Utils/rich-messages.js +1 -1
  118. package/lib/Utils/signal.js +437 -0
  119. package/lib/Utils/stanza-ack.js +51 -1
  120. package/lib/Utils/status-refresh.js +37 -0
  121. package/lib/Utils/status-tools.js +4 -2
  122. package/lib/Utils/sync-action-utils.js +63 -1
  123. package/lib/Utils/tc-token-utils.js +185 -1
  124. package/lib/Utils/terminal-banner.js +31 -0
  125. package/lib/Utils/use-multi-file-auth-state.js +271 -0
  126. package/lib/Utils/use-single-file-auth-state.js +108 -0
  127. package/lib/Utils/use-sqlite-auth-state.js +142 -0
  128. package/lib/Utils/validate-connection.js +497 -1
  129. package/lib/Utils/view-once-compat.js +13 -5
  130. package/lib/VoIP/audio-feeder.js +150 -0
  131. package/lib/VoIP/index.js +289 -0
  132. package/lib/VoIP/relay-transport.js +608 -0
  133. package/lib/VoIP/signaling.js +583 -0
  134. package/lib/VoIP/types.js +30 -0
  135. package/lib/VoIP/wasm-engine.js +1261 -2
  136. package/lib/VoIP/worker-bootstrap.js +1090 -2
  137. package/lib/WABinary/constants.js +2805 -0
  138. package/lib/WABinary/decode.js +649 -2
  139. package/lib/WABinary/encode.js +564 -2
  140. package/lib/WABinary/generic-utils.js +401 -0
  141. package/lib/WABinary/index.js +82 -0
  142. package/lib/WABinary/jid-utils.js +290 -0
  143. package/lib/WABinary/types.js +25 -0
  144. package/lib/WAM/BinaryInfo.js +50 -0
  145. package/lib/WAM/constants.js +45747 -0
  146. package/lib/WAM/encode.js +393 -0
  147. package/lib/WAM/index.js +78 -0
  148. package/lib/WAUSync/Protocols/USyncBotProfileProtocol.js +135 -0
  149. package/lib/WAUSync/Protocols/USyncContactProtocol.js +110 -0
  150. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +142 -0
  151. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +85 -0
  152. package/lib/WAUSync/Protocols/USyncLIDProtocol.js +69 -0
  153. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +107 -0
  154. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +38 -1
  155. package/lib/WAUSync/Protocols/index.js +83 -0
  156. package/lib/WAUSync/USyncQuery.js +241 -2
  157. package/lib/WAUSync/USyncUser.js +86 -0
  158. package/lib/WAUSync/index.js +78 -0
  159. package/lib/assets/wasm/loader.js +1875 -1
  160. package/lib/assets/wasm/worker-modules.js +11047 -1
  161. package/lib/index.d.ts +126 -0
  162. package/lib/index.js +53 -21
  163. package/lib/index.mjs +437 -0
  164. package/licenses/upstream-MIT.txt +22 -0
  165. package/maintenance/api-coverage.json +1642 -0
  166. package/maintenance/archivos-sha256.json +207 -0
  167. package/maintenance/consumer-validation.log +3 -0
  168. package/maintenance/dependency-audit.json +4 -4
  169. package/maintenance/extension-inventory.json +57 -0
  170. package/maintenance/protocol-update.json +1101 -0
  171. package/maintenance/protocol-wire-audit.json +4 -0
  172. package/maintenance/reference-socket-surface.json +209 -0
  173. package/maintenance/resumen-validacion.json +50 -0
  174. package/maintenance/validation.log +2725 -0
  175. package/maintenance/verification.json +566 -0
  176. package/package.json +107 -9
  177. package/scripts/check-imports.cjs +10 -0
  178. package/scripts/check-package.cjs +40 -0
  179. package/scripts/check-syntax.cjs +1 -1
  180. package/scripts/generate-protobuf.cjs +6 -1
  181. package/scripts/generate-public-types.cjs +9 -0
  182. package/scripts/kage-base64-fix.cjs +45 -0
  183. package/scripts/maintenance.cjs +10 -0
  184. package/test/advanced.test.cjs +24 -0
  185. package/test/base64-fix.test.cjs +49 -0
  186. package/test/maintenance.test.cjs +1 -1
  187. package/test/npm-publication.test.cjs +141 -0
  188. package/test/recepcion-reparada.test.cjs +98 -0
  189. package/test/resilience-binary.test.cjs +69 -0
  190. package/test/resilience-transport.test.cjs +34 -0
  191. package/test/types.test.ts +5 -0
  192. package/test/upgrade.test.cjs +104 -0
  193. package/tsconfig.json +20 -0
  194. package/WAProto/p.html +0 -1
  195. package/lib/KageExtra/Modded/message_builder.js +0 -2330
  196. package/lib/KageExtra/VoIP/audio-feeder.js +0 -136
  197. package/lib/KageExtra/VoIP/index.js +0 -282
  198. package/lib/KageExtra/VoIP/relay-transport.js +0 -559
  199. package/lib/KageExtra/VoIP/signaling.js +0 -603
  200. package/lib/KageExtra/VoIP/types.js +0 -17
  201. package/lib/KageExtra/VoIP/wasm-engine.js +0 -1214
  202. package/lib/KageExtra/VoIP/worker-bootstrap.js +0 -1042
  203. package/lib/KageExtra/assets/wasm/loader.js +0 -5
  204. package/lib/KageExtra/assets/wasm/whatsapp.wasm +0 -0
  205. package/lib/KageExtra/assets/wasm/worker-modules.js +0 -273
  206. package/lib/KageExtra/index.js +0 -3
  207. package/lib/KageExtra/package.json +0 -1
  208. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +0 -53
  209. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +0 -24
  210. package/maintenance/revision-full-check.txt +0 -1097
  211. package/maintenance/revision-library-tests.tap +0 -1088
  212. package/maintenance/revision-protobuf.txt +0 -5
  213. package/maintenance/revision-syntax.txt +0 -5
  214. package/maintenance/schema-coverage.json +0 -23
  215. package/maintenance/schema-merge.json +0 -30
  216. package/maintenance/validation-node20.txt +0 -191
  217. package/maintenance/validation-node22.txt +0 -237
@@ -728,3 +728,1864 @@ const processSyncAction = (syncAction, ev, me, initialSyncOpts, logger) => {
728
728
  }
729
729
  };
730
730
  exports.processSyncAction = processSyncAction;
731
+
732
+ // Kage: compatibilidad de API.
733
+ const __kageModule61 = (() => {
734
+ const module = { exports: {} }; const exports = module.exports;
735
+ "use strict"
736
+
737
+ Object.defineProperty(exports, "__esModule", { value: true })
738
+
739
+ const { Boom } = require("@hapi/boom")
740
+ const { proto } = require("../../WAProto")
741
+ const { LabelAssociationType } = require("../Types/LabelAssociation")
742
+ const {
743
+ getBinaryNodeChild,
744
+ getBinaryNodeChildren,
745
+ isJidGroup,
746
+ jidNormalizedUser
747
+ } = require("../WABinary")
748
+ const {
749
+ hkdf,
750
+ hmacSign,
751
+ aesEncrypt,
752
+ aesDecrypt
753
+ } = require("./crypto")
754
+ const { toNumber } = require("./generics")
755
+ const { LT_HASH_ANTI_TAMPERING } = require("./lt-hash")
756
+ const { downloadContentFromMessage } = require("./messages-media")
757
+
758
+ const mutationKeys = async (keydata) => {
759
+ const expanded = await hkdf(keydata, 160, { info: 'WhatsApp Mutation Keys' })
760
+ return {
761
+ indexKey: expanded.slice(0, 32),
762
+ valueEncryptionKey: expanded.slice(32, 64),
763
+ valueMacKey: expanded.slice(64, 96),
764
+ snapshotMacKey: expanded.slice(96, 128),
765
+ patchMacKey: expanded.slice(128, 160)
766
+ }
767
+ }
768
+
769
+ const generateMac = (operation, data, keyId, key) => {
770
+ const getKeyData = () => {
771
+ let r
772
+ switch (operation) {
773
+ case proto.SyncdMutation.SyncdOperation.SET:
774
+ r = 0x01
775
+ break
776
+ case proto.SyncdMutation.SyncdOperation.REMOVE:
777
+ r = 0x02
778
+ break
779
+ }
780
+ const buff = Buffer.from([r])
781
+ return Buffer.concat([buff, Buffer.from(keyId, 'base64')])
782
+ }
783
+ const keyData = getKeyData()
784
+ const last = Buffer.alloc(8) // 8 bytes
785
+ last.set([keyData.length], last.length - 1)
786
+ const total = Buffer.concat([keyData, data, last])
787
+ const hmac = hmacSign(total, key, 'sha512')
788
+ return hmac.slice(0, 32)
789
+ }
790
+
791
+ const to64BitNetworkOrder = (e) => {
792
+ const buff = Buffer.alloc(8)
793
+ buff.writeUint32BE(e, 4)
794
+ return buff
795
+ }
796
+
797
+ const makeLtHashGenerator = ({ indexValueMap, hash }) => {
798
+ indexValueMap = { ...indexValueMap }
799
+ const addBuffs = []
800
+ const subBuffs = []
801
+ return {
802
+ mix: ({ indexMac, valueMac, operation }) => {
803
+ const indexMacBase64 = Buffer.from(indexMac).toString('base64')
804
+ const prevOp = indexValueMap[indexMacBase64]
805
+ if (operation === proto.SyncdMutation.SyncdOperation.REMOVE) {
806
+ if (!prevOp) {
807
+ throw new Boom('tried remove, but no previous op', { data: { indexMac, valueMac } })
808
+ }
809
+ // remove from index value mac, since this mutation is erased
810
+ delete indexValueMap[indexMacBase64]
811
+ }
812
+ else {
813
+ addBuffs.push(new Uint8Array(valueMac).buffer)
814
+ // add this index into the history map
815
+ indexValueMap[indexMacBase64] = { valueMac }
816
+ }
817
+ if (prevOp) {
818
+ subBuffs.push(new Uint8Array(prevOp.valueMac).buffer)
819
+ }
820
+ },
821
+ finish: async () => {
822
+ const hashArrayBuffer = new Uint8Array(hash).buffer
823
+ const result = await LT_HASH_ANTI_TAMPERING.subtractThenAdd(hashArrayBuffer, addBuffs, subBuffs)
824
+ const buffer = Buffer.from(result)
825
+ return {
826
+ hash: buffer,
827
+ indexValueMap
828
+ }
829
+ }
830
+ }
831
+ }
832
+
833
+ const generateSnapshotMac = (lthash, version, name, key) => {
834
+ const total = Buffer.concat([
835
+ lthash,
836
+ to64BitNetworkOrder(version),
837
+ Buffer.from(name, 'utf-8')
838
+ ])
839
+ return hmacSign(total, key, 'sha256')
840
+ }
841
+
842
+ const generatePatchMac = (snapshotMac, valueMacs, version, type, key) => {
843
+ const total = Buffer.concat([
844
+ snapshotMac,
845
+ ...valueMacs,
846
+ to64BitNetworkOrder(version),
847
+ Buffer.from(type, 'utf-8')
848
+ ])
849
+ return hmacSign(total, key)
850
+ }
851
+
852
+ const newLTHashState = () => ({ version: 0, hash: Buffer.alloc(128), indexValueMap: {} })
853
+
854
+ const encodeSyncdPatch = async ({ type, index, syncAction, apiVersion, operation }, myAppStateKeyId, state, getAppStateSyncKey) => {
855
+ const key = !!myAppStateKeyId ? await getAppStateSyncKey(myAppStateKeyId) : undefined
856
+ if (!key) {
857
+ throw new Boom(`myAppStateKey ("${myAppStateKeyId}") not present`, { statusCode: 404 })
858
+ }
859
+ const encKeyId = Buffer.from(myAppStateKeyId, 'base64')
860
+ state = { ...state, indexValueMap: { ...state.indexValueMap } }
861
+ const indexBuffer = Buffer.from(JSON.stringify(index))
862
+ const dataProto = proto.SyncActionData.fromObject({
863
+ index: indexBuffer,
864
+ value: syncAction,
865
+ padding: new Uint8Array(0),
866
+ version: apiVersion
867
+ })
868
+ const encoded = proto.SyncActionData.encode(dataProto).finish()
869
+ const keyValue = await mutationKeys(key.keyData)
870
+ const encValue = aesEncrypt(encoded, keyValue.valueEncryptionKey)
871
+ const valueMac = generateMac(operation, encValue, encKeyId, keyValue.valueMacKey)
872
+ const indexMac = hmacSign(indexBuffer, keyValue.indexKey)
873
+ // update LT hash
874
+ const generator = makeLtHashGenerator(state)
875
+ generator.mix({ indexMac, valueMac, operation })
876
+ Object.assign(state, await generator.finish())
877
+ state.version += 1
878
+ const snapshotMac = generateSnapshotMac(state.hash, state.version, type, keyValue.snapshotMacKey)
879
+ const patch = {
880
+ patchMac: generatePatchMac(snapshotMac, [valueMac], state.version, type, keyValue.patchMacKey),
881
+ snapshotMac: snapshotMac,
882
+ keyId: { id: encKeyId },
883
+ mutations: [
884
+ {
885
+ operation: operation,
886
+ record: {
887
+ index: {
888
+ blob: indexMac
889
+ },
890
+ value: {
891
+ blob: Buffer.concat([encValue, valueMac])
892
+ },
893
+ keyId: { id: encKeyId }
894
+ }
895
+ }
896
+ ]
897
+ }
898
+ const base64Index = indexMac.toString('base64')
899
+ state.indexValueMap[base64Index] = { valueMac }
900
+ return { patch, state }
901
+ }
902
+
903
+ const decodeSyncdMutations = async (msgMutations, initialState, getAppStateSyncKey, onMutation, validateMacs) => {
904
+ const ltGenerator = makeLtHashGenerator(initialState)
905
+ // indexKey used to HMAC sign record.index.blob
906
+ // valueEncryptionKey used to AES-256-CBC encrypt record.value.blob[0:-32]
907
+ // the remaining record.value.blob[0:-32] is the mac, it the HMAC sign of key.keyId + decoded proto data + length of bytes in keyId
908
+ for (const msgMutation of msgMutations) {
909
+ // if it's a syncdmutation, get the operation property
910
+ // otherwise, if it's only a record -- it'll be a SET mutation
911
+ const operation = 'operation' in msgMutation ? msgMutation.operation : proto.SyncdMutation.SyncdOperation.SET
912
+ const record = 'record' in msgMutation && !!msgMutation.record ? msgMutation.record : msgMutation
913
+ const key = await getKey(record.keyId.id)
914
+ const content = Buffer.from(record.value.blob)
915
+ const encContent = content.slice(0, -32)
916
+ const ogValueMac = content.slice(-32)
917
+ if (validateMacs) {
918
+ const contentHmac = generateMac(operation, encContent, record.keyId.id, key.valueMacKey)
919
+ if (Buffer.compare(contentHmac, ogValueMac) !== 0) {
920
+ throw new Boom('HMAC content verification failed')
921
+ }
922
+ }
923
+ const result = aesDecrypt(encContent, key.valueEncryptionKey)
924
+ const syncAction = proto.SyncActionData.decode(result)
925
+ if (validateMacs) {
926
+ const hmac = hmacSign(syncAction.index, key.indexKey)
927
+ if (Buffer.compare(hmac, record.index.blob) !== 0) {
928
+ throw new Boom('HMAC index verification failed')
929
+ }
930
+ }
931
+ const indexStr = Buffer.from(syncAction.index).toString()
932
+ onMutation({ syncAction, index: JSON.parse(indexStr) })
933
+ ltGenerator.mix({
934
+ indexMac: record.index.blob,
935
+ valueMac: ogValueMac,
936
+ operation: operation
937
+ })
938
+ }
939
+ return await ltGenerator.finish()
940
+ async function getKey(keyId) {
941
+ const base64Key = Buffer.from(keyId).toString('base64')
942
+ const keyEnc = await getAppStateSyncKey(base64Key)
943
+ if (!keyEnc) {
944
+ throw new Boom(`failed to find key "${base64Key}" to decode mutation`, { statusCode: 404, data: { msgMutations } })
945
+ }
946
+ return mutationKeys(keyEnc.keyData)
947
+ }
948
+ }
949
+
950
+ const decodeSyncdPatch = async (msg, name, initialState, getAppStateSyncKey, onMutation, validateMacs) => {
951
+ if (validateMacs) {
952
+ const base64Key = Buffer.from(msg.keyId.id).toString('base64')
953
+ const mainKeyObj = await getAppStateSyncKey(base64Key)
954
+ if (!mainKeyObj) {
955
+ throw new Boom(`failed to find key "${base64Key}" to decode patch`, { statusCode: 404, data: { msg } })
956
+ }
957
+ const mainKey = await mutationKeys(mainKeyObj.keyData)
958
+ const mutationmacs = msg.mutations.map(mutation => mutation.record.value.blob.slice(-32))
959
+ const patchMac = generatePatchMac(msg.snapshotMac, mutationmacs, toNumber(msg.version.version), name, mainKey.patchMacKey)
960
+ if (Buffer.compare(patchMac, msg.patchMac) !== 0) {
961
+ throw new Boom('Invalid patch mac')
962
+ }
963
+ }
964
+ const result = await decodeSyncdMutations(msg.mutations, initialState, getAppStateSyncKey, onMutation, validateMacs)
965
+ return result
966
+ }
967
+
968
+ const extractSyncdPatches = async (result, options) => {
969
+ const syncNode = getBinaryNodeChild(result, 'sync')
970
+ const collectionNodes = getBinaryNodeChildren(syncNode, 'collection')
971
+ const final = {}
972
+ await Promise.all(collectionNodes.map(async (collectionNode) => {
973
+ const patchesNode = getBinaryNodeChild(collectionNode, 'patches')
974
+ const patches = getBinaryNodeChildren(patchesNode || collectionNode, 'patch')
975
+ const snapshotNode = getBinaryNodeChild(collectionNode, 'snapshot')
976
+ const syncds = []
977
+ const name = collectionNode.attrs.name
978
+ const hasMorePatches = collectionNode.attrs.has_more_patches === 'true'
979
+ let snapshot = undefined
980
+ if (snapshotNode && !!snapshotNode.content) {
981
+ if (!Buffer.isBuffer(snapshotNode)) {
982
+ snapshotNode.content = Buffer.from(Object.values(snapshotNode.content))
983
+ }
984
+ const blobRef = proto.ExternalBlobReference.decode(snapshotNode.content)
985
+ const data = await downloadExternalBlob(blobRef, options)
986
+ snapshot = proto.SyncdSnapshot.decode(data)
987
+ }
988
+ for (let { content } of patches) {
989
+ if (content) {
990
+ if (!Buffer.isBuffer(content)) {
991
+ content = Buffer.from(Object.values(content))
992
+ }
993
+ const syncd = proto.SyncdPatch.decode(content)
994
+ if (!syncd.version) {
995
+ syncd.version = { version: +collectionNode.attrs.version + 1 }
996
+ }
997
+ syncds.push(syncd)
998
+ }
999
+ }
1000
+ final[name] = { patches: syncds, hasMorePatches, snapshot }
1001
+ }))
1002
+ return final
1003
+ }
1004
+
1005
+ const downloadExternalBlob = async (blob, options) => {
1006
+ const stream = await downloadContentFromMessage(blob, 'md-app-state', { options })
1007
+ const bufferArray = []
1008
+ for await (const chunk of stream) {
1009
+ bufferArray.push(chunk)
1010
+ }
1011
+ return Buffer.concat(bufferArray)
1012
+ }
1013
+
1014
+ const downloadExternalPatch = async (blob, options) => {
1015
+ const buffer = await downloadExternalBlob(blob, options)
1016
+ const syncData = proto.SyncdMutations.decode(buffer)
1017
+ return syncData
1018
+ }
1019
+
1020
+ const decodeSyncdSnapshot = async (name, snapshot, getAppStateSyncKey, minimumVersionNumber, validateMacs = true) => {
1021
+ const newState = newLTHashState()
1022
+ newState.version = toNumber(snapshot.version.version)
1023
+ const mutationMap = {}
1024
+ const areMutationsRequired = typeof minimumVersionNumber === 'undefined'
1025
+ || newState.version > minimumVersionNumber
1026
+ const { hash, indexValueMap } = await decodeSyncdMutations(snapshot.records, newState, getAppStateSyncKey, areMutationsRequired
1027
+ ? (mutation) => {
1028
+ const index = mutation.syncAction.index?.toString()
1029
+ mutationMap[index] = mutation
1030
+ }
1031
+ : () => { }, validateMacs)
1032
+ newState.hash = hash
1033
+ newState.indexValueMap = indexValueMap
1034
+ if (validateMacs) {
1035
+ const base64Key = Buffer.from(snapshot.keyId.id).toString('base64')
1036
+ const keyEnc = await getAppStateSyncKey(base64Key)
1037
+ if (!keyEnc) {
1038
+ throw new Boom(`failed to find key "${base64Key}" to decode mutation`)
1039
+ }
1040
+ const result = await mutationKeys(keyEnc.keyData)
1041
+ const computedSnapshotMac = generateSnapshotMac(newState.hash, newState.version, name, result.snapshotMacKey)
1042
+ if (Buffer.compare(snapshot.mac, computedSnapshotMac) !== 0) {
1043
+ throw new Boom(`failed to verify LTHash at ${newState.version} of ${name} from snapshot`)
1044
+ }
1045
+ }
1046
+ return {
1047
+ state: newState,
1048
+ mutationMap
1049
+ }
1050
+ }
1051
+
1052
+ const decodePatches = async (name, syncds, initial, getAppStateSyncKey, options, minimumVersionNumber, logger, validateMacs = true) => {
1053
+ const newState = {
1054
+ ...initial,
1055
+ indexValueMap: { ...initial.indexValueMap }
1056
+ }
1057
+
1058
+ const mutationMap = {}
1059
+
1060
+ for (const syncd of syncds) {
1061
+ const { version, keyId, snapshotMac } = syncd
1062
+
1063
+ if (syncd.externalMutations) {
1064
+ logger?.trace({ name, version }, 'downloading external patch')
1065
+
1066
+ const ref = await downloadExternalPatch(syncd.externalMutations, options)
1067
+
1068
+ logger?.debug({ name, version, mutations: ref.mutations.length }, 'downloaded external patch')
1069
+ syncd.mutations?.push(...ref.mutations);
1070
+ }
1071
+
1072
+ const patchVersion = toNumber(version.version)
1073
+
1074
+ newState.version = patchVersion
1075
+
1076
+ const shouldMutate = typeof minimumVersionNumber === 'undefined' || patchVersion > minimumVersionNumber
1077
+ const decodeResult = await decodeSyncdPatch(syncd, name, newState, getAppStateSyncKey, shouldMutate
1078
+ ? mutation => {
1079
+ const index = mutation.syncAction.index?.toString()
1080
+ mutationMap[index] = mutation
1081
+ }
1082
+ : () => { }, true)
1083
+
1084
+ newState.hash = decodeResult.hash
1085
+ newState.indexValueMap = decodeResult.indexValueMap
1086
+
1087
+ if (validateMacs) {
1088
+ const base64Key = Buffer.from(keyId.id).toString('base64')
1089
+ const keyEnc = await getAppStateSyncKey(base64Key)
1090
+
1091
+ if (!keyEnc) {
1092
+ throw new Boom(`failed to find key "${base64Key}" to decode mutation`)
1093
+ }
1094
+
1095
+ const result = await mutationKeys(keyEnc.keyData)
1096
+ const computedSnapshotMac = generateSnapshotMac(newState.hash, newState.version, name, result.snapshotMacKey)
1097
+
1098
+ if (Buffer.compare(snapshotMac, computedSnapshotMac) !== 0) {
1099
+ throw new Boom(`failed to verify LTHash at ${newState.version} of ${name}`)
1100
+ }
1101
+ }
1102
+
1103
+ // clear memory used up by the mutations
1104
+ syncd.mutations = []
1105
+ }
1106
+ return { state: newState, mutationMap }
1107
+ }
1108
+
1109
+ const chatModificationToAppPatch = (mod, jid) => {
1110
+ const OP = proto.SyncdMutation.SyncdOperation
1111
+ const getMessageRange = (lastMessages) => {
1112
+ let messageRange
1113
+
1114
+ if (Array.isArray(lastMessages)) {
1115
+ const lastMsg = lastMessages[lastMessages.length - 1]
1116
+ messageRange = {
1117
+ lastMessageTimestamp: lastMsg?.messageTimestamp,
1118
+ messages: lastMessages?.length
1119
+ ? lastMessages.map(m => {
1120
+ if (!m.key?.id || !m.key?.remoteJid) {
1121
+ throw new Boom('Incomplete key', { statusCode: 400, data: m })
1122
+ }
1123
+
1124
+ if (isJidGroup(m.key.remoteJid) && !m.key.fromMe && !m.key.participant) {
1125
+ throw new Boom('Expected not from me message to have participant', { statusCode: 400, data: m })
1126
+ }
1127
+
1128
+ if (!m.messageTimestamp || !toNumber(m.messageTimestamp)) {
1129
+ throw new Boom('Missing timestamp in last message list', { statusCode: 400, data: m })
1130
+ }
1131
+
1132
+ if (m.key.participant) {
1133
+ m.key.participant = jidNormalizedUser(m.key.participant)
1134
+ }
1135
+
1136
+ return m
1137
+ })
1138
+ : undefined
1139
+ }
1140
+ }
1141
+
1142
+ else {
1143
+ messageRange = lastMessages
1144
+ }
1145
+
1146
+ return messageRange
1147
+ }
1148
+
1149
+ let patch
1150
+
1151
+ if ('mute' in mod) {
1152
+ patch = {
1153
+ syncAction: {
1154
+ muteAction: {
1155
+ muted: !!mod.mute,
1156
+ muteEndTimestamp: mod.mute || undefined
1157
+ }
1158
+ },
1159
+ index: ['mute', jid],
1160
+ type: 'regular_high',
1161
+ apiVersion: 2,
1162
+ operation: OP.SET
1163
+ }
1164
+ }
1165
+
1166
+ else if ('archive' in mod) {
1167
+ patch = {
1168
+ syncAction: {
1169
+ archiveChatAction: {
1170
+ archived: !!mod.archive,
1171
+ messageRange: getMessageRange(mod.lastMessages)
1172
+ }
1173
+ },
1174
+ index: ['archive', jid],
1175
+ type: 'regular_low',
1176
+ apiVersion: 3,
1177
+ operation: OP.SET
1178
+ }
1179
+ }
1180
+
1181
+ else if ('markRead' in mod) {
1182
+ patch = {
1183
+ syncAction: {
1184
+ markChatAsReadAction: {
1185
+ read: mod.markRead,
1186
+ messageRange: getMessageRange(mod.lastMessages)
1187
+ }
1188
+ },
1189
+ index: ['markChatAsRead', jid],
1190
+ type: 'regular_low',
1191
+ apiVersion: 3,
1192
+ operation: OP.SET
1193
+ }
1194
+ }
1195
+
1196
+ else if ('deleteForMe' in mod) {
1197
+ const { timestamp, key, deleteMedia } = mod.deleteForMe
1198
+ patch = {
1199
+ syncAction: {
1200
+ deleteMessageForMeAction: {
1201
+ deleteMedia,
1202
+ messageTimestamp: timestamp
1203
+ }
1204
+ },
1205
+ index: ['deleteMessageForMe', jid, key.id, key.fromMe ? '1' : '0', '0'],
1206
+ type: 'regular_high',
1207
+ apiVersion: 3,
1208
+ operation: OP.SET
1209
+ }
1210
+ }
1211
+
1212
+ else if ('clear' in mod) {
1213
+ patch = {
1214
+ syncAction: {
1215
+ clearChatAction: {
1216
+ messageRange: getMessageRange(mod.lastMessages)
1217
+ }
1218
+ },
1219
+ index: ['clearChat', jid, '1' /*the option here is 0 when keep starred messages is enabled*/, '0'],
1220
+ type: 'regular_high',
1221
+ apiVersion: 6,
1222
+ operation: OP.SET
1223
+ }
1224
+ }
1225
+
1226
+ else if ('pin' in mod) {
1227
+ patch = {
1228
+ syncAction: {
1229
+ pinAction: {
1230
+ pinned: !!mod.pin
1231
+ }
1232
+ },
1233
+ index: ['pin_v1', jid],
1234
+ type: 'regular_low',
1235
+ apiVersion: 5,
1236
+ operation: OP.SET
1237
+ }
1238
+ }
1239
+
1240
+ else if ('contact' in mod) {
1241
+ patch = {
1242
+ syncAction: {
1243
+ contactAction: mod.contact || {}
1244
+ },
1245
+ index: ['contact', jid],
1246
+ type: 'critical_unblock_low',
1247
+ apiVersion: 2,
1248
+ operation: mod.contact ? OP.SET : OP.REMOVE
1249
+ }
1250
+ }
1251
+
1252
+ else if ('disableLinkPreviews' in mod) {
1253
+ patch = {
1254
+ syncAction: {
1255
+ privacySettingDisableLinkPreviewsAction: mod.disableLinkPreviews || {}
1256
+ },
1257
+ index: ['setting_disableLinkPreviews'],
1258
+ type: 'regular',
1259
+ apiVersion: 8,
1260
+ operation: OP.SET
1261
+ }
1262
+ }
1263
+
1264
+ else if ('star' in mod) {
1265
+ const key = mod.star.messages[0]
1266
+ patch = {
1267
+ syncAction: {
1268
+ starAction: {
1269
+ starred: !!mod.star.star
1270
+ }
1271
+ },
1272
+ index: ['star', jid, key.id, key.fromMe ? '1' : '0', '0'],
1273
+ type: 'regular_low',
1274
+ apiVersion: 2,
1275
+ operation: OP.SET
1276
+ }
1277
+ }
1278
+
1279
+ else if ('delete' in mod) {
1280
+ patch = {
1281
+ syncAction: {
1282
+ deleteChatAction: {
1283
+ messageRange: getMessageRange(mod.lastMessages),
1284
+ }
1285
+ },
1286
+ index: ['deleteChat', jid, '1'],
1287
+ type: 'regular_high',
1288
+ apiVersion: 6,
1289
+ operation: OP.SET
1290
+ }
1291
+ }
1292
+
1293
+ else if ('pushNameSetting' in mod) {
1294
+ patch = {
1295
+ syncAction: {
1296
+ pushNameSetting: {
1297
+ name: mod.pushNameSetting
1298
+ }
1299
+ },
1300
+ index: ['setting_pushName'],
1301
+ type: 'critical_block',
1302
+ apiVersion: 1,
1303
+ operation: OP.SET
1304
+ }
1305
+ }
1306
+
1307
+ else if ('quickReply' in mod) {
1308
+ patch = {
1309
+ syncAction: {
1310
+ quickReplyAction: {
1311
+ count: 0,
1312
+ deleted: mod.quickReply.deleted || false,
1313
+ keywords: [],
1314
+ message: mod.quickReply.message || '',
1315
+ shortcut: mod.quickReply.shortcut || ''
1316
+ }
1317
+ },
1318
+ index: ['quick_reply', mod.quickReply.timestamp || String(Math.floor(Date.now() / 1000))],
1319
+ type: 'regular',
1320
+ apiVersion: 2,
1321
+ operation: OP.SET
1322
+ }
1323
+ }
1324
+
1325
+ else if ('addLabel' in mod) {
1326
+ patch = {
1327
+ syncAction: {
1328
+ labelEditAction: {
1329
+ name: mod.addLabel.name,
1330
+ color: mod.addLabel.color,
1331
+ predefinedId: mod.addLabel.predefinedId,
1332
+ deleted: mod.addLabel.deleted
1333
+ }
1334
+ },
1335
+ index: ['label_edit', mod.addLabel.id],
1336
+ type: 'regular',
1337
+ apiVersion: 3,
1338
+ operation: OP.SET
1339
+ }
1340
+ }
1341
+
1342
+ else if ('addChatLabel' in mod) {
1343
+ patch = {
1344
+ syncAction: {
1345
+ labelAssociationAction: {
1346
+ labeled: true,
1347
+ }
1348
+ },
1349
+ index: [LabelAssociationType.Chat, mod.addChatLabel.labelId, jid],
1350
+ type: 'regular',
1351
+ apiVersion: 3,
1352
+ operation: OP.SET
1353
+ }
1354
+ }
1355
+
1356
+ else if ('removeChatLabel' in mod) {
1357
+ patch = {
1358
+ syncAction: {
1359
+ labelAssociationAction: {
1360
+ labeled: false
1361
+ }
1362
+ },
1363
+ index: [LabelAssociationType.Chat, mod.removeChatLabel.labelId, jid],
1364
+ type: 'regular',
1365
+ apiVersion: 3,
1366
+ operation: OP.SET
1367
+ }
1368
+ }
1369
+
1370
+ else if ('addMessageLabel' in mod) {
1371
+ patch = {
1372
+ syncAction: {
1373
+ labelAssociationAction: {
1374
+ labeled: true,
1375
+ }
1376
+ },
1377
+ index: [
1378
+ LabelAssociationType.Message,
1379
+ mod.addMessageLabel.labelId,
1380
+ jid,
1381
+ mod.addMessageLabel.messageId,
1382
+ '0',
1383
+ '0'
1384
+ ],
1385
+ type: 'regular',
1386
+ apiVersion: 3,
1387
+ operation: OP.SET
1388
+ }
1389
+ }
1390
+
1391
+ else if ('removeMessageLabel' in mod) {
1392
+ patch = {
1393
+ syncAction: {
1394
+ labelAssociationAction: {
1395
+ labeled: false,
1396
+ }
1397
+ },
1398
+ index: [
1399
+ LabelAssociationType.Message,
1400
+ mod.removeMessageLabel.labelId,
1401
+ jid,
1402
+ mod.removeMessageLabel.messageId,
1403
+ '0',
1404
+ '0'
1405
+ ],
1406
+ type: 'regular',
1407
+ apiVersion: 3,
1408
+ operation: OP.SET
1409
+ }
1410
+ }
1411
+
1412
+ else {
1413
+ throw new Boom('not supported')
1414
+ }
1415
+
1416
+ patch.syncAction.timestamp = Date.now()
1417
+
1418
+ return patch
1419
+ }
1420
+
1421
+ const processSyncAction = (syncAction, ev, me, initialSyncOpts, logger) => {
1422
+ const isInitialSync = !!initialSyncOpts
1423
+ const accountSettings = initialSyncOpts?.accountSettings
1424
+
1425
+ logger?.trace({ syncAction, initialSync: !!initialSyncOpts }, 'processing sync action')
1426
+
1427
+ const { syncAction: { value: action }, index: [type, id, msgId, fromMe] } = syncAction
1428
+
1429
+ if (action?.muteAction) {
1430
+ ev.emit('chats.update', [
1431
+ {
1432
+ id,
1433
+ muteEndTime: action.muteAction?.muted
1434
+ ? toNumber(action.muteAction.muteEndTimestamp)
1435
+ : null,
1436
+ conditional: getChatUpdateConditional(id, undefined)
1437
+ }
1438
+ ])
1439
+ }
1440
+
1441
+ else if (action?.archiveChatAction || type === 'archive' || type === 'unarchive') {
1442
+ // okay so we've to do some annoying computation here
1443
+ // when we're initially syncing the app state
1444
+ // there are a few cases we need to handle
1445
+ // 1. if the account unarchiveChats setting is true
1446
+ // a. if the chat is archived, and no further messages have been received -- simple, keep archived
1447
+ // b. if the chat was archived, and the user received messages from the other person afterwards
1448
+ // then the chat should be marked unarchved --
1449
+ // we compare the timestamp of latest message from the other person to determine this
1450
+ // 2. if the account unarchiveChats setting is false -- then it doesn't matter,
1451
+ // it'll always take an app state action to mark in unarchived -- which we'll get anyway
1452
+ const archiveAction = action?.archiveChatAction
1453
+ const isArchived = archiveAction ? archiveAction.archived : type === 'archive'
1454
+ // // basically we don't need to fire an "archive" update if the chat is being marked unarchvied
1455
+ // // this only applies for the initial sync
1456
+ // if(isInitialSync && !isArchived) {
1457
+ // isArchived = false
1458
+ // }
1459
+ const msgRange = !accountSettings?.unarchiveChats ? undefined : archiveAction?.messageRange
1460
+ // logger?.debug({ chat: id, syncAction }, 'message range archive')
1461
+ ev.emit('chats.update', [
1462
+ {
1463
+ id,
1464
+ archived: isArchived,
1465
+ conditional: getChatUpdateConditional(id, msgRange)
1466
+ }
1467
+ ])
1468
+ }
1469
+
1470
+ else if (action?.markChatAsReadAction) {
1471
+ const markReadAction = action.markChatAsReadAction
1472
+ // basically we don't need to fire an "read" update if the chat is being marked as read
1473
+ // because the chat is read by default
1474
+ // this only applies for the initial sync
1475
+ const isNullUpdate = isInitialSync && markReadAction.read
1476
+ ev.emit('chats.update', [{
1477
+ id,
1478
+ unreadCount: isNullUpdate ? null : !!markReadAction?.read ? 0 : -1,
1479
+ conditional: getChatUpdateConditional(id, markReadAction?.messageRange)
1480
+ }])
1481
+ }
1482
+
1483
+ else if (action?.deleteMessageForMeAction || type === 'deleteMessageForMe') {
1484
+ ev.emit('messages.delete', {
1485
+ keys: [
1486
+ {
1487
+ remoteJid: id,
1488
+ id: msgId,
1489
+ fromMe: fromMe === '1'
1490
+ }
1491
+ ]
1492
+ })
1493
+ }
1494
+
1495
+ else if (action?.contactAction) {
1496
+ ev.emit('contacts.upsert', [
1497
+ {
1498
+ id: id,
1499
+ name: action.contactAction.fullName,
1500
+ lid: action.contactAction.lidJid || undefined,
1501
+ phoneNumber: action.contactAction.pnJid || undefined
1502
+ }
1503
+ ])
1504
+ }
1505
+
1506
+ else if (action?.pushNameSetting) {
1507
+ const name = action?.pushNameSetting?.name
1508
+ if (name && me?.name !== name) {
1509
+ ev.emit('creds.update', { me: { ...me, name } })
1510
+ }
1511
+ }
1512
+
1513
+ else if (action?.pinAction) {
1514
+ ev.emit('chats.update', [{
1515
+ id,
1516
+ pinned: action.pinAction?.pinned ? toNumber(action.timestamp) : null,
1517
+ conditional: getChatUpdateConditional(id, undefined)
1518
+ }])
1519
+ }
1520
+
1521
+ else if (action?.unarchiveChatsSetting) {
1522
+ const unarchiveChats = !!action.unarchiveChatsSetting.unarchiveChats
1523
+
1524
+ ev.emit('creds.update', { accountSettings: { unarchiveChats } })
1525
+ logger?.info(`archive setting updated => '${action.unarchiveChatsSetting.unarchiveChats}'`)
1526
+
1527
+ if (accountSettings) {
1528
+ accountSettings.unarchiveChats = unarchiveChats
1529
+ }
1530
+ }
1531
+
1532
+ else if (action?.starAction || type === 'star') {
1533
+ let starred = action?.starAction?.starred
1534
+ if (typeof starred !== 'boolean') {
1535
+ starred = syncAction.index[syncAction.index.length - 1] === '1'
1536
+ }
1537
+ ev.emit('messages.update', [
1538
+ {
1539
+ key: { remoteJid: id, id: msgId, fromMe: fromMe === '1' },
1540
+ update: { starred }
1541
+ }
1542
+ ])
1543
+ }
1544
+
1545
+ else if (action?.deleteChatAction || type === 'deleteChat') {
1546
+ if (!isInitialSync) {
1547
+ ev.emit('chats.delete', [id])
1548
+ }
1549
+ }
1550
+
1551
+ else if (action?.labelEditAction) {
1552
+ const { name, color, deleted, predefinedId } = action.labelEditAction
1553
+ ev.emit('labels.edit', {
1554
+ id,
1555
+ name: name,
1556
+ color: color,
1557
+ deleted: deleted,
1558
+ predefinedId: predefinedId ? String(predefinedId) : undefined
1559
+ })
1560
+ }
1561
+
1562
+ else if (action?.labelAssociationAction) {
1563
+ ev.emit('labels.association', {
1564
+ type: action.labelAssociationAction.labeled
1565
+ ? 'add'
1566
+ : 'remove',
1567
+ association: type === LabelAssociationType.Chat
1568
+ ? {
1569
+ type: LabelAssociationType.Chat,
1570
+ chatId: syncAction.index[2],
1571
+ labelId: syncAction.index[1]
1572
+ }
1573
+ : {
1574
+ type: LabelAssociationType.Message,
1575
+ chatId: syncAction.index[2],
1576
+ messageId: syncAction.index[3],
1577
+ labelId: syncAction.index[1]
1578
+ }
1579
+ })
1580
+ }
1581
+
1582
+ else if (action?.localeSetting?.locale) {
1583
+ ev.emit('settings.update', { setting: 'locale', value: action.localeSetting.locale })
1584
+ }
1585
+
1586
+ else if (action?.timeFormatAction) {
1587
+ ev.emit('settings.update', { setting: 'timeFormat', value: action.timeFormatAction })
1588
+ }
1589
+
1590
+ else if (action?.pnForLidChatAction) {
1591
+ if (action.pnForLidChatAction.pnJid) {
1592
+ ev.emit('lid-mapping.update', { lid: id, pn: action.pnForLidChatAction.pnJid })
1593
+ }
1594
+ }
1595
+
1596
+ else if (action?.privacySettingRelayAllCalls) {
1597
+ ev.emit('settings.update', {
1598
+ setting: 'privacySettingRelayAllCalls',
1599
+ value: action.privacySettingRelayAllCalls
1600
+ })
1601
+ }
1602
+
1603
+ else if (action?.statusPrivacy) {
1604
+ ev.emit('settings.update', { setting: 'statusPrivacy', value: action.statusPrivacy })
1605
+ }
1606
+
1607
+ else if (action?.lockChatAction) {
1608
+ ev.emit('chats.lock', { id: id, locked: !!action.lockChatAction.locked })
1609
+ }
1610
+
1611
+ else if (action?.privacySettingDisableLinkPreviewsAction) {
1612
+ ev.emit('settings.update', {
1613
+ setting: 'disableLinkPreviews',
1614
+ value: action.privacySettingDisableLinkPreviewsAction
1615
+ })
1616
+ }
1617
+
1618
+ else if (action?.notificationActivitySettingAction?.notificationActivitySetting) {
1619
+ ev.emit('settings.update', {
1620
+ setting: 'notificationActivitySetting',
1621
+ value: action.notificationActivitySettingAction.notificationActivitySetting
1622
+ })
1623
+ }
1624
+
1625
+ else if (action?.lidContactAction) {
1626
+ ev.emit('contacts.upsert', [
1627
+ {
1628
+ id: id,
1629
+ name: action.lidContactAction.fullName,
1630
+ lid: id,
1631
+ phoneNumber: undefined
1632
+ }
1633
+ ])
1634
+ }
1635
+
1636
+ else if (action?.privacySettingChannelsPersonalisedRecommendationAction) {
1637
+ ev.emit('settings.update', {
1638
+ setting: 'channelsPersonalisedRecommendation',
1639
+ value: action.privacySettingChannelsPersonalisedRecommendationAction
1640
+ })
1641
+ }
1642
+
1643
+ else {
1644
+ logger?.debug({ syncAction, id }, 'unprocessable update')
1645
+ }
1646
+
1647
+ function getChatUpdateConditional(id, msgRange) {
1648
+ return isInitialSync
1649
+ ? (data) => {
1650
+ const chat = data.historySets.chats[id] || data.chatUpserts[id]
1651
+ if (chat) {
1652
+ return msgRange ? isValidPatchBasedOnMessageRange(chat, msgRange) : true
1653
+ }
1654
+ }
1655
+ : undefined
1656
+ }
1657
+
1658
+ function isValidPatchBasedOnMessageRange(chat, msgRange) {
1659
+ const lastMsgTimestamp = Number(msgRange?.lastMessageTimestamp || msgRange?.lastSystemMessageTimestamp || 0)
1660
+ const chatLastMsgTimestamp = Number(chat?.lastMessageRecvTimestamp || 0)
1661
+ return lastMsgTimestamp >= chatLastMsgTimestamp
1662
+ }
1663
+ }
1664
+
1665
+ module.exports = {
1666
+ mutationKeys,
1667
+ newLTHashState,
1668
+ encodeSyncdPatch,
1669
+ decodeSyncdMutations,
1670
+ decodeSyncdPatch,
1671
+ extractSyncdPatches,
1672
+ downloadExternalBlob,
1673
+ downloadExternalPatch,
1674
+ decodeSyncdSnapshot,
1675
+ decodePatches,
1676
+ chatModificationToAppPatch,
1677
+ processSyncAction
1678
+ }
1679
+ return module.exports;
1680
+ })();
1681
+ for (const key of Object.keys(__kageModule61)) {
1682
+ if (key !== '__esModule' && !Object.prototype.hasOwnProperty.call(exports,key)) Object.defineProperty(exports,key,{enumerable:true,configurable:true,get:()=>__kageModule61[key]});
1683
+ }
1684
+
1685
+ // Kage: funciones avanzadas.
1686
+ const __kageModule164 = (() => {
1687
+ const module = { exports: {} }; const exports = module.exports;
1688
+ "use strict";
1689
+ Object.defineProperty(exports, "__esModule", { value: true });
1690
+ exports.processSyncAction = exports.chatModificationToAppPatch = exports.decodePatches = exports.decodeSyncdSnapshot = exports.downloadExternalPatch = exports.downloadExternalBlob = exports.extractSyncdPatches = exports.decodeSyncdPatch = exports.decodeSyncdMutations = exports.encodeSyncdPatch = exports.isAppStateSyncIrrecoverable = exports.isMissingKeyError = exports.MAX_SYNC_ATTEMPTS = exports.ensureLTHashStateVersion = exports.newLTHashState = exports.makeLtHashGenerator = void 0;
1691
+ const expandAppStateKeys = key => { const b = require('node:crypto').hkdfSync('sha256', key, Buffer.alloc(0), 'WhatsApp Mutation Keys', 160); const v = Buffer.from(b); return { indexKey: v.subarray(0, 32), valueEncryptionKey: v.subarray(32, 64), valueMacKey: v.subarray(64, 96), snapshotMacKey: v.subarray(96, 128), patchMacKey: v.subarray(128, 160) }; };
1692
+ const index_js_1 = require("../../WAProto/index.js");
1693
+ const index_js_2 = require("../Addon/index.js");
1694
+ const LabelAssociation_js_1 = require("../Types/LabelAssociation.js");
1695
+ const index_js_3 = require("../WABinary/index.js");
1696
+ const crypto_js_1 = require("./crypto.js");
1697
+ const generics_js_1 = require("./generics.js");
1698
+ const lt_hash_js_1 = require("./lt-hash.js");
1699
+ const messages_media_js_1 = require("./messages-media.js");
1700
+ const sync_action_utils_js_1 = require("./sync-action-utils.js");
1701
+ const mutationKeys = (keydata) => {
1702
+ const keys = expandAppStateKeys(keydata);
1703
+ return {
1704
+ indexKey: keys.indexKey,
1705
+ valueEncryptionKey: keys.valueEncryptionKey,
1706
+ valueMacKey: keys.valueMacKey,
1707
+ snapshotMacKey: keys.snapshotMacKey,
1708
+ patchMacKey: keys.patchMacKey
1709
+ };
1710
+ };
1711
+ const generateMac = (operation, data, keyId, key) => {
1712
+ const opByte = operation === index_js_1.proto.SyncdMutation.SyncdOperation.SET ? 0x01 : 0x02;
1713
+ const keyIdBuffer = typeof keyId === 'string' ? Buffer.from(keyId, 'base64') : keyId;
1714
+ const keyData = new Uint8Array(1 + keyIdBuffer.length);
1715
+ keyData[0] = opByte;
1716
+ keyData.set(keyIdBuffer, 1);
1717
+ const last = new Uint8Array(8);
1718
+ last[7] = keyData.length;
1719
+ const total = new Uint8Array(keyData.length + data.length + last.length);
1720
+ total.set(keyData, 0);
1721
+ total.set(data, keyData.length);
1722
+ total.set(last, keyData.length + data.length);
1723
+ const hmac = (0, crypto_js_1.hmacSign)(total, key, 'sha512');
1724
+ return hmac.subarray(0, 32);
1725
+ };
1726
+ const to64BitNetworkOrder = (e) => {
1727
+ const buff = Buffer.alloc(8);
1728
+ buff.writeUint32BE(e, 4);
1729
+ return buff;
1730
+ };
1731
+ const makeLtHashGenerator = ({ indexValueMap, hash }) => {
1732
+ indexValueMap = { ...indexValueMap };
1733
+ const addBuffs = [];
1734
+ const subBuffs = [];
1735
+ return {
1736
+ mix: ({ indexMac, valueMac, operation }) => {
1737
+ const indexMacBase64 = Buffer.from(indexMac).toString('base64');
1738
+ const prevOp = indexValueMap[indexMacBase64];
1739
+ if (operation === index_js_1.proto.SyncdMutation.SyncdOperation.REMOVE) {
1740
+ if (!prevOp) {
1741
+ // WA Web does not throw here — it logs a warning and skips the subtract.
1742
+ // The missing REMOVE will cause an LTHash mismatch, which is handled
1743
+ // by the MAC validation layer (snapshot recovery or retry).
1744
+ return;
1745
+ }
1746
+ // remove from index value mac, since this mutation is erased
1747
+ delete indexValueMap[indexMacBase64];
1748
+ }
1749
+ else {
1750
+ addBuffs.push(valueMac);
1751
+ // add this index into the history map
1752
+ indexValueMap[indexMacBase64] = { valueMac };
1753
+ }
1754
+ if (prevOp) {
1755
+ subBuffs.push(prevOp.valueMac);
1756
+ }
1757
+ },
1758
+ finish: () => {
1759
+ const result = lt_hash_js_1.LT_HASH_ANTI_TAMPERING.subtractThenAdd(hash, subBuffs, addBuffs);
1760
+ return {
1761
+ hash: Buffer.from(result),
1762
+ indexValueMap
1763
+ };
1764
+ }
1765
+ };
1766
+ };
1767
+ exports.makeLtHashGenerator = makeLtHashGenerator;
1768
+ const generateSnapshotMac = (lthash, version, name, key) => {
1769
+ const total = Buffer.concat([lthash, to64BitNetworkOrder(version), Buffer.from(name, 'utf-8')]);
1770
+ return (0, crypto_js_1.hmacSign)(total, key, 'sha256');
1771
+ };
1772
+ const generatePatchMac = (snapshotMac, valueMacs, version, type, key) => {
1773
+ const total = Buffer.concat([snapshotMac, ...valueMacs, to64BitNetworkOrder(version), Buffer.from(type, 'utf-8')]);
1774
+ return (0, crypto_js_1.hmacSign)(total, key);
1775
+ };
1776
+ const newLTHashState = () => ({ version: 0, hash: Buffer.alloc(128), indexValueMap: {} });
1777
+ exports.newLTHashState = newLTHashState;
1778
+ const ensureLTHashStateVersion = (state) => {
1779
+ if (typeof state.version !== 'number' || isNaN(state.version)) {
1780
+ state.version = 0;
1781
+ }
1782
+ return state;
1783
+ };
1784
+ exports.ensureLTHashStateVersion = ensureLTHashStateVersion;
1785
+ exports.MAX_SYNC_ATTEMPTS = 2;
1786
+ /**
1787
+ * Check if an error is a missing app state sync key.
1788
+ * WA Web treats these as "Blocked" (waits for key arrival), not fatal.
1789
+ * In Baileys we retry with a snapshot which may use a different key.
1790
+ */
1791
+ const isMissingKeyError = (error) => {
1792
+ return error?.data?.isMissingKey === true;
1793
+ };
1794
+ exports.isMissingKeyError = isMissingKeyError;
1795
+ /**
1796
+ * Determines if an app state sync error is unrecoverable.
1797
+ * TypeError indicates a WASM crash; otherwise we give up after MAX_SYNC_ATTEMPTS.
1798
+ * Missing keys are NOT checked here — they are handled separately as "Blocked".
1799
+ */
1800
+ const isAppStateSyncIrrecoverable = (error, attempts) => {
1801
+ return attempts >= exports.MAX_SYNC_ATTEMPTS || error?.name === 'TypeError';
1802
+ };
1803
+ exports.isAppStateSyncIrrecoverable = isAppStateSyncIrrecoverable;
1804
+ const encodeSyncdPatch = async ({ type, index, syncAction, apiVersion, operation }, myAppStateKeyId, state, getAppStateSyncKey) => {
1805
+ const key = !!myAppStateKeyId ? await getAppStateSyncKey(myAppStateKeyId) : undefined;
1806
+ if (!key) {
1807
+ throw new index_js_2.HttpError(`myAppStateKey ("${myAppStateKeyId}") not present`, { data: { isMissingKey: true } });
1808
+ }
1809
+ const encKeyId = Buffer.from(myAppStateKeyId, 'base64');
1810
+ state = { ...state, indexValueMap: { ...state.indexValueMap } };
1811
+ const indexBuffer = Buffer.from(JSON.stringify(index));
1812
+ const dataProto = index_js_1.proto.SyncActionData.fromObject({
1813
+ index: indexBuffer,
1814
+ value: syncAction,
1815
+ padding: new Uint8Array(0),
1816
+ version: apiVersion
1817
+ });
1818
+ const encoded = index_js_1.proto.SyncActionData.encode(dataProto).finish();
1819
+ const keyValue = mutationKeys(key.keyData);
1820
+ const encValue = (0, crypto_js_1.aesEncrypt)(encoded, keyValue.valueEncryptionKey);
1821
+ const valueMac = generateMac(operation, encValue, encKeyId, keyValue.valueMacKey);
1822
+ const indexMac = (0, crypto_js_1.hmacSign)(indexBuffer, keyValue.indexKey);
1823
+ // update LT hash
1824
+ const generator = (0, exports.makeLtHashGenerator)(state);
1825
+ generator.mix({ indexMac, valueMac, operation });
1826
+ Object.assign(state, generator.finish());
1827
+ state.version += 1;
1828
+ const snapshotMac = generateSnapshotMac(state.hash, state.version, type, keyValue.snapshotMacKey);
1829
+ const patch = {
1830
+ patchMac: generatePatchMac(snapshotMac, [valueMac], state.version, type, keyValue.patchMacKey),
1831
+ snapshotMac: snapshotMac,
1832
+ keyId: { id: encKeyId },
1833
+ mutations: [
1834
+ {
1835
+ operation: operation,
1836
+ record: {
1837
+ index: {
1838
+ blob: indexMac
1839
+ },
1840
+ value: {
1841
+ blob: Buffer.concat([encValue, valueMac])
1842
+ },
1843
+ keyId: { id: encKeyId }
1844
+ }
1845
+ }
1846
+ ]
1847
+ };
1848
+ const base64Index = indexMac.toString('base64');
1849
+ state.indexValueMap[base64Index] = { valueMac };
1850
+ return { patch, state };
1851
+ };
1852
+ exports.encodeSyncdPatch = encodeSyncdPatch;
1853
+ const decodeSyncdMutations = async (msgMutations, initialState, getAppStateSyncKey, onMutation, validateMacs) => {
1854
+ const ltGenerator = (0, exports.makeLtHashGenerator)(initialState);
1855
+ const derivedKeyCache = new Map();
1856
+ // indexKey used to HMAC sign record.index.blob
1857
+ // valueEncryptionKey used to AES-256-CBC encrypt record.value.blob[0:-32]
1858
+ // the remaining record.value.blob[0:-32] is the mac, it the HMAC sign of key.keyId + decoded proto data + length of bytes in keyId
1859
+ for (const msgMutation of msgMutations) {
1860
+ // if it's a syncdmutation, get the operation property
1861
+ // otherwise, if it's only a record -- it'll be a SET mutation
1862
+ const operation = 'operation' in msgMutation ? msgMutation.operation : index_js_1.proto.SyncdMutation.SyncdOperation.SET;
1863
+ const record = 'record' in msgMutation && !!msgMutation.record ? msgMutation.record : msgMutation;
1864
+ let key;
1865
+ try {
1866
+ key = await getKey(record.keyId.id);
1867
+ }
1868
+ catch (err) {
1869
+ // Missing-key errors must propagate so the orchestrator can park the
1870
+ // collection (Blocked) and retry when APP_STATE_SYNC_KEY_SHARE arrives.
1871
+ // Other errors → individual record corruption, skip and keep going.
1872
+ if ((0, exports.isMissingKeyError)(err))
1873
+ throw err;
1874
+ continue;
1875
+ }
1876
+ const content = record.value.blob;
1877
+ const encContent = content.subarray(0, -32);
1878
+ const ogValueMac = content.subarray(-32);
1879
+ if (validateMacs) {
1880
+ const contentHmac = generateMac(operation, encContent, record.keyId.id, key.valueMacKey);
1881
+ if (Buffer.compare(contentHmac, ogValueMac) !== 0) {
1882
+ // HMAC verification failed — skip this record
1883
+ continue;
1884
+ }
1885
+ }
1886
+ let result;
1887
+ try {
1888
+ result = (0, crypto_js_1.aesDecrypt)(encContent, key.valueEncryptionKey);
1889
+ }
1890
+ catch {
1891
+ // decrypt failed — skip this record instead of aborting
1892
+ continue;
1893
+ }
1894
+ const syncAction = index_js_1.proto.SyncActionData.decode(result);
1895
+ if (validateMacs) {
1896
+ const hmac = (0, crypto_js_1.hmacSign)(syncAction.index, key.indexKey);
1897
+ if (Buffer.compare(hmac, record.index.blob) !== 0) {
1898
+ throw new index_js_2.HttpError('HMAC index verification failed');
1899
+ }
1900
+ }
1901
+ const indexStr = Buffer.from(syncAction.index).toString();
1902
+ onMutation({ syncAction, index: JSON.parse(indexStr) });
1903
+ ltGenerator.mix({
1904
+ indexMac: record.index.blob,
1905
+ valueMac: ogValueMac,
1906
+ operation: operation
1907
+ });
1908
+ }
1909
+ return ltGenerator.finish();
1910
+ async function getKey(keyId) {
1911
+ const base64Key = Buffer.from(keyId).toString('base64');
1912
+ const cached = derivedKeyCache.get(base64Key);
1913
+ if (cached) {
1914
+ return cached;
1915
+ }
1916
+ const keyEnc = await getAppStateSyncKey(base64Key);
1917
+ if (!keyEnc) {
1918
+ throw new index_js_2.HttpError(`failed to find key "${base64Key}" to decode mutation`, {
1919
+ data: { isMissingKey: true, msgMutations }
1920
+ });
1921
+ }
1922
+ const keys = mutationKeys(keyEnc.keyData);
1923
+ derivedKeyCache.set(base64Key, keys);
1924
+ return keys;
1925
+ }
1926
+ };
1927
+ exports.decodeSyncdMutations = decodeSyncdMutations;
1928
+ const decodeSyncdPatch = async (msg, name, initialState, getAppStateSyncKey, onMutation, validateMacs) => {
1929
+ if (validateMacs) {
1930
+ const base64Key = Buffer.from(msg.keyId.id).toString('base64');
1931
+ const mainKeyObj = await getAppStateSyncKey(base64Key);
1932
+ if (!mainKeyObj) {
1933
+ throw new index_js_2.HttpError(`failed to find key "${base64Key}" to decode patch`, { data: { isMissingKey: true, msg } });
1934
+ }
1935
+ const mainKey = mutationKeys(mainKeyObj.keyData);
1936
+ const mutationmacs = msg.mutations.map(mutation => mutation.record.value.blob.slice(-32));
1937
+ const patchMac = generatePatchMac(msg.snapshotMac, mutationmacs, (0, generics_js_1.toNumber)(msg.version.version), name, mainKey.patchMacKey);
1938
+ if (Buffer.compare(patchMac, msg.patchMac) !== 0) {
1939
+ throw new index_js_2.HttpError('Invalid patch mac');
1940
+ }
1941
+ }
1942
+ const result = await (0, exports.decodeSyncdMutations)(msg.mutations, initialState, getAppStateSyncKey, onMutation, validateMacs);
1943
+ return result;
1944
+ };
1945
+ exports.decodeSyncdPatch = decodeSyncdPatch;
1946
+ const extractSyncdPatches = async (result, options) => {
1947
+ const syncNode = (0, index_js_3.getBinaryNodeChild)(result, 'sync');
1948
+ const collectionNodes = (0, index_js_3.getBinaryNodeChildren)(syncNode, 'collection');
1949
+ const final = {};
1950
+ await Promise.all(collectionNodes.map(async (collectionNode) => {
1951
+ const patchesNode = (0, index_js_3.getBinaryNodeChild)(collectionNode, 'patches');
1952
+ const patches = (0, index_js_3.getBinaryNodeChildren)(patchesNode || collectionNode, 'patch');
1953
+ const snapshotNode = (0, index_js_3.getBinaryNodeChild)(collectionNode, 'snapshot');
1954
+ const syncds = [];
1955
+ const name = collectionNode.attrs.name;
1956
+ const hasMorePatches = collectionNode.attrs.has_more_patches === 'true';
1957
+ let snapshot = undefined;
1958
+ if (snapshotNode && !!snapshotNode.content) {
1959
+ if (!Buffer.isBuffer(snapshotNode)) {
1960
+ snapshotNode.content = Buffer.from(Object.values(snapshotNode.content));
1961
+ }
1962
+ const blobRef = index_js_1.proto.ExternalBlobReference.decode(snapshotNode.content);
1963
+ const data = await (0, exports.downloadExternalBlob)(blobRef, options);
1964
+ snapshot = index_js_1.proto.SyncdSnapshot.decode(data);
1965
+ }
1966
+ for (let { content } of patches) {
1967
+ if (content) {
1968
+ if (!Buffer.isBuffer(content)) {
1969
+ content = Buffer.from(Object.values(content));
1970
+ }
1971
+ const syncd = index_js_1.proto.SyncdPatch.decode(content);
1972
+ if (!syncd.version) {
1973
+ syncd.version = { version: +collectionNode.attrs.version + 1 };
1974
+ }
1975
+ syncds.push(syncd);
1976
+ }
1977
+ }
1978
+ final[name] = { patches: syncds, hasMorePatches, snapshot };
1979
+ }));
1980
+ return final;
1981
+ };
1982
+ exports.extractSyncdPatches = extractSyncdPatches;
1983
+ const downloadExternalBlob = async (blob, options) => {
1984
+ const stream = await (0, messages_media_js_1.downloadContentFromMessage)(blob, 'md-app-state', { options });
1985
+ const bufferArray = [];
1986
+ for await (const chunk of stream) {
1987
+ bufferArray.push(chunk);
1988
+ }
1989
+ return Buffer.concat(bufferArray);
1990
+ };
1991
+ exports.downloadExternalBlob = downloadExternalBlob;
1992
+ const downloadExternalPatch = async (blob, options) => {
1993
+ const buffer = await (0, exports.downloadExternalBlob)(blob, options);
1994
+ const syncData = index_js_1.proto.SyncdMutations.decode(buffer);
1995
+ return syncData;
1996
+ };
1997
+ exports.downloadExternalPatch = downloadExternalPatch;
1998
+ const decodeSyncdSnapshot = async (name, snapshot, getAppStateSyncKey, minimumVersionNumber, validateMacs = true, logger) => {
1999
+ const newState = (0, exports.newLTHashState)();
2000
+ newState.version = (0, generics_js_1.toNumber)(snapshot.version.version);
2001
+ const mutationMap = {};
2002
+ const areMutationsRequired = typeof minimumVersionNumber === 'undefined' || newState.version > minimumVersionNumber;
2003
+ const { hash, indexValueMap } = await (0, exports.decodeSyncdMutations)(snapshot.records, newState, getAppStateSyncKey, areMutationsRequired
2004
+ ? mutation => {
2005
+ const index = mutation.syncAction.index?.toString();
2006
+ mutationMap[index] = mutation;
2007
+ }
2008
+ : () => { }, validateMacs);
2009
+ newState.hash = hash;
2010
+ newState.indexValueMap = indexValueMap;
2011
+ if (validateMacs) {
2012
+ const base64Key = Buffer.from(snapshot.keyId.id).toString('base64');
2013
+ const keyEnc = await getAppStateSyncKey(base64Key);
2014
+ if (!keyEnc) {
2015
+ throw new index_js_2.HttpError(`failed to find key "${base64Key}" to decode mutation`, { data: { isMissingKey: true } });
2016
+ }
2017
+ const result = mutationKeys(keyEnc.keyData);
2018
+ const computedSnapshotMac = generateSnapshotMac(newState.hash, newState.version, name, result.snapshotMacKey);
2019
+ if (Buffer.compare(snapshot.mac, computedSnapshotMac) !== 0) {
2020
+ // LTHash verification may fail when decodeSyncdMutations skipped undecryptable
2021
+ // records (poisoned server-side snapshot); the aggregate client hash diverges
2022
+ // from the server-computed mac. Fall through with a warning so the session stays
2023
+ // alive with partial state, symmetric to how decodePatches handles its own
2024
+ // LTHash mismatch a few lines below.
2025
+ logger?.warn({ name, version: newState.version }, 'LTHash verification failed on snapshot, continuing with partial state');
2026
+ }
2027
+ }
2028
+ return {
2029
+ state: newState,
2030
+ mutationMap
2031
+ };
2032
+ };
2033
+ exports.decodeSyncdSnapshot = decodeSyncdSnapshot;
2034
+ const decodePatches = async (name, syncds, initial, getAppStateSyncKey, options, minimumVersionNumber, logger, validateMacs = true) => {
2035
+ const newState = {
2036
+ ...initial,
2037
+ indexValueMap: { ...initial.indexValueMap }
2038
+ };
2039
+ const mutationMap = {};
2040
+ for (const syncd of syncds) {
2041
+ const { version, keyId, snapshotMac } = syncd;
2042
+ if (syncd.externalMutations) {
2043
+ logger?.trace({ name, version }, 'downloading external patch');
2044
+ const ref = await (0, exports.downloadExternalPatch)(syncd.externalMutations, options);
2045
+ logger?.debug({ name, version, mutations: ref.mutations.length }, 'downloaded external patch');
2046
+ syncd.mutations?.push(...ref.mutations);
2047
+ }
2048
+ const patchVersion = (0, generics_js_1.toNumber)(version.version);
2049
+ newState.version = patchVersion;
2050
+ const shouldMutate = typeof minimumVersionNumber === 'undefined' || patchVersion > minimumVersionNumber;
2051
+ let decodeResult;
2052
+ try {
2053
+ decodeResult = await (0, exports.decodeSyncdPatch)(syncd, name, newState, getAppStateSyncKey, shouldMutate
2054
+ ? mutation => {
2055
+ const index = mutation.syncAction.index?.toString();
2056
+ mutationMap[index] = mutation;
2057
+ }
2058
+ : () => { }, validateMacs);
2059
+ }
2060
+ catch (err) {
2061
+ if ((0, exports.isMissingKeyError)(err))
2062
+ throw err;
2063
+ logger?.warn({ name, version: patchVersion, error: err.message }, 'failed to decode patch, skipping');
2064
+ continue;
2065
+ }
2066
+ newState.hash = decodeResult.hash;
2067
+ newState.indexValueMap = decodeResult.indexValueMap;
2068
+ if (validateMacs) {
2069
+ const base64Key = Buffer.from(keyId.id).toString('base64');
2070
+ const keyEnc = await getAppStateSyncKey(base64Key);
2071
+ if (!keyEnc) {
2072
+ throw new index_js_2.HttpError(`failed to find key "${base64Key}" to decode mutation`, { data: { isMissingKey: true } });
2073
+ }
2074
+ const result = mutationKeys(keyEnc.keyData);
2075
+ const computedSnapshotMac = generateSnapshotMac(newState.hash, newState.version, name, result.snapshotMacKey);
2076
+ if (Buffer.compare(snapshotMac, computedSnapshotMac) !== 0) {
2077
+ logger?.warn({ name, version: newState.version }, 'LTHash verification failed, skipping remaining patches');
2078
+ break;
2079
+ }
2080
+ }
2081
+ // clear memory used up by the mutations
2082
+ syncd.mutations = [];
2083
+ }
2084
+ return { state: newState, mutationMap };
2085
+ };
2086
+ exports.decodePatches = decodePatches;
2087
+ const chatModificationToAppPatch = (mod, jid) => {
2088
+ const OP = index_js_1.proto.SyncdMutation.SyncdOperation;
2089
+ const getMessageRange = (lastMessages) => {
2090
+ let messageRange;
2091
+ if (Array.isArray(lastMessages)) {
2092
+ const lastMsg = lastMessages[lastMessages.length - 1];
2093
+ messageRange = {
2094
+ lastMessageTimestamp: lastMsg?.messageTimestamp,
2095
+ messages: lastMessages?.length
2096
+ ? lastMessages.map(m => {
2097
+ if (!m.key?.id || !m.key?.remoteJid) {
2098
+ throw new index_js_2.HttpError('Incomplete key', { statusCode: 400, data: m });
2099
+ }
2100
+ if ((0, index_js_3.isJidGroup)(m.key.remoteJid) && !m.key.fromMe && !m.key.participant) {
2101
+ throw new index_js_2.HttpError('Expected not from me message to have participant', { statusCode: 400, data: m });
2102
+ }
2103
+ if (!m.messageTimestamp || !(0, generics_js_1.toNumber)(m.messageTimestamp)) {
2104
+ throw new index_js_2.HttpError('Missing timestamp in last message list', { statusCode: 400, data: m });
2105
+ }
2106
+ if (m.key.participant) {
2107
+ m.key.participant = (0, index_js_3.jidNormalizedUser)(m.key.participant);
2108
+ }
2109
+ return m;
2110
+ })
2111
+ : undefined
2112
+ };
2113
+ }
2114
+ else {
2115
+ messageRange = lastMessages;
2116
+ }
2117
+ return messageRange;
2118
+ };
2119
+ let patch;
2120
+ if ('mute' in mod) {
2121
+ patch = {
2122
+ syncAction: {
2123
+ muteAction: {
2124
+ muted: !!mod.mute,
2125
+ muteEndTimestamp: mod.mute || undefined
2126
+ }
2127
+ },
2128
+ index: ['mute', jid],
2129
+ type: 'regular_high',
2130
+ apiVersion: 2,
2131
+ operation: OP.SET
2132
+ };
2133
+ }
2134
+ else if ('archive' in mod) {
2135
+ patch = {
2136
+ syncAction: {
2137
+ archiveChatAction: {
2138
+ archived: !!mod.archive,
2139
+ messageRange: getMessageRange(mod.lastMessages)
2140
+ }
2141
+ },
2142
+ index: ['archive', jid],
2143
+ type: 'regular_low',
2144
+ apiVersion: 3,
2145
+ operation: OP.SET
2146
+ };
2147
+ }
2148
+ else if ('markRead' in mod) {
2149
+ patch = {
2150
+ syncAction: {
2151
+ markChatAsReadAction: {
2152
+ read: mod.markRead,
2153
+ messageRange: getMessageRange(mod.lastMessages)
2154
+ }
2155
+ },
2156
+ index: ['markChatAsRead', jid],
2157
+ type: 'regular_low',
2158
+ apiVersion: 3,
2159
+ operation: OP.SET
2160
+ };
2161
+ }
2162
+ else if ('deleteForMe' in mod) {
2163
+ const { timestamp, key, deleteMedia } = mod.deleteForMe;
2164
+ patch = {
2165
+ syncAction: {
2166
+ deleteMessageForMeAction: {
2167
+ deleteMedia,
2168
+ messageTimestamp: timestamp
2169
+ }
2170
+ },
2171
+ index: ['deleteMessageForMe', jid, key.id, key.fromMe ? '1' : '0', '0'],
2172
+ type: 'regular_high',
2173
+ apiVersion: 3,
2174
+ operation: OP.SET
2175
+ };
2176
+ }
2177
+ else if ('clear' in mod) {
2178
+ patch = {
2179
+ syncAction: {
2180
+ clearChatAction: {
2181
+ messageRange: getMessageRange(mod.lastMessages)
2182
+ }
2183
+ },
2184
+ index: ['clearChat', jid, '1' /*the option here is 0 when keep starred messages is enabled*/, '0'],
2185
+ type: 'regular_high',
2186
+ apiVersion: 6,
2187
+ operation: OP.SET
2188
+ };
2189
+ }
2190
+ else if ('pin' in mod) {
2191
+ patch = {
2192
+ syncAction: {
2193
+ pinAction: {
2194
+ pinned: !!mod.pin
2195
+ }
2196
+ },
2197
+ index: ['pin_v1', jid],
2198
+ type: 'regular_low',
2199
+ apiVersion: 5,
2200
+ operation: OP.SET
2201
+ };
2202
+ }
2203
+ else if ('contact' in mod) {
2204
+ patch = {
2205
+ syncAction: {
2206
+ contactAction: mod.contact || {}
2207
+ },
2208
+ index: ['contact', jid],
2209
+ type: 'critical_unblock_low',
2210
+ apiVersion: 2,
2211
+ operation: mod.contact ? OP.SET : OP.REMOVE
2212
+ };
2213
+ }
2214
+ else if ('disableLinkPreviews' in mod) {
2215
+ patch = {
2216
+ syncAction: {
2217
+ privacySettingDisableLinkPreviewsAction: mod.disableLinkPreviews || {}
2218
+ },
2219
+ index: ['setting_disableLinkPreviews'],
2220
+ type: 'regular',
2221
+ apiVersion: 8,
2222
+ operation: OP.SET
2223
+ };
2224
+ }
2225
+ else if ('star' in mod) {
2226
+ const key = mod.star.messages[0];
2227
+ patch = {
2228
+ syncAction: {
2229
+ starAction: {
2230
+ starred: !!mod.star.star
2231
+ }
2232
+ },
2233
+ index: ['star', jid, key.id, key.fromMe ? '1' : '0', '0'],
2234
+ type: 'regular_low',
2235
+ apiVersion: 2,
2236
+ operation: OP.SET
2237
+ };
2238
+ }
2239
+ else if ('delete' in mod) {
2240
+ patch = {
2241
+ syncAction: {
2242
+ deleteChatAction: {
2243
+ messageRange: getMessageRange(mod.lastMessages)
2244
+ }
2245
+ },
2246
+ index: ['deleteChat', jid, '1'],
2247
+ type: 'regular_high',
2248
+ apiVersion: 6,
2249
+ operation: OP.SET
2250
+ };
2251
+ }
2252
+ else if ('pushNameSetting' in mod) {
2253
+ patch = {
2254
+ syncAction: {
2255
+ pushNameSetting: {
2256
+ name: mod.pushNameSetting
2257
+ }
2258
+ },
2259
+ index: ['setting_pushName'],
2260
+ type: 'critical_block',
2261
+ apiVersion: 1,
2262
+ operation: OP.SET
2263
+ };
2264
+ }
2265
+ else if ('quickReply' in mod) {
2266
+ patch = {
2267
+ syncAction: {
2268
+ quickReplyAction: {
2269
+ count: 0,
2270
+ deleted: mod.quickReply.deleted || false,
2271
+ keywords: [],
2272
+ message: mod.quickReply.message || '',
2273
+ shortcut: mod.quickReply.shortcut || ''
2274
+ }
2275
+ },
2276
+ index: ['quick_reply', mod.quickReply.timestamp || String(Math.floor(Date.now() / 1000))],
2277
+ type: 'regular',
2278
+ apiVersion: 2,
2279
+ operation: OP.SET
2280
+ };
2281
+ }
2282
+ else if ('addLabel' in mod) {
2283
+ patch = {
2284
+ syncAction: {
2285
+ labelEditAction: {
2286
+ name: mod.addLabel.name,
2287
+ color: mod.addLabel.color,
2288
+ predefinedId: mod.addLabel.predefinedId,
2289
+ deleted: mod.addLabel.deleted
2290
+ }
2291
+ },
2292
+ index: ['label_edit', mod.addLabel.id],
2293
+ type: 'regular',
2294
+ apiVersion: 3,
2295
+ operation: OP.SET
2296
+ };
2297
+ }
2298
+ else if ('addChatLabel' in mod) {
2299
+ patch = {
2300
+ syncAction: {
2301
+ labelAssociationAction: {
2302
+ labeled: true
2303
+ }
2304
+ },
2305
+ index: [LabelAssociation_js_1.LabelAssociationType.Chat, mod.addChatLabel.labelId, jid],
2306
+ type: 'regular',
2307
+ apiVersion: 3,
2308
+ operation: OP.SET
2309
+ };
2310
+ }
2311
+ else if ('removeChatLabel' in mod) {
2312
+ patch = {
2313
+ syncAction: {
2314
+ labelAssociationAction: {
2315
+ labeled: false
2316
+ }
2317
+ },
2318
+ index: [LabelAssociation_js_1.LabelAssociationType.Chat, mod.removeChatLabel.labelId, jid],
2319
+ type: 'regular',
2320
+ apiVersion: 3,
2321
+ operation: OP.SET
2322
+ };
2323
+ }
2324
+ else if ('addMessageLabel' in mod) {
2325
+ patch = {
2326
+ syncAction: {
2327
+ labelAssociationAction: {
2328
+ labeled: true
2329
+ }
2330
+ },
2331
+ index: [LabelAssociation_js_1.LabelAssociationType.Message, mod.addMessageLabel.labelId, jid, mod.addMessageLabel.messageId, '0', '0'],
2332
+ type: 'regular',
2333
+ apiVersion: 3,
2334
+ operation: OP.SET
2335
+ };
2336
+ }
2337
+ else if ('removeMessageLabel' in mod) {
2338
+ patch = {
2339
+ syncAction: {
2340
+ labelAssociationAction: {
2341
+ labeled: false
2342
+ }
2343
+ },
2344
+ index: [
2345
+ LabelAssociation_js_1.LabelAssociationType.Message,
2346
+ mod.removeMessageLabel.labelId,
2347
+ jid,
2348
+ mod.removeMessageLabel.messageId,
2349
+ '0',
2350
+ '0'
2351
+ ],
2352
+ type: 'regular',
2353
+ apiVersion: 3,
2354
+ operation: OP.SET
2355
+ };
2356
+ }
2357
+ else {
2358
+ throw new index_js_2.HttpError('not supported');
2359
+ }
2360
+ patch.syncAction.timestamp = Date.now();
2361
+ return patch;
2362
+ };
2363
+ exports.chatModificationToAppPatch = chatModificationToAppPatch;
2364
+ const processSyncAction = (syncAction, ev, me, initialSyncOpts, logger) => {
2365
+ const isInitialSync = !!initialSyncOpts;
2366
+ const accountSettings = initialSyncOpts?.accountSettings;
2367
+ logger?.trace({ syncAction, initialSync: !!initialSyncOpts }, 'processing sync action');
2368
+ const { syncAction: { value: action }, index: [type, id, msgId, fromMe] } = syncAction;
2369
+ if (action?.muteAction) {
2370
+ ev.emit('chats.update', [
2371
+ {
2372
+ id,
2373
+ muteEndTime: action.muteAction?.muted ? (0, generics_js_1.toNumber)(action.muteAction.muteEndTimestamp) : null,
2374
+ conditional: getChatUpdateConditional(id, undefined)
2375
+ }
2376
+ ]);
2377
+ }
2378
+ else if (action?.archiveChatAction || type === 'archive' || type === 'unarchive') {
2379
+ // okay so we've to do some annoying computation here
2380
+ // when we're initially syncing the app state
2381
+ // there are a few cases we need to handle
2382
+ // 1. if the account unarchiveChats setting is true
2383
+ // a. if the chat is archived, and no further messages have been received -- simple, keep archived
2384
+ // b. if the chat was archived, and the user received messages from the other person afterwards
2385
+ // then the chat should be marked unarchved --
2386
+ // we compare the timestamp of latest message from the other person to determine this
2387
+ // 2. if the account unarchiveChats setting is false -- then it doesn't matter,
2388
+ // it'll always take an app state action to mark in unarchived -- which we'll get anyway
2389
+ const archiveAction = action?.archiveChatAction;
2390
+ const isArchived = archiveAction ? archiveAction.archived : type === 'archive';
2391
+ // // basically we don't need to fire an "archive" update if the chat is being marked unarchvied
2392
+ // // this only applies for the initial sync
2393
+ // if(isInitialSync && !isArchived) {
2394
+ // isArchived = false
2395
+ // }
2396
+ const msgRange = !accountSettings?.unarchiveChats ? undefined : archiveAction?.messageRange;
2397
+ // logger?.debug({ chat: id, syncAction }, 'message range archive')
2398
+ ev.emit('chats.update', [
2399
+ {
2400
+ id,
2401
+ archived: isArchived,
2402
+ conditional: getChatUpdateConditional(id, msgRange)
2403
+ }
2404
+ ]);
2405
+ }
2406
+ else if (action?.markChatAsReadAction) {
2407
+ const markReadAction = action.markChatAsReadAction;
2408
+ // basically we don't need to fire an "read" update if the chat is being marked as read
2409
+ // because the chat is read by default
2410
+ // this only applies for the initial sync
2411
+ const isNullUpdate = isInitialSync && markReadAction.read;
2412
+ ev.emit('chats.update', [
2413
+ {
2414
+ id,
2415
+ unreadCount: isNullUpdate ? null : !!markReadAction?.read ? 0 : -1,
2416
+ conditional: getChatUpdateConditional(id, markReadAction?.messageRange)
2417
+ }
2418
+ ]);
2419
+ }
2420
+ else if (action?.deleteMessageForMeAction || type === 'deleteMessageForMe') {
2421
+ ev.emit('messages.delete', {
2422
+ keys: [
2423
+ {
2424
+ remoteJid: id,
2425
+ id: msgId,
2426
+ fromMe: fromMe === '1'
2427
+ }
2428
+ ]
2429
+ });
2430
+ }
2431
+ else if (action?.contactAction) {
2432
+ const results = (0, sync_action_utils_js_1.processContactAction)(action.contactAction, id, logger);
2433
+ (0, sync_action_utils_js_1.emitSyncActionResults)(ev, results);
2434
+ }
2435
+ else if (action?.pushNameSetting) {
2436
+ const name = action?.pushNameSetting?.name;
2437
+ if (name && me?.name !== name) {
2438
+ ev.emit('creds.update', { me: { ...me, name } });
2439
+ }
2440
+ }
2441
+ else if (action?.pinAction) {
2442
+ ev.emit('chats.update', [
2443
+ {
2444
+ id,
2445
+ pinned: action.pinAction?.pinned ? (0, generics_js_1.toNumber)(action.timestamp) : null,
2446
+ conditional: getChatUpdateConditional(id, undefined)
2447
+ }
2448
+ ]);
2449
+ }
2450
+ else if (action?.unarchiveChatsSetting) {
2451
+ const unarchiveChats = !!action.unarchiveChatsSetting.unarchiveChats;
2452
+ ev.emit('creds.update', { accountSettings: { unarchiveChats } });
2453
+ logger?.info(`archive setting updated => '${action.unarchiveChatsSetting.unarchiveChats}'`);
2454
+ if (accountSettings) {
2455
+ accountSettings.unarchiveChats = unarchiveChats;
2456
+ }
2457
+ }
2458
+ else if (action?.starAction || type === 'star') {
2459
+ let starred = action?.starAction?.starred;
2460
+ if (typeof starred !== 'boolean') {
2461
+ starred = syncAction.index[syncAction.index.length - 1] === '1';
2462
+ }
2463
+ ev.emit('messages.update', [
2464
+ {
2465
+ key: { remoteJid: id, id: msgId, fromMe: fromMe === '1' },
2466
+ update: { starred }
2467
+ }
2468
+ ]);
2469
+ }
2470
+ else if (action?.deleteChatAction || type === 'deleteChat') {
2471
+ if (!isInitialSync) {
2472
+ ev.emit('chats.delete', [id]);
2473
+ }
2474
+ }
2475
+ else if (action?.labelEditAction) {
2476
+ const { name, color, deleted, predefinedId } = action.labelEditAction;
2477
+ ev.emit('labels.edit', {
2478
+ id: id,
2479
+ name: name,
2480
+ color: color,
2481
+ deleted: deleted,
2482
+ predefinedId: predefinedId ? String(predefinedId) : undefined
2483
+ });
2484
+ }
2485
+ else if (action?.labelAssociationAction) {
2486
+ ev.emit('labels.association', {
2487
+ type: action.labelAssociationAction.labeled ? 'add' : 'remove',
2488
+ association: type === LabelAssociation_js_1.LabelAssociationType.Chat
2489
+ ? {
2490
+ type: LabelAssociation_js_1.LabelAssociationType.Chat,
2491
+ chatId: syncAction.index[2],
2492
+ labelId: syncAction.index[1]
2493
+ }
2494
+ : {
2495
+ type: LabelAssociation_js_1.LabelAssociationType.Message,
2496
+ chatId: syncAction.index[2],
2497
+ messageId: syncAction.index[3],
2498
+ labelId: syncAction.index[1]
2499
+ }
2500
+ });
2501
+ }
2502
+ else if (action?.localeSetting?.locale) {
2503
+ ev.emit('settings.update', { setting: 'locale', value: action.localeSetting.locale });
2504
+ }
2505
+ else if (action?.timeFormatAction) {
2506
+ ev.emit('settings.update', { setting: 'timeFormat', value: action.timeFormatAction });
2507
+ }
2508
+ else if (action?.pnForLidChatAction) {
2509
+ if (action.pnForLidChatAction.pnJid) {
2510
+ ev.emit('lid-mapping.update', { lid: id, pn: action.pnForLidChatAction.pnJid });
2511
+ }
2512
+ }
2513
+ else if (action?.privacySettingRelayAllCalls) {
2514
+ ev.emit('settings.update', {
2515
+ setting: 'privacySettingRelayAllCalls',
2516
+ value: action.privacySettingRelayAllCalls
2517
+ });
2518
+ }
2519
+ else if (action?.statusPrivacy) {
2520
+ ev.emit('settings.update', { setting: 'statusPrivacy', value: action.statusPrivacy });
2521
+ }
2522
+ else if (action?.lockChatAction) {
2523
+ ev.emit('chats.lock', { id: id, locked: !!action.lockChatAction.locked });
2524
+ }
2525
+ else if (action?.privacySettingDisableLinkPreviewsAction) {
2526
+ ev.emit('settings.update', {
2527
+ setting: 'disableLinkPreviews',
2528
+ value: action.privacySettingDisableLinkPreviewsAction
2529
+ });
2530
+ }
2531
+ else if (action?.notificationActivitySettingAction?.notificationActivitySetting) {
2532
+ ev.emit('settings.update', {
2533
+ setting: 'notificationActivitySetting',
2534
+ value: action.notificationActivitySettingAction.notificationActivitySetting
2535
+ });
2536
+ }
2537
+ else if (action?.lidContactAction) {
2538
+ ev.emit('contacts.upsert', [
2539
+ {
2540
+ id: id,
2541
+ name: action.lidContactAction.fullName ||
2542
+ action.lidContactAction.firstName ||
2543
+ action.lidContactAction.username ||
2544
+ undefined,
2545
+ username: action.lidContactAction.username || undefined,
2546
+ lid: id,
2547
+ phoneNumber: undefined
2548
+ }
2549
+ ]);
2550
+ }
2551
+ else if (action?.privacySettingChannelsPersonalisedRecommendationAction) {
2552
+ ev.emit('settings.update', {
2553
+ setting: 'channelsPersonalisedRecommendation',
2554
+ value: action.privacySettingChannelsPersonalisedRecommendationAction
2555
+ });
2556
+ }
2557
+ else {
2558
+ logger?.debug({ syncAction, id }, 'unprocessable update');
2559
+ }
2560
+ function getChatUpdateConditional(id, msgRange) {
2561
+ return isInitialSync
2562
+ ? data => {
2563
+ const chat = data.historySets.chats[id] || data.chatUpserts[id];
2564
+ if (chat) {
2565
+ return msgRange ? isValidPatchBasedOnMessageRange(chat, msgRange) : true;
2566
+ }
2567
+ }
2568
+ : undefined;
2569
+ }
2570
+ function isValidPatchBasedOnMessageRange(chat, msgRange) {
2571
+ const lastMsgTimestamp = Number(msgRange?.lastMessageTimestamp || msgRange?.lastSystemMessageTimestamp || 0);
2572
+ const chatLastMsgTimestamp = Number(chat?.lastMessageRecvTimestamp || 0);
2573
+ return lastMsgTimestamp >= chatLastMsgTimestamp;
2574
+ }
2575
+ };
2576
+ exports.processSyncAction = processSyncAction;
2577
+
2578
+ return module.exports;
2579
+ })();
2580
+ for (const key of Object.keys(__kageModule164)) {
2581
+ if (key !== '__esModule' && !Object.prototype.hasOwnProperty.call(exports,key)) Object.defineProperty(exports,key,{enumerable:true,configurable:true,get:()=>__kageModule164[key]});
2582
+ }
2583
+ Object.defineProperty(exports, Symbol.for('kage.advanced'), {value:__kageModule164, configurable:true});
2584
+
2585
+ {
2586
+ const previous=exports.chatModificationToAppPatch;
2587
+ exports.chatModificationToAppPatch=function(mod,jid,...rest){
2588
+ if(mod && 'quickReply' in mod){return {syncAction:{quickReplyAction:{count:0,deleted:mod.quickReply.deleted||false,keywords:[],message:mod.quickReply.message||'',shortcut:mod.quickReply.shortcut||''}},index:['quick_reply',mod.quickReply.timestamp||String(Math.floor(Date.now()/1000))],type:'regular',apiVersion:2,operation:0};}
2589
+ return previous(mod,jid,...rest);
2590
+ };
2591
+ }