gifted-baileys 1.5.5 → 1.5.7

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 (251) hide show
  1. package/README.md +6 -1642
  2. package/WAProto/WAProto.proto +969 -88
  3. package/WAProto/index.d.ts +13199 -1260
  4. package/WAProto/index.js +124901 -74525
  5. package/lib/Defaults/baileys-version.json +3 -0
  6. package/lib/Defaults/index.d.ts +284 -0
  7. package/{src → lib}/Defaults/index.js +7 -14
  8. package/lib/Signal/libsignal.d.ts +3 -0
  9. package/lib/Signal/libsignal.js +161 -0
  10. package/lib/Socket/Client/abstract-socket-client.d.ts +15 -0
  11. package/lib/Socket/Client/index.d.ts +2 -0
  12. package/{src → lib}/Socket/Client/index.js +2 -3
  13. package/lib/Socket/Client/mobile-socket-client.d.ts +12 -0
  14. package/lib/Socket/Client/mobile-socket-client.js +65 -0
  15. package/lib/Socket/Client/types.d.ts +17 -0
  16. package/lib/Socket/Client/types.js +13 -0
  17. package/lib/Socket/Client/websocket.d.ts +12 -0
  18. package/lib/Socket/Client/websocket.js +62 -0
  19. package/lib/Socket/business.d.ts +170 -0
  20. package/{src → lib}/Socket/business.js +28 -33
  21. package/lib/Socket/chats.d.ts +81 -0
  22. package/{src → lib}/Socket/chats.js +174 -176
  23. package/lib/Socket/groups.d.ts +115 -0
  24. package/{src → lib}/Socket/groups.js +80 -68
  25. package/lib/Socket/index.d.ts +172 -0
  26. package/{src → lib}/Socket/index.js +4 -1
  27. package/lib/Socket/messages-recv.d.ts +158 -0
  28. package/{src → lib}/Socket/messages-recv.js +378 -211
  29. package/lib/Socket/messages-send.d.ts +155 -0
  30. package/{src → lib}/Socket/messages-send.js +452 -177
  31. package/lib/Socket/newsletter.d.ts +132 -0
  32. package/{src → lib}/Socket/newsletter.js +107 -98
  33. package/lib/Socket/registration.d.ts +264 -0
  34. package/{src → lib}/Socket/registration.js +56 -48
  35. package/lib/Socket/socket.d.ts +44 -0
  36. package/{src → lib}/Socket/socket.js +77 -77
  37. package/lib/Socket/usync.d.ts +37 -0
  38. package/lib/Socket/usync.js +70 -0
  39. package/lib/Store/index.d.ts +3 -0
  40. package/lib/Store/make-cache-manager-store.d.ts +14 -0
  41. package/{src → lib}/Store/make-cache-manager-store.js +25 -34
  42. package/lib/Store/make-in-memory-store.d.ts +118 -0
  43. package/{src → lib}/Store/make-in-memory-store.js +36 -32
  44. package/lib/Store/make-ordered-dictionary.d.ts +13 -0
  45. package/lib/Store/object-repository.d.ts +10 -0
  46. package/{src → lib}/Store/object-repository.js +1 -1
  47. package/lib/Types/Auth.d.ts +109 -0
  48. package/lib/Types/Call.d.ts +13 -0
  49. package/lib/Types/Chat.d.ts +107 -0
  50. package/{src/Types/Contact.ts → lib/Types/Contact.d.ts} +8 -9
  51. package/lib/Types/Events.d.ts +172 -0
  52. package/lib/Types/GroupMetadata.d.ts +56 -0
  53. package/lib/Types/Label.d.ts +46 -0
  54. package/{src/Types/LabelAssociation.ts → lib/Types/LabelAssociation.d.ts} +16 -22
  55. package/lib/Types/Message.d.ts +433 -0
  56. package/lib/Types/Newsletter.d.ts +92 -0
  57. package/lib/Types/Product.d.ts +78 -0
  58. package/lib/Types/Signal.d.ts +57 -0
  59. package/{src/Types/Socket.ts → lib/Types/Socket.d.ts} +61 -68
  60. package/lib/Types/State.d.ts +27 -0
  61. package/lib/Types/USync.d.ts +25 -0
  62. package/lib/Types/index.d.ts +66 -0
  63. package/lib/Utils/auth-utils.d.ts +18 -0
  64. package/{src → lib}/Utils/auth-utils.js +73 -90
  65. package/lib/Utils/baileys-event-stream.d.ts +16 -0
  66. package/lib/Utils/baileys-event-stream.js +63 -0
  67. package/lib/Utils/business.d.ts +22 -0
  68. package/{src → lib}/Utils/business.js +15 -43
  69. package/lib/Utils/chat-utils.d.ts +70 -0
  70. package/{src → lib}/Utils/chat-utils.js +87 -94
  71. package/lib/Utils/crypto.d.ts +40 -0
  72. package/{src → lib}/Utils/crypto.js +4 -2
  73. package/lib/Utils/decode-wa-message.d.ts +36 -0
  74. package/lib/Utils/decode-wa-message.js +226 -0
  75. package/lib/Utils/event-buffer.d.ts +35 -0
  76. package/{src → lib}/Utils/event-buffer.js +4 -13
  77. package/lib/Utils/generics.d.ts +88 -0
  78. package/{src → lib}/Utils/generics.js +67 -86
  79. package/lib/Utils/history.d.ts +19 -0
  80. package/{src → lib}/Utils/history.js +13 -39
  81. package/lib/Utils/index.d.ts +17 -0
  82. package/lib/Utils/link-preview.d.ts +21 -0
  83. package/{src → lib}/Utils/link-preview.js +17 -54
  84. package/lib/Utils/logger.d.ts +2 -0
  85. package/lib/Utils/lt-hash.d.ts +12 -0
  86. package/lib/Utils/make-mutex.d.ts +7 -0
  87. package/{src → lib}/Utils/make-mutex.js +4 -13
  88. package/lib/Utils/messages-media.d.ts +113 -0
  89. package/{src → lib}/Utils/messages-media.js +193 -255
  90. package/lib/Utils/messages.d.ts +77 -0
  91. package/{src → lib}/Utils/messages.js +588 -118
  92. package/lib/Utils/noise-handler.d.ts +20 -0
  93. package/lib/Utils/process-message.d.ts +41 -0
  94. package/{src → lib}/Utils/process-message.js +27 -30
  95. package/lib/Utils/signal.d.ts +33 -0
  96. package/{src → lib}/Utils/signal.js +25 -42
  97. package/lib/Utils/use-multi-file-auth-state.d.ts +12 -0
  98. package/{src → lib}/Utils/use-multi-file-auth-state.js +27 -28
  99. package/lib/Utils/validate-connection.d.ts +11 -0
  100. package/{src → lib}/Utils/validate-connection.js +40 -9
  101. package/lib/WABinary/constants.d.ts +27 -0
  102. package/lib/WABinary/decode.d.ts +6 -0
  103. package/lib/WABinary/encode.d.ts +2 -0
  104. package/{src → lib}/WABinary/encode.js +16 -10
  105. package/lib/WABinary/generic-utils.d.ts +14 -0
  106. package/lib/WABinary/index.d.ts +5 -0
  107. package/lib/WABinary/jid-utils.d.ts +31 -0
  108. package/lib/WABinary/types.d.ts +18 -0
  109. package/lib/WABinary/types.js +2 -0
  110. package/lib/WAM/BinaryInfo.d.ts +8 -0
  111. package/lib/WAM/constants.d.ts +38 -0
  112. package/lib/WAM/encode.d.ts +2 -0
  113. package/lib/WAM/index.d.ts +3 -0
  114. package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +9 -0
  115. package/lib/WAUSync/Protocols/USyncContactProtocol.js +32 -0
  116. package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +22 -0
  117. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +57 -0
  118. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +12 -0
  119. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +30 -0
  120. package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +12 -0
  121. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +42 -0
  122. package/lib/WAUSync/Protocols/index.d.ts +4 -0
  123. package/lib/WAUSync/Protocols/index.js +20 -0
  124. package/lib/WAUSync/USyncQuery.d.ts +26 -0
  125. package/lib/WAUSync/USyncQuery.js +79 -0
  126. package/lib/WAUSync/USyncUser.d.ts +10 -0
  127. package/lib/WAUSync/USyncUser.js +22 -0
  128. package/lib/WAUSync/index.d.ts +3 -0
  129. package/lib/WAUSync/index.js +19 -0
  130. package/{src → lib}/index.js +1 -0
  131. package/package.json +26 -8
  132. package/LICENSE +0 -21
  133. package/src/Defaults/baileys-version.json +0 -3
  134. package/src/Defaults/index.ts +0 -131
  135. package/src/Signal/libsignal.js +0 -180
  136. package/src/Signal/libsignal.ts +0 -141
  137. package/src/Socket/Client/abstract-socket-client.ts +0 -19
  138. package/src/Socket/Client/index.ts +0 -3
  139. package/src/Socket/Client/mobile-socket-client.js +0 -78
  140. package/src/Socket/Client/mobile-socket-client.ts +0 -66
  141. package/src/Socket/Client/web-socket-client.js +0 -75
  142. package/src/Socket/Client/web-socket-client.ts +0 -57
  143. package/src/Socket/business.ts +0 -281
  144. package/src/Socket/chats.ts +0 -1030
  145. package/src/Socket/groups.ts +0 -356
  146. package/src/Socket/index.ts +0 -13
  147. package/src/Socket/messages-recv.ts +0 -985
  148. package/src/Socket/messages-send.ts +0 -871
  149. package/src/Socket/newsletter.ts +0 -282
  150. package/src/Socket/registration.ts +0 -250
  151. package/src/Socket/socket.ts +0 -777
  152. package/src/Store/index.ts +0 -3
  153. package/src/Store/make-cache-manager-store.ts +0 -100
  154. package/src/Store/make-in-memory-store.ts +0 -475
  155. package/src/Store/make-ordered-dictionary.ts +0 -86
  156. package/src/Store/object-repository.ts +0 -32
  157. package/src/Tests/test.app-state-sync.js +0 -204
  158. package/src/Tests/test.app-state-sync.ts +0 -207
  159. package/src/Tests/test.event-buffer.js +0 -270
  160. package/src/Tests/test.event-buffer.ts +0 -319
  161. package/src/Tests/test.key-store.js +0 -76
  162. package/src/Tests/test.key-store.ts +0 -92
  163. package/src/Tests/test.libsignal.js +0 -141
  164. package/src/Tests/test.libsignal.ts +0 -186
  165. package/src/Tests/test.media-download.js +0 -93
  166. package/src/Tests/test.media-download.ts +0 -76
  167. package/src/Tests/test.messages.js +0 -33
  168. package/src/Tests/test.messages.ts +0 -37
  169. package/src/Tests/utils.js +0 -34
  170. package/src/Tests/utils.ts +0 -36
  171. package/src/Types/Auth.ts +0 -113
  172. package/src/Types/Call.ts +0 -15
  173. package/src/Types/Chat.ts +0 -106
  174. package/src/Types/Events.ts +0 -93
  175. package/src/Types/GroupMetadata.ts +0 -53
  176. package/src/Types/Label.ts +0 -36
  177. package/src/Types/Message.ts +0 -288
  178. package/src/Types/Newsletter.ts +0 -98
  179. package/src/Types/Product.ts +0 -85
  180. package/src/Types/Signal.ts +0 -68
  181. package/src/Types/State.ts +0 -29
  182. package/src/Types/index.ts +0 -59
  183. package/src/Utils/auth-utils.ts +0 -222
  184. package/src/Utils/baileys-event-stream.js +0 -92
  185. package/src/Utils/baileys-event-stream.ts +0 -66
  186. package/src/Utils/business.ts +0 -275
  187. package/src/Utils/chat-utils.ts +0 -860
  188. package/src/Utils/crypto.ts +0 -131
  189. package/src/Utils/decode-wa-message.js +0 -211
  190. package/src/Utils/decode-wa-message.ts +0 -228
  191. package/src/Utils/event-buffer.ts +0 -613
  192. package/src/Utils/generics.ts +0 -434
  193. package/src/Utils/history.ts +0 -112
  194. package/src/Utils/index.ts +0 -17
  195. package/src/Utils/link-preview.ts +0 -122
  196. package/src/Utils/logger.ts +0 -3
  197. package/src/Utils/lt-hash.ts +0 -61
  198. package/src/Utils/make-mutex.ts +0 -44
  199. package/src/Utils/messages-media.ts +0 -847
  200. package/src/Utils/messages.ts +0 -956
  201. package/src/Utils/noise-handler.ts +0 -197
  202. package/src/Utils/process-message.ts +0 -414
  203. package/src/Utils/signal.ts +0 -177
  204. package/src/Utils/use-multi-file-auth-state.ts +0 -90
  205. package/src/Utils/validate-connection.ts +0 -238
  206. package/src/WABinary/constants.ts +0 -42
  207. package/src/WABinary/decode.ts +0 -265
  208. package/src/WABinary/encode.ts +0 -236
  209. package/src/WABinary/generic-utils.ts +0 -121
  210. package/src/WABinary/index.ts +0 -5
  211. package/src/WABinary/jid-utils.ts +0 -68
  212. package/src/WABinary/types.ts +0 -17
  213. package/src/WAM/BinaryInfo.ts +0 -12
  214. package/src/WAM/constants.ts +0 -15382
  215. package/src/WAM/encode.ts +0 -174
  216. package/src/WAM/index.ts +0 -3
  217. package/src/gifted +0 -1
  218. package/src/index.ts +0 -13
  219. /package/{src → lib}/Defaults/phonenumber-mcc.json +0 -0
  220. /package/{src → lib}/Socket/Client/abstract-socket-client.js +0 -0
  221. /package/{src → lib}/Store/index.js +0 -0
  222. /package/{src → lib}/Store/make-ordered-dictionary.js +0 -0
  223. /package/{src → lib}/Types/Auth.js +0 -0
  224. /package/{src → lib}/Types/Call.js +0 -0
  225. /package/{src → lib}/Types/Chat.js +0 -0
  226. /package/{src → lib}/Types/Contact.js +0 -0
  227. /package/{src → lib}/Types/Events.js +0 -0
  228. /package/{src → lib}/Types/GroupMetadata.js +0 -0
  229. /package/{src → lib}/Types/Label.js +0 -0
  230. /package/{src → lib}/Types/LabelAssociation.js +0 -0
  231. /package/{src → lib}/Types/Message.js +0 -0
  232. /package/{src → lib}/Types/Newsletter.js +0 -0
  233. /package/{src → lib}/Types/Product.js +0 -0
  234. /package/{src → lib}/Types/Signal.js +0 -0
  235. /package/{src → lib}/Types/Socket.js +0 -0
  236. /package/{src → lib}/Types/State.js +0 -0
  237. /package/{src/WABinary/types.js → lib/Types/USync.js} +0 -0
  238. /package/{src → lib}/Types/index.js +0 -0
  239. /package/{src → lib}/Utils/index.js +0 -0
  240. /package/{src → lib}/Utils/logger.js +0 -0
  241. /package/{src → lib}/Utils/lt-hash.js +0 -0
  242. /package/{src → lib}/Utils/noise-handler.js +0 -0
  243. /package/{src → lib}/WABinary/constants.js +0 -0
  244. /package/{src → lib}/WABinary/decode.js +0 -0
  245. /package/{src → lib}/WABinary/generic-utils.js +0 -0
  246. /package/{src → lib}/WABinary/index.js +0 -0
  247. /package/{src → lib}/WABinary/jid-utils.js +0 -0
  248. /package/{src → lib}/WAM/BinaryInfo.js +0 -0
  249. /package/{src → lib}/WAM/constants.js +0 -0
  250. /package/{src → lib}/WAM/encode.js +0 -0
  251. /package/{src → lib}/WAM/index.js +0 -0
@@ -1,860 +0,0 @@
1
- import { Boom } from '@hapi/boom'
2
- import { AxiosRequestConfig } from 'axios'
3
- import type { Logger } from 'pino'
4
- import { proto } from '../../WAProto'
5
- import { BaileysEventEmitter, Chat, ChatModification, ChatMutation, ChatUpdate, Contact, InitialAppStateSyncOptions, LastMessageList, LTHashState, WAPatchCreate, WAPatchName } from '../Types'
6
- import { ChatLabelAssociation, LabelAssociationType, MessageLabelAssociation } from '../Types/LabelAssociation'
7
- import { BinaryNode, getBinaryNodeChild, getBinaryNodeChildren, isJidGroup, jidNormalizedUser } from '../WABinary'
8
- import { aesDecrypt, aesEncrypt, hkdf, hmacSign } from './crypto'
9
- import { toNumber } from './generics'
10
- import { LT_HASH_ANTI_TAMPERING } from './lt-hash'
11
- import { downloadContentFromMessage, } from './messages-media'
12
-
13
- type FetchAppStateSyncKey = (keyId: string) => Promise<proto.Message.IAppStateSyncKeyData | null | undefined>
14
-
15
- export type ChatMutationMap = { [index: string]: ChatMutation }
16
-
17
- const mutationKeys = (keydata: Uint8Array) => {
18
- const expanded = hkdf(keydata, 160, { info: 'WhatsApp Mutation Keys' })
19
- return {
20
- indexKey: expanded.slice(0, 32),
21
- valueEncryptionKey: expanded.slice(32, 64),
22
- valueMacKey: expanded.slice(64, 96),
23
- snapshotMacKey: expanded.slice(96, 128),
24
- patchMacKey: expanded.slice(128, 160)
25
- }
26
- }
27
-
28
- const generateMac = (operation: proto.SyncdMutation.SyncdOperation, data: Buffer, keyId: Uint8Array | string, key: Buffer) => {
29
- const getKeyData = () => {
30
- let r: number
31
- switch (operation) {
32
- case proto.SyncdMutation.SyncdOperation.SET:
33
- r = 0x01
34
- break
35
- case proto.SyncdMutation.SyncdOperation.REMOVE:
36
- r = 0x02
37
- break
38
- }
39
-
40
- const buff = Buffer.from([r])
41
- return Buffer.concat([buff, Buffer.from(keyId as any, 'base64')])
42
- }
43
-
44
- const keyData = getKeyData()
45
-
46
- const last = Buffer.alloc(8) // 8 bytes
47
- last.set([keyData.length], last.length - 1)
48
-
49
- const total = Buffer.concat([keyData, data, last])
50
- const hmac = hmacSign(total, key, 'sha512')
51
-
52
- return hmac.slice(0, 32)
53
- }
54
-
55
- const to64BitNetworkOrder = (e: number) => {
56
- const buff = Buffer.alloc(8)
57
- buff.writeUint32BE(e, 4)
58
- return buff
59
- }
60
-
61
- type Mac = { indexMac: Uint8Array, valueMac: Uint8Array, operation: proto.SyncdMutation.SyncdOperation }
62
-
63
- const makeLtHashGenerator = ({ indexValueMap, hash }: Pick<LTHashState, 'hash' | 'indexValueMap'>) => {
64
- indexValueMap = { ...indexValueMap }
65
- const addBuffs: ArrayBuffer[] = []
66
- const subBuffs: ArrayBuffer[] = []
67
-
68
- return {
69
- mix: ({ indexMac, valueMac, operation }: Mac) => {
70
- const indexMacBase64 = Buffer.from(indexMac).toString('base64')
71
- const prevOp = indexValueMap[indexMacBase64]
72
- if(operation === proto.SyncdMutation.SyncdOperation.REMOVE) {
73
- if(!prevOp) {
74
- throw new Boom('tried remove, but no previous op', { data: { indexMac, valueMac } })
75
- }
76
-
77
- // remove from index value mac, since this mutation is erased
78
- delete indexValueMap[indexMacBase64]
79
- } else {
80
- addBuffs.push(new Uint8Array(valueMac).buffer)
81
- // add this index into the history map
82
- indexValueMap[indexMacBase64] = { valueMac }
83
- }
84
-
85
- if(prevOp) {
86
- subBuffs.push(new Uint8Array(prevOp.valueMac).buffer)
87
- }
88
- },
89
- finish: () => {
90
- const hashArrayBuffer = new Uint8Array(hash).buffer
91
- const result = LT_HASH_ANTI_TAMPERING.subtractThenAdd(hashArrayBuffer, addBuffs, subBuffs)
92
- const buffer = Buffer.from(result)
93
-
94
- return {
95
- hash: buffer,
96
- indexValueMap
97
- }
98
- }
99
- }
100
- }
101
-
102
- const generateSnapshotMac = (lthash: Uint8Array, version: number, name: WAPatchName, key: Buffer) => {
103
- const total = Buffer.concat([
104
- lthash,
105
- to64BitNetworkOrder(version),
106
- Buffer.from(name, 'utf-8')
107
- ])
108
- return hmacSign(total, key, 'sha256')
109
- }
110
-
111
- const generatePatchMac = (snapshotMac: Uint8Array, valueMacs: Uint8Array[], version: number, type: WAPatchName, key: Buffer) => {
112
- const total = Buffer.concat([
113
- snapshotMac,
114
- ...valueMacs,
115
- to64BitNetworkOrder(version),
116
- Buffer.from(type, 'utf-8')
117
- ])
118
- return hmacSign(total, key)
119
- }
120
-
121
- export const newLTHashState = (): LTHashState => ({ version: 0, hash: Buffer.alloc(128), indexValueMap: {} })
122
-
123
- export const encodeSyncdPatch = async(
124
- { type, index, syncAction, apiVersion, operation }: WAPatchCreate,
125
- myAppStateKeyId: string,
126
- state: LTHashState,
127
- getAppStateSyncKey: FetchAppStateSyncKey
128
- ) => {
129
- const key = !!myAppStateKeyId ? await getAppStateSyncKey(myAppStateKeyId) : undefined
130
- if(!key) {
131
- throw new Boom(`myAppStateKey ("${myAppStateKeyId}") not present`, { statusCode: 404 })
132
- }
133
-
134
- const encKeyId = Buffer.from(myAppStateKeyId, 'base64')
135
-
136
- state = { ...state, indexValueMap: { ...state.indexValueMap } }
137
-
138
- const indexBuffer = Buffer.from(JSON.stringify(index))
139
- const dataProto = proto.SyncActionData.fromObject({
140
- index: indexBuffer,
141
- value: syncAction,
142
- padding: new Uint8Array(0),
143
- version: apiVersion
144
- })
145
- const encoded = proto.SyncActionData.encode(dataProto).finish()
146
-
147
- const keyValue = mutationKeys(key!.keyData!)
148
-
149
- const encValue = aesEncrypt(encoded, keyValue.valueEncryptionKey)
150
- const valueMac = generateMac(operation, encValue, encKeyId, keyValue.valueMacKey)
151
- const indexMac = hmacSign(indexBuffer, keyValue.indexKey)
152
-
153
- // update LT hash
154
- const generator = makeLtHashGenerator(state)
155
- generator.mix({ indexMac, valueMac, operation })
156
- Object.assign(state, generator.finish())
157
-
158
- state.version += 1
159
-
160
- const snapshotMac = generateSnapshotMac(state.hash, state.version, type, keyValue.snapshotMacKey)
161
-
162
- const patch: proto.ISyncdPatch = {
163
- patchMac: generatePatchMac(snapshotMac, [valueMac], state.version, type, keyValue.patchMacKey),
164
- snapshotMac: snapshotMac,
165
- keyId: { id: encKeyId },
166
- mutations: [
167
- {
168
- operation: operation,
169
- record: {
170
- index: {
171
- blob: indexMac
172
- },
173
- value: {
174
- blob: Buffer.concat([encValue, valueMac])
175
- },
176
- keyId: { id: encKeyId }
177
- }
178
- }
179
- ]
180
- }
181
-
182
- const base64Index = indexMac.toString('base64')
183
- state.indexValueMap[base64Index] = { valueMac }
184
-
185
- return { patch, state }
186
- }
187
-
188
- export const decodeSyncdMutations = async(
189
- msgMutations: (proto.ISyncdMutation | proto.ISyncdRecord)[],
190
- initialState: LTHashState,
191
- getAppStateSyncKey: FetchAppStateSyncKey,
192
- onMutation: (mutation: ChatMutation) => void,
193
- validateMacs: boolean
194
- ) => {
195
- const ltGenerator = makeLtHashGenerator(initialState)
196
- // indexKey used to HMAC sign record.index.blob
197
- // valueEncryptionKey used to AES-256-CBC encrypt record.value.blob[0:-32]
198
- // 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
199
- for(const msgMutation of msgMutations!) {
200
- // if it's a syncdmutation, get the operation property
201
- // otherwise, if it's only a record -- it'll be a SET mutation
202
- const operation = 'operation' in msgMutation ? msgMutation.operation : proto.SyncdMutation.SyncdOperation.SET
203
- const record = ('record' in msgMutation && !!msgMutation.record) ? msgMutation.record : msgMutation as proto.ISyncdRecord
204
-
205
- const key = await getKey(record.keyId!.id!)
206
- const content = Buffer.from(record.value!.blob!)
207
- const encContent = content.slice(0, -32)
208
- const ogValueMac = content.slice(-32)
209
- if(validateMacs) {
210
- const contentHmac = generateMac(operation!, encContent, record.keyId!.id!, key.valueMacKey)
211
- if(Buffer.compare(contentHmac, ogValueMac) !== 0) {
212
- throw new Boom('HMAC content verification failed')
213
- }
214
- }
215
-
216
- const result = aesDecrypt(encContent, key.valueEncryptionKey)
217
- const syncAction = proto.SyncActionData.decode(result)
218
-
219
- if(validateMacs) {
220
- const hmac = hmacSign(syncAction.index, key.indexKey)
221
- if(Buffer.compare(hmac, record.index!.blob!) !== 0) {
222
- throw new Boom('HMAC index verification failed')
223
- }
224
- }
225
-
226
- const indexStr = Buffer.from(syncAction.index).toString()
227
- onMutation({ syncAction, index: JSON.parse(indexStr) })
228
-
229
- ltGenerator.mix({
230
- indexMac: record.index!.blob!,
231
- valueMac: ogValueMac,
232
- operation: operation!
233
- })
234
- }
235
-
236
- return ltGenerator.finish()
237
-
238
- async function getKey(keyId: Uint8Array) {
239
- const base64Key = Buffer.from(keyId!).toString('base64')
240
- const keyEnc = await getAppStateSyncKey(base64Key)
241
- if(!keyEnc) {
242
- throw new Boom(`failed to find key "${base64Key}" to decode mutation`, { statusCode: 404, data: { msgMutations } })
243
- }
244
-
245
- return mutationKeys(keyEnc.keyData!)
246
- }
247
- }
248
-
249
- export const decodeSyncdPatch = async(
250
- msg: proto.ISyncdPatch,
251
- name: WAPatchName,
252
- initialState: LTHashState,
253
- getAppStateSyncKey: FetchAppStateSyncKey,
254
- onMutation: (mutation: ChatMutation) => void,
255
- validateMacs: boolean
256
- ) => {
257
- if(validateMacs) {
258
- const base64Key = Buffer.from(msg.keyId!.id!).toString('base64')
259
- const mainKeyObj = await getAppStateSyncKey(base64Key)
260
- if(!mainKeyObj) {
261
- throw new Boom(`failed to find key "${base64Key}" to decode patch`, { statusCode: 404, data: { msg } })
262
- }
263
-
264
- const mainKey = mutationKeys(mainKeyObj.keyData!)
265
- const mutationmacs = msg.mutations!.map(mutation => mutation.record!.value!.blob!.slice(-32))
266
-
267
- const patchMac = generatePatchMac(msg.snapshotMac!, mutationmacs, toNumber(msg.version!.version!), name, mainKey.patchMacKey)
268
- if(Buffer.compare(patchMac, msg.patchMac!) !== 0) {
269
- throw new Boom('Invalid patch mac')
270
- }
271
- }
272
-
273
- const result = await decodeSyncdMutations(msg!.mutations!, initialState, getAppStateSyncKey, onMutation, validateMacs)
274
- return result
275
- }
276
-
277
- export const extractSyncdPatches = async(
278
- result: BinaryNode,
279
- options: AxiosRequestConfig<any>
280
- ) => {
281
- const syncNode = getBinaryNodeChild(result, 'sync')
282
- const collectionNodes = getBinaryNodeChildren(syncNode, 'collection')
283
-
284
- const final = {} as { [T in WAPatchName]: { patches: proto.ISyncdPatch[], hasMorePatches: boolean, snapshot?: proto.ISyncdSnapshot } }
285
- await Promise.all(
286
- collectionNodes.map(
287
- async collectionNode => {
288
- const patchesNode = getBinaryNodeChild(collectionNode, 'patches')
289
-
290
- const patches = getBinaryNodeChildren(patchesNode || collectionNode, 'patch')
291
- const snapshotNode = getBinaryNodeChild(collectionNode, 'snapshot')
292
-
293
- const syncds: proto.ISyncdPatch[] = []
294
- const name = collectionNode.attrs.name as WAPatchName
295
-
296
- const hasMorePatches = collectionNode.attrs.has_more_patches === 'true'
297
-
298
- let snapshot: proto.ISyncdSnapshot | undefined = undefined
299
- if(snapshotNode && !!snapshotNode.content) {
300
- if(!Buffer.isBuffer(snapshotNode)) {
301
- snapshotNode.content = Buffer.from(Object.values(snapshotNode.content))
302
- }
303
-
304
- const blobRef = proto.ExternalBlobReference.decode(
305
- snapshotNode.content! as Buffer
306
- )
307
- const data = await downloadExternalBlob(blobRef, options)
308
- snapshot = proto.SyncdSnapshot.decode(data)
309
- }
310
-
311
- for(let { content } of patches) {
312
- if(content) {
313
- if(!Buffer.isBuffer(content)) {
314
- content = Buffer.from(Object.values(content))
315
- }
316
-
317
- const syncd = proto.SyncdPatch.decode(content! as Uint8Array)
318
- if(!syncd.version) {
319
- syncd.version = { version: +collectionNode.attrs.version + 1 }
320
- }
321
-
322
- syncds.push(syncd)
323
- }
324
- }
325
-
326
- final[name] = { patches: syncds, hasMorePatches, snapshot }
327
- }
328
- )
329
- )
330
-
331
- return final
332
- }
333
-
334
-
335
- export const downloadExternalBlob = async(
336
- blob: proto.IExternalBlobReference,
337
- options: AxiosRequestConfig<any>
338
- ) => {
339
- const stream = await downloadContentFromMessage(blob, 'md-app-state', { options })
340
- const bufferArray: Buffer[] = []
341
- for await (const chunk of stream) {
342
- bufferArray.push(chunk)
343
- }
344
-
345
- return Buffer.concat(bufferArray)
346
- }
347
-
348
- export const downloadExternalPatch = async(
349
- blob: proto.IExternalBlobReference,
350
- options: AxiosRequestConfig<any>
351
- ) => {
352
- const buffer = await downloadExternalBlob(blob, options)
353
- const syncData = proto.SyncdMutations.decode(buffer)
354
- return syncData
355
- }
356
-
357
- export const decodeSyncdSnapshot = async(
358
- name: WAPatchName,
359
- snapshot: proto.ISyncdSnapshot,
360
- getAppStateSyncKey: FetchAppStateSyncKey,
361
- minimumVersionNumber: number | undefined,
362
- validateMacs: boolean = true
363
- ) => {
364
- const newState = newLTHashState()
365
- newState.version = toNumber(snapshot.version!.version!)
366
-
367
- const mutationMap: ChatMutationMap = {}
368
- const areMutationsRequired = typeof minimumVersionNumber === 'undefined'
369
- || newState.version > minimumVersionNumber
370
-
371
- const { hash, indexValueMap } = await decodeSyncdMutations(
372
- snapshot.records!,
373
- newState,
374
- getAppStateSyncKey,
375
- areMutationsRequired
376
- ? (mutation) => {
377
- const index = mutation.syncAction.index?.toString()
378
- mutationMap[index!] = mutation
379
- }
380
- : () => { },
381
- validateMacs
382
- )
383
- newState.hash = hash
384
- newState.indexValueMap = indexValueMap
385
-
386
- if(validateMacs) {
387
- const base64Key = Buffer.from(snapshot.keyId!.id!).toString('base64')
388
- const keyEnc = await getAppStateSyncKey(base64Key)
389
- if(!keyEnc) {
390
- throw new Boom(`failed to find key "${base64Key}" to decode mutation`)
391
- }
392
-
393
- const result = mutationKeys(keyEnc.keyData!)
394
- const computedSnapshotMac = generateSnapshotMac(newState.hash, newState.version, name, result.snapshotMacKey)
395
- if(Buffer.compare(snapshot.mac!, computedSnapshotMac) !== 0) {
396
- throw new Boom(`failed to verify LTHash at ${newState.version} of ${name} from snapshot`)
397
- }
398
- }
399
-
400
- return {
401
- state: newState,
402
- mutationMap
403
- }
404
- }
405
-
406
- export const decodePatches = async(
407
- name: WAPatchName,
408
- syncds: proto.ISyncdPatch[],
409
- initial: LTHashState,
410
- getAppStateSyncKey: FetchAppStateSyncKey,
411
- options: AxiosRequestConfig<any>,
412
- minimumVersionNumber?: number,
413
- logger?: Logger,
414
- validateMacs: boolean = true
415
- ) => {
416
- const newState: LTHashState = {
417
- ...initial,
418
- indexValueMap: { ...initial.indexValueMap }
419
- }
420
-
421
- const mutationMap: ChatMutationMap = {}
422
-
423
- for(let i = 0; i < syncds.length; i++) {
424
- const syncd = syncds[i]
425
- const { version, keyId, snapshotMac } = syncd
426
- if(syncd.externalMutations) {
427
- logger?.trace({ name, version }, 'downloading external patch')
428
- const ref = await downloadExternalPatch(syncd.externalMutations, options)
429
- logger?.debug({ name, version, mutations: ref.mutations.length }, 'downloaded external patch')
430
- syncd.mutations?.push(...ref.mutations)
431
- }
432
-
433
- const patchVersion = toNumber(version!.version!)
434
-
435
- newState.version = patchVersion
436
- const shouldMutate = typeof minimumVersionNumber === 'undefined' || patchVersion > minimumVersionNumber
437
-
438
- const decodeResult = await decodeSyncdPatch(
439
- syncd,
440
- name,
441
- newState,
442
- getAppStateSyncKey,
443
- shouldMutate
444
- ? mutation => {
445
- const index = mutation.syncAction.index?.toString()
446
- mutationMap[index!] = mutation
447
- }
448
- : (() => { }),
449
- true
450
- )
451
-
452
- newState.hash = decodeResult.hash
453
- newState.indexValueMap = decodeResult.indexValueMap
454
-
455
- if(validateMacs) {
456
- const base64Key = Buffer.from(keyId!.id!).toString('base64')
457
- const keyEnc = await getAppStateSyncKey(base64Key)
458
- if(!keyEnc) {
459
- throw new Boom(`failed to find key "${base64Key}" to decode mutation`)
460
- }
461
-
462
- const result = mutationKeys(keyEnc.keyData!)
463
- const computedSnapshotMac = generateSnapshotMac(newState.hash, newState.version, name, result.snapshotMacKey)
464
- if(Buffer.compare(snapshotMac!, computedSnapshotMac) !== 0) {
465
- throw new Boom(`failed to verify LTHash at ${newState.version} of ${name}`)
466
- }
467
- }
468
-
469
- // clear memory used up by the mutations
470
- syncd.mutations = []
471
- }
472
-
473
- return { state: newState, mutationMap }
474
- }
475
-
476
- export const chatModificationToAppPatch = (
477
- mod: ChatModification,
478
- jid: string
479
- ) => {
480
- const OP = proto.SyncdMutation.SyncdOperation
481
- const getMessageRange = (lastMessages: LastMessageList) => {
482
- let messageRange: proto.SyncActionValue.ISyncActionMessageRange
483
- if(Array.isArray(lastMessages)) {
484
- const lastMsg = lastMessages[lastMessages.length - 1]
485
- messageRange = {
486
- lastMessageTimestamp: lastMsg?.messageTimestamp,
487
- messages: lastMessages?.length ? lastMessages.map(
488
- m => {
489
- if(!m.key?.id || !m.key?.remoteJid) {
490
- throw new Boom('Incomplete key', { statusCode: 400, data: m })
491
- }
492
-
493
- if(isJidGroup(m.key.remoteJid) && !m.key.fromMe && !m.key.participant) {
494
- throw new Boom('Expected not from me message to have participant', { statusCode: 400, data: m })
495
- }
496
-
497
- if(!m.messageTimestamp || !toNumber(m.messageTimestamp)) {
498
- throw new Boom('Missing timestamp in last message list', { statusCode: 400, data: m })
499
- }
500
-
501
- if(m.key.participant) {
502
- m.key.participant = jidNormalizedUser(m.key.participant)
503
- }
504
-
505
- return m
506
- }
507
- ) : undefined
508
- }
509
- } else {
510
- messageRange = lastMessages
511
- }
512
-
513
- return messageRange
514
- }
515
-
516
- let patch: WAPatchCreate
517
- if('mute' in mod) {
518
- patch = {
519
- syncAction: {
520
- muteAction: {
521
- muted: !!mod.mute,
522
- muteEndTimestamp: mod.mute || undefined
523
- }
524
- },
525
- index: ['mute', jid],
526
- type: 'regular_high',
527
- apiVersion: 2,
528
- operation: OP.SET
529
- }
530
- } else if('archive' in mod) {
531
- patch = {
532
- syncAction: {
533
- archiveChatAction: {
534
- archived: !!mod.archive,
535
- messageRange: getMessageRange(mod.lastMessages)
536
- }
537
- },
538
- index: ['archive', jid],
539
- type: 'regular_low',
540
- apiVersion: 3,
541
- operation: OP.SET
542
- }
543
- } else if('markRead' in mod) {
544
- patch = {
545
- syncAction: {
546
- markChatAsReadAction: {
547
- read: mod.markRead,
548
- messageRange: getMessageRange(mod.lastMessages)
549
- }
550
- },
551
- index: ['markChatAsRead', jid],
552
- type: 'regular_low',
553
- apiVersion: 3,
554
- operation: OP.SET
555
- }
556
- } else if('clear' in mod) {
557
- if(mod.clear === 'all') {
558
- throw new Boom('not supported')
559
- } else {
560
- const key = mod.clear.messages[0]
561
- patch = {
562
- syncAction: {
563
- deleteMessageForMeAction: {
564
- deleteMedia: false,
565
- messageTimestamp: key.timestamp
566
- }
567
- },
568
- index: ['deleteMessageForMe', jid, key.id, key.fromMe ? '1' : '0', '0'],
569
- type: 'regular_high',
570
- apiVersion: 3,
571
- operation: OP.SET
572
- }
573
- }
574
- } else if('pin' in mod) {
575
- patch = {
576
- syncAction: {
577
- pinAction: {
578
- pinned: !!mod.pin
579
- }
580
- },
581
- index: ['pin_v1', jid],
582
- type: 'regular_low',
583
- apiVersion: 5,
584
- operation: OP.SET
585
- }
586
- } else if('star' in mod) {
587
- const key = mod.star.messages[0]
588
- patch = {
589
- syncAction: {
590
- starAction: {
591
- starred: !!mod.star.star
592
- }
593
- },
594
- index: ['star', jid, key.id, key.fromMe ? '1' : '0', '0'],
595
- type: 'regular_low',
596
- apiVersion: 2,
597
- operation: OP.SET
598
- }
599
- } else if('delete' in mod) {
600
- patch = {
601
- syncAction: {
602
- deleteChatAction: {
603
- messageRange: getMessageRange(mod.lastMessages),
604
- }
605
- },
606
- index: ['deleteChat', jid, '1'],
607
- type: 'regular_high',
608
- apiVersion: 6,
609
- operation: OP.SET
610
- }
611
- } else if('pushNameSetting' in mod) {
612
- patch = {
613
- syncAction: {
614
- pushNameSetting: {
615
- name: mod.pushNameSetting
616
- }
617
- },
618
- index: ['setting_pushName'],
619
- type: 'critical_block',
620
- apiVersion: 1,
621
- operation: OP.SET,
622
- }
623
- } else if('addChatLabel' in mod) {
624
- patch = {
625
- syncAction: {
626
- labelAssociationAction: {
627
- labeled: true,
628
- }
629
- },
630
- index: [LabelAssociationType.Chat, mod.addChatLabel.labelId, jid],
631
- type: 'regular',
632
- apiVersion: 3,
633
- operation: OP.SET,
634
- }
635
- } else if('removeChatLabel' in mod) {
636
- patch = {
637
- syncAction: {
638
- labelAssociationAction: {
639
- labeled: false,
640
- }
641
- },
642
- index: [LabelAssociationType.Chat, mod.removeChatLabel.labelId, jid],
643
- type: 'regular',
644
- apiVersion: 3,
645
- operation: OP.SET,
646
- }
647
- } else if('addMessageLabel' in mod) {
648
- patch = {
649
- syncAction: {
650
- labelAssociationAction: {
651
- labeled: true,
652
- }
653
- },
654
- index: [
655
- LabelAssociationType.Message,
656
- mod.addMessageLabel.labelId,
657
- jid,
658
- mod.addMessageLabel.messageId,
659
- '0',
660
- '0'
661
- ],
662
- type: 'regular',
663
- apiVersion: 3,
664
- operation: OP.SET,
665
- }
666
- } else if('removeMessageLabel' in mod) {
667
- patch = {
668
- syncAction: {
669
- labelAssociationAction: {
670
- labeled: false,
671
- }
672
- },
673
- index: [
674
- LabelAssociationType.Message,
675
- mod.removeMessageLabel.labelId,
676
- jid,
677
- mod.removeMessageLabel.messageId,
678
- '0',
679
- '0'
680
- ],
681
- type: 'regular',
682
- apiVersion: 3,
683
- operation: OP.SET,
684
- }
685
- } else {
686
- throw new Boom('not supported')
687
- }
688
-
689
- patch.syncAction.timestamp = Date.now()
690
-
691
- return patch
692
- }
693
-
694
- export const processSyncAction = (
695
- syncAction: ChatMutation,
696
- ev: BaileysEventEmitter,
697
- me: Contact,
698
- initialSyncOpts?: InitialAppStateSyncOptions,
699
- logger?: Logger,
700
- ) => {
701
- const isInitialSync = !!initialSyncOpts
702
- const accountSettings = initialSyncOpts?.accountSettings
703
-
704
- logger?.trace({ syncAction, initialSync: !!initialSyncOpts }, 'processing sync action')
705
-
706
- const {
707
- syncAction: { value: action },
708
- index: [type, id, msgId, fromMe]
709
- } = syncAction
710
-
711
- if(action?.muteAction) {
712
- ev.emit(
713
- 'chats.update',
714
- [
715
- {
716
- id,
717
- muteEndTime: action.muteAction?.muted
718
- ? toNumber(action.muteAction!.muteEndTimestamp!)
719
- : null,
720
- conditional: getChatUpdateConditional(id, undefined)
721
- }
722
- ]
723
- )
724
- } else if(action?.archiveChatAction || type === 'archive' || type === 'unarchive') {
725
- // okay so we've to do some annoying computation here
726
- // when we're initially syncing the app state
727
- // there are a few cases we need to handle
728
- // 1. if the account unarchiveChats setting is true
729
- // a. if the chat is archived, and no further messages have been received -- simple, keep archived
730
- // b. if the chat was archived, and the user received messages from the other person afterwards
731
- // then the chat should be marked unarchved --
732
- // we compare the timestamp of latest message from the other person to determine this
733
- // 2. if the account unarchiveChats setting is false -- then it doesn't matter,
734
- // it'll always take an app state action to mark in unarchived -- which we'll get anyway
735
- const archiveAction = action?.archiveChatAction
736
- const isArchived = archiveAction
737
- ? archiveAction.archived
738
- : type === 'archive'
739
- // // basically we don't need to fire an "archive" update if the chat is being marked unarchvied
740
- // // this only applies for the initial sync
741
- // if(isInitialSync && !isArchived) {
742
- // isArchived = false
743
- // }
744
-
745
- const msgRange = !accountSettings?.unarchiveChats ? undefined : archiveAction?.messageRange
746
- // logger?.debug({ chat: id, syncAction }, 'message range archive')
747
-
748
- ev.emit('chats.update', [{
749
- id,
750
- archived: isArchived,
751
- conditional: getChatUpdateConditional(id, msgRange)
752
- }])
753
- } else if(action?.markChatAsReadAction) {
754
- const markReadAction = action.markChatAsReadAction
755
- // basically we don't need to fire an "read" update if the chat is being marked as read
756
- // because the chat is read by default
757
- // this only applies for the initial sync
758
- const isNullUpdate = isInitialSync && markReadAction.read
759
-
760
- ev.emit('chats.update', [{
761
- id,
762
- unreadCount: isNullUpdate ? null : !!markReadAction?.read ? 0 : -1,
763
- conditional: getChatUpdateConditional(id, markReadAction?.messageRange)
764
- }])
765
- } else if(action?.deleteMessageForMeAction || type === 'deleteMessageForMe') {
766
- ev.emit('messages.delete', {
767
- keys: [
768
- {
769
- remoteJid: id,
770
- id: msgId,
771
- fromMe: fromMe === '1'
772
- }
773
- ]
774
- })
775
- } else if(action?.contactAction) {
776
- ev.emit('contacts.upsert', [{ id, name: action.contactAction!.fullName! }])
777
- } else if(action?.pushNameSetting) {
778
- const name = action?.pushNameSetting?.name
779
- if(name && me?.name !== name) {
780
- ev.emit('creds.update', { me: { ...me, name } })
781
- }
782
- } else if(action?.pinAction) {
783
- ev.emit('chats.update', [{
784
- id,
785
- pinned: action.pinAction?.pinned ? toNumber(action.timestamp!) : null,
786
- conditional: getChatUpdateConditional(id, undefined)
787
- }])
788
- } else if(action?.unarchiveChatsSetting) {
789
- const unarchiveChats = !!action.unarchiveChatsSetting.unarchiveChats
790
- ev.emit('creds.update', { accountSettings: { unarchiveChats } })
791
-
792
- logger?.info(`archive setting updated => '${action.unarchiveChatsSetting.unarchiveChats}'`)
793
- if(accountSettings) {
794
- accountSettings.unarchiveChats = unarchiveChats
795
- }
796
- } else if(action?.starAction || type === 'star') {
797
- let starred = action?.starAction?.starred
798
- if(typeof starred !== 'boolean') {
799
- starred = syncAction.index[syncAction.index.length - 1] === '1'
800
- }
801
-
802
- ev.emit('messages.update', [
803
- {
804
- key: { remoteJid: id, id: msgId, fromMe: fromMe === '1' },
805
- update: { starred }
806
- }
807
- ])
808
- } else if(action?.deleteChatAction || type === 'deleteChat') {
809
- if(!isInitialSync) {
810
- ev.emit('chats.delete', [id])
811
- }
812
- } else if(action?.labelEditAction) {
813
- const { name, color, deleted, predefinedId } = action.labelEditAction!
814
-
815
- ev.emit('labels.edit', {
816
- id,
817
- name: name!,
818
- color: color!,
819
- deleted: deleted!,
820
- predefinedId: predefinedId ? String(predefinedId) : undefined
821
- })
822
- } else if(action?.labelAssociationAction) {
823
- ev.emit('labels.association', {
824
- type: action.labelAssociationAction.labeled
825
- ? 'add'
826
- : 'remove',
827
- association: type === LabelAssociationType.Chat
828
- ? {
829
- type: LabelAssociationType.Chat,
830
- chatId: syncAction.index[2],
831
- labelId: syncAction.index[1]
832
- } as ChatLabelAssociation
833
- : {
834
- type: LabelAssociationType.Message,
835
- chatId: syncAction.index[2],
836
- messageId: syncAction.index[3],
837
- labelId: syncAction.index[1]
838
- } as MessageLabelAssociation
839
- })
840
- } else {
841
- logger?.debug({ syncAction, id }, 'unprocessable update')
842
- }
843
-
844
- function getChatUpdateConditional(id: string, msgRange: proto.SyncActionValue.ISyncActionMessageRange | null | undefined): ChatUpdate['conditional'] {
845
- return isInitialSync
846
- ? (data) => {
847
- const chat = data.historySets.chats[id] || data.chatUpserts[id]
848
- if(chat) {
849
- return msgRange ? isValidPatchBasedOnMessageRange(chat, msgRange) : true
850
- }
851
- }
852
- : undefined
853
- }
854
-
855
- function isValidPatchBasedOnMessageRange(chat: Chat, msgRange: proto.SyncActionValue.ISyncActionMessageRange | null | undefined) {
856
- const lastMsgTimestamp = Number(msgRange?.lastMessageTimestamp || msgRange?.lastSystemMessageTimestamp || 0)
857
- const chatLastMsgTimestamp = Number(chat?.lastMessageRecvTimestamp || 0)
858
- return lastMsgTimestamp >= chatLastMsgTimestamp
859
- }
860
- }