@mrlegendbot/baileys 1.2.5 → 1.2.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 (212) hide show
  1. package/WAProto/GenerateStatics.sh +3 -0
  2. package/WAProto/fix-imports.js +85 -0
  3. package/WAProto/index.d.ts +14017 -0
  4. package/WAProto/index.js +97691 -2
  5. package/lib/Defaults/index.d.ts +75 -0
  6. package/lib/Defaults/index.js +92 -89
  7. package/lib/Signal/Group/ciphertext-message.d.ts +10 -0
  8. package/lib/Signal/Group/ciphertext-message.js +10 -9
  9. package/lib/Signal/Group/group-session-builder.d.ts +15 -0
  10. package/lib/Signal/Group/group-session-builder.js +22 -21
  11. package/lib/Signal/Group/group_cipher.d.ts +17 -0
  12. package/lib/Signal/Group/group_cipher.js +69 -75
  13. package/lib/Signal/Group/index.d.ts +12 -0
  14. package/lib/Signal/Group/index.js +1 -0
  15. package/lib/Signal/Group/keyhelper.d.ts +11 -0
  16. package/lib/Signal/Group/keyhelper.js +10 -9
  17. package/lib/Signal/Group/sender-chain-key.d.ts +14 -0
  18. package/lib/Signal/Group/sender-chain-key.js +22 -21
  19. package/lib/Signal/Group/sender-key-distribution-message.d.ts +17 -0
  20. package/lib/Signal/Group/sender-key-distribution-message.js +60 -51
  21. package/lib/Signal/Group/sender-key-message.d.ts +19 -0
  22. package/lib/Signal/Group/sender-key-message.js +62 -56
  23. package/lib/Signal/Group/sender-key-name.d.ts +18 -0
  24. package/lib/Signal/Group/sender-key-name.js +40 -38
  25. package/lib/Signal/Group/sender-key-record.d.ts +31 -0
  26. package/lib/Signal/Group/sender-key-record.js +34 -33
  27. package/lib/Signal/Group/sender-key-state.d.ts +39 -0
  28. package/lib/Signal/Group/sender-key-state.js +76 -72
  29. package/lib/Signal/Group/sender-message-key.d.ts +12 -0
  30. package/lib/Signal/Group/sender-message-key.js +23 -22
  31. package/lib/Signal/libsignal.d.ts +5 -0
  32. package/lib/Signal/libsignal.js +405 -506
  33. package/lib/Signal/lid-mapping.d.ts +23 -0
  34. package/lib/Signal/lid-mapping.js +258 -276
  35. package/lib/Socket/Client/index.d.ts +3 -0
  36. package/lib/Socket/Client/index.js +1 -0
  37. package/lib/Socket/Client/types.d.ts +16 -0
  38. package/lib/Socket/Client/types.js +7 -6
  39. package/lib/Socket/Client/websocket.d.ts +13 -0
  40. package/lib/Socket/Client/websocket.js +49 -48
  41. package/lib/Socket/business.d.ts +217 -0
  42. package/lib/Socket/business.js +369 -349
  43. package/lib/Socket/chats.d.ts +125 -0
  44. package/lib/Socket/chats.js +1148 -1172
  45. package/lib/Socket/communities.d.ts +273 -0
  46. package/lib/Socket/communities.js +413 -473
  47. package/lib/Socket/groups.d.ts +161 -0
  48. package/lib/Socket/groups.js +331 -387
  49. package/lib/Socket/index.d.ts +260 -0
  50. package/lib/Socket/index.js +8 -6
  51. package/lib/Socket/messages-recv.d.ts +203 -0
  52. package/lib/Socket/messages-recv.js +1719 -1782
  53. package/lib/Socket/messages-send.d.ts +200 -0
  54. package/lib/Socket/messages-send.js +2508 -1384
  55. package/lib/Socket/mex.d.ts +3 -0
  56. package/lib/Socket/mex.js +35 -40
  57. package/lib/Socket/newsletter.d.ts +170 -0
  58. package/lib/Socket/newsletter.js +191 -239
  59. package/lib/Socket/socket.d.ts +59 -0
  60. package/lib/Socket/socket.js +923 -992
  61. package/lib/Types/Auth.d.ts +117 -0
  62. package/lib/Types/Auth.js +1 -0
  63. package/lib/Types/Bussines.d.ts +25 -0
  64. package/lib/Types/Bussines.js +1 -0
  65. package/lib/Types/Call.d.ts +15 -0
  66. package/lib/Types/Call.js +1 -0
  67. package/lib/Types/Chat.d.ts +124 -0
  68. package/lib/Types/Chat.js +8 -1
  69. package/lib/Types/Contact.d.ts +26 -0
  70. package/lib/Types/Contact.js +1 -0
  71. package/lib/Types/Events.d.ts +256 -0
  72. package/lib/Types/Events.js +2 -1
  73. package/lib/Types/GroupMetadata.d.ts +71 -0
  74. package/lib/Types/GroupMetadata.js +1 -0
  75. package/lib/Types/Label.d.ts +47 -0
  76. package/lib/Types/Label.js +22 -20
  77. package/lib/Types/LabelAssociation.d.ts +30 -0
  78. package/lib/Types/LabelAssociation.js +4 -2
  79. package/lib/Types/Message.d.ts +320 -0
  80. package/lib/Types/Message.js +5 -3
  81. package/lib/Types/Mex.d.ts +141 -0
  82. package/lib/Types/Mex.js +31 -26
  83. package/lib/Types/Product.d.ts +79 -0
  84. package/lib/Types/Product.js +1 -0
  85. package/lib/Types/Signal.d.ts +87 -0
  86. package/lib/Types/Signal.js +2 -1
  87. package/lib/Types/Socket.d.ts +136 -0
  88. package/lib/Types/Socket.js +3 -2
  89. package/lib/Types/State.d.ts +97 -0
  90. package/lib/Types/State.js +52 -4
  91. package/lib/Types/USync.d.ts +26 -0
  92. package/lib/Types/USync.js +1 -0
  93. package/lib/Types/index.d.ts +66 -0
  94. package/lib/Types/index.js +11 -10
  95. package/lib/Utils/auth-utils.d.ts +24 -0
  96. package/lib/Utils/auth-utils.js +277 -240
  97. package/lib/Utils/browser-utils.d.ts +4 -0
  98. package/lib/Utils/browser-utils.js +23 -20
  99. package/lib/Utils/business.d.ts +23 -0
  100. package/lib/Utils/business.js +204 -200
  101. package/lib/Utils/chat-utils.d.ts +100 -0
  102. package/lib/Utils/chat-utils.js +810 -836
  103. package/lib/Utils/companion-reg-client-utils.d.ts +17 -0
  104. package/lib/Utils/companion-reg-client-utils.js +25 -23
  105. package/lib/Utils/crypto.d.ts +37 -0
  106. package/lib/Utils/crypto.js +86 -70
  107. package/lib/Utils/decode-wa-message.d.ts +66 -0
  108. package/lib/Utils/decode-wa-message.js +284 -277
  109. package/lib/Utils/event-buffer.d.ts +36 -0
  110. package/lib/Utils/event-buffer.js +595 -592
  111. package/lib/Utils/generics.d.ts +92 -0
  112. package/lib/Utils/generics.js +335 -312
  113. package/lib/Utils/history.d.ts +24 -0
  114. package/lib/Utils/history.js +115 -123
  115. package/lib/Utils/identity-change-handler.d.ts +44 -0
  116. package/lib/Utils/identity-change-handler.js +50 -0
  117. package/lib/Utils/index.d.ts +22 -0
  118. package/lib/Utils/index.js +2 -1
  119. package/lib/Utils/link-preview.d.ts +21 -0
  120. package/lib/Utils/link-preview.js +73 -74
  121. package/lib/Utils/logger.d.ts +12 -0
  122. package/lib/Utils/logger.js +2 -3
  123. package/lib/Utils/lt-hash.d.ts +8 -0
  124. package/lib/Utils/lt-hash.js +6 -0
  125. package/lib/Utils/make-mutex.d.ts +9 -0
  126. package/lib/Utils/make-mutex.js +27 -27
  127. package/lib/Utils/message-retry-manager.d.ts +115 -0
  128. package/lib/Utils/message-retry-manager.js +254 -213
  129. package/lib/Utils/messages-media.d.ts +133 -0
  130. package/lib/Utils/messages-media.js +715 -741
  131. package/lib/Utils/messages.d.ts +91 -0
  132. package/lib/Utils/messages.js +827 -791
  133. package/lib/Utils/noise-handler.d.ts +20 -0
  134. package/lib/Utils/noise-handler.js +187 -215
  135. package/lib/Utils/offline-node-processor.d.ts +17 -0
  136. package/lib/Utils/offline-node-processor.js +40 -0
  137. package/lib/Utils/pre-key-manager.d.ts +28 -0
  138. package/lib/Utils/pre-key-manager.js +98 -69
  139. package/lib/Utils/process-message.d.ts +60 -0
  140. package/lib/Utils/process-message.js +586 -617
  141. package/lib/Utils/reporting-utils.d.ts +11 -0
  142. package/lib/Utils/reporting-utils.js +234 -553
  143. package/lib/Utils/signal.d.ts +47 -0
  144. package/lib/Utils/signal.js +170 -217
  145. package/lib/Utils/stanza-ack.d.ts +11 -0
  146. package/lib/Utils/stanza-ack.js +35 -27
  147. package/lib/Utils/sync-action-utils.d.ts +19 -0
  148. package/lib/Utils/sync-action-utils.js +41 -37
  149. package/lib/Utils/tc-token-utils.d.ts +37 -0
  150. package/lib/Utils/tc-token-utils.js +141 -120
  151. package/lib/Utils/use-multi-file-auth-state.d.ts +13 -0
  152. package/lib/Utils/use-multi-file-auth-state.js +105 -87
  153. package/lib/Utils/validate-connection.d.ts +11 -0
  154. package/lib/Utils/validate-connection.js +188 -190
  155. package/lib/WABinary/constants.d.ts +28 -0
  156. package/lib/WABinary/constants.js +1294 -28
  157. package/lib/WABinary/decode.d.ts +7 -0
  158. package/lib/WABinary/decode.js +249 -245
  159. package/lib/WABinary/encode.d.ts +3 -0
  160. package/lib/WABinary/encode.js +206 -201
  161. package/lib/WABinary/generic-utils.d.ts +15 -0
  162. package/lib/WABinary/generic-utils.js +89 -86
  163. package/lib/WABinary/index.d.ts +6 -0
  164. package/lib/WABinary/index.js +1 -0
  165. package/lib/WABinary/jid-utils.d.ts +48 -0
  166. package/lib/WABinary/jid-utils.js +77 -68
  167. package/lib/WABinary/types.d.ts +19 -0
  168. package/lib/WABinary/types.js +1 -0
  169. package/lib/WAM/BinaryInfo.d.ts +9 -0
  170. package/lib/WAM/BinaryInfo.js +8 -7
  171. package/lib/WAM/constants.d.ts +40 -0
  172. package/lib/WAM/constants.js +22853 -19892
  173. package/lib/WAM/encode.d.ts +3 -0
  174. package/lib/WAM/encode.js +132 -121
  175. package/lib/WAM/index.d.ts +4 -0
  176. package/lib/WAM/index.js +1 -0
  177. package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +10 -0
  178. package/lib/WAUSync/Protocols/USyncContactProtocol.js +43 -46
  179. package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +23 -0
  180. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +48 -47
  181. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +13 -0
  182. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +23 -22
  183. package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +13 -0
  184. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +33 -30
  185. package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts +10 -0
  186. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +20 -19
  187. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts +26 -0
  188. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +44 -53
  189. package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts +10 -0
  190. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +24 -24
  191. package/lib/WAUSync/Protocols/index.d.ts +6 -0
  192. package/lib/WAUSync/Protocols/index.js +1 -0
  193. package/lib/WAUSync/USyncQuery.d.ts +30 -0
  194. package/lib/WAUSync/USyncQuery.js +88 -79
  195. package/lib/WAUSync/USyncUser.d.ts +17 -0
  196. package/lib/WAUSync/USyncUser.js +29 -28
  197. package/lib/WAUSync/index.d.ts +4 -0
  198. package/lib/WAUSync/index.js +1 -0
  199. package/lib/index.d.ts +12 -0
  200. package/lib/index.js +2 -2
  201. package/package.json +86 -22
  202. package/README.upstream.md +0 -1312
  203. package/WAProto/WAProto.json +0 -1
  204. package/WAProto/WAProto.proto +0 -6275
  205. package/WAProto/compiler.js +0 -550
  206. package/lib/Store/index.js +0 -8
  207. package/lib/Store/keyed-db.js +0 -108
  208. package/lib/Store/make-cache-manager-store.js +0 -70
  209. package/lib/Store/make-in-memory-store.js +0 -417
  210. package/lib/Store/make-ordered-dictionary.js +0 -69
  211. package/lib/Store/object-repository.js +0 -27
  212. package/lib/Utils/use-sqlite-auth-state.js +0 -162
@@ -1,6 +1,6 @@
1
1
  import NodeCache from '@cacheable/node-cache';
2
2
  import { Boom } from '@hapi/boom';
3
- import { proto } from '../../WAProto/compiler.js';
3
+ import { proto } from '../../WAProto/index.js';
4
4
  import { DEFAULT_CACHE_TTLS, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
5
5
  import { aggregateMessageKeysNotFromMe, assertMediaContent, assertMeId, bindWaitForEvent, decryptMediaRetryData, DEF_MEDIA_HOST, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, generateMessageIDV2, generateParticipantHashV2, generateWAMessage, generateWAMessageFromContent, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, MessageRetryManager, normalizeMessageContent, parseAndInjectE2ESessions, unixTimestampSeconds } from '../Utils/index.js';
6
6
  import { getUrlInfo } from '../Utils/link-preview.js';
@@ -10,1299 +10,1342 @@ import { buildMergedTcTokenIndexWrite, isTcTokenExpired, resolveIssuanceJid, res
10
10
  import { areJidsSameUser, getBinaryNodeChild, getBinaryNodeChildren, isHostedLidUser, isHostedPnUser, isJidBot, isJidGroup, isJidMetaAI, isLidUser, isPnUser, jidDecode, jidEncode, jidNormalizedUser, PSA_WID, S_WHATSAPP_NET } from '../WABinary/index.js';
11
11
  import { USyncQuery, USyncUser } from '../WAUSync/index.js';
12
12
  import { makeNewsletterSocket } from './newsletter.js';
13
- import util from 'node:util';
14
- import { createHmac } from 'node:crypto';
15
- export const makeMessagesSocket = config => {
16
- const {
17
- logger,
18
- linkPreviewImageThumbnailWidth,
19
- generateHighQualityLinkPreview,
20
- options: httpRequestOptions,
21
- patchMessageBeforeSending,
22
- cachedGroupMetadata,
23
- enableRecentMessageCache,
24
- maxMsgRetryCount
25
- } = config;
26
- const sock = makeNewsletterSocket(config);
27
- const {
28
- ev,
29
- authState,
30
- messageMutex,
31
- signalRepository,
32
- upsertMessage,
33
- query,
34
- fetchPrivacySettings,
35
- sendNode,
36
- groupMetadata,
37
- groupToggleEphemeral,
38
- registerSocketEndHandler
39
- } = sock;
40
- const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping);
41
- const inFlightTcTokenIssuance = new Set();
42
- const userDevicesCache = config.userDevicesCache || new NodeCache({
43
- stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES,
44
- useClones: false
45
- });
46
- const devicesMutex = makeMutex();
47
- const messageRetryManager = enableRecentMessageCache ? new MessageRetryManager(logger, maxMsgRetryCount) : null;
48
- const encryptionMutex = makeKeyedMutex();
49
- let mediaConn;
50
- let mediaHost = DEF_MEDIA_HOST;
51
- const refreshMediaConn = async (forceGet = false) => {
52
- const media = await mediaConn;
53
- if (!media || forceGet || new Date().getTime() - media.fetchDate.getTime() > media.ttl * 1000) {
54
- mediaConn = (async () => {
55
- const result = await query({
56
- tag: 'iq',
57
- attrs: {
58
- type: 'set',
59
- xmlns: 'w:m',
60
- to: S_WHATSAPP_NET
61
- },
62
- content: [{
63
- tag: 'media_conn',
64
- attrs: {}
65
- }]
13
+
14
+ export const makeMessagesSocket = (config) => {
15
+ const { logger, linkPreviewImageThumbnailWidth, generateHighQualityLinkPreview, options: httpRequestOptions, patchMessageBeforeSending, cachedGroupMetadata, enableRecentMessageCache, maxMsgRetryCount } = config;
16
+ const sock = makeNewsletterSocket(config);
17
+ const { ev, authState, messageMutex, signalRepository, upsertMessage, query, fetchPrivacySettings, sendNode, groupMetadata, groupToggleEphemeral, registerSocketEndHandler } = sock;
18
+ const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping);
19
+
20
+ const inFlightTcTokenIssuance = new Set();
21
+ const userDevicesCache = config.userDevicesCache ||
22
+ new NodeCache({
23
+ stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES,
24
+ useClones: false
66
25
  });
67
- const mediaConnNode = getBinaryNodeChild(result, 'media_conn');
68
- const node = {
69
- hosts: getBinaryNodeChildren(mediaConnNode, 'host').map(({
70
- attrs
71
- }) => ({
72
- hostname: attrs.hostname,
73
- maxContentLengthBytes: +attrs.maxContentLengthBytes
74
- })),
75
- auth: mediaConnNode.attrs.auth,
76
- ttl: +mediaConnNode.attrs.ttl,
77
- fetchDate: new Date()
78
- };
79
- logger.debug('fetched media conn');
80
- if (node.hosts[0]) {
81
- mediaHost = node.hosts[0].hostname;
82
- }
83
- return node;
84
- })();
85
- }
86
- return mediaConn;
87
- };
88
- const sendReceipt = async (jid, participant, messageIds, type) => {
89
- if (!messageIds || messageIds.length === 0) {
90
- throw new Boom('missing ids in receipt');
91
- }
92
- const node = {
93
- tag: 'receipt',
94
- attrs: {
95
- id: messageIds[0]
96
- }
26
+ const devicesMutex = makeMutex();
27
+ const messageRetryManager = enableRecentMessageCache ? new MessageRetryManager(logger, maxMsgRetryCount) : null;
28
+ const encryptionMutex = makeKeyedMutex();
29
+ let mediaConn;
30
+ let mediaHost = DEF_MEDIA_HOST;
31
+
32
+ const refreshMediaConn = async (forceGet = false) => {
33
+ const media = await mediaConn;
34
+ if (!media || forceGet || new Date().getTime() - media.fetchDate.getTime() > media.ttl * 1000) {
35
+ mediaConn = (async () => {
36
+ const result = await query({
37
+ tag: 'iq',
38
+ attrs: {
39
+ type: 'set',
40
+ xmlns: 'w:m',
41
+ to: S_WHATSAPP_NET
42
+ },
43
+ content: [{ tag: 'media_conn', attrs: {} }]
44
+ });
45
+ const mediaConnNode = getBinaryNodeChild(result, 'media_conn');
46
+ const node = {
47
+ hosts: getBinaryNodeChildren(mediaConnNode, 'host').map(({ attrs }) => ({
48
+ hostname: attrs.hostname,
49
+ maxContentLengthBytes: +attrs.maxContentLengthBytes
50
+ })),
51
+ auth: mediaConnNode.attrs.auth,
52
+ ttl: +mediaConnNode.attrs.ttl,
53
+ fetchDate: new Date()
54
+ };
55
+ logger.debug('fetched media conn');
56
+ if (node.hosts[0]) {
57
+ mediaHost = node.hosts[0].hostname;
58
+ }
59
+ return node;
60
+ })();
61
+ }
62
+ return mediaConn;
97
63
  };
98
- const isReadReceipt = type === 'read' || type === 'read-self';
99
- if (isReadReceipt) {
100
- node.attrs.t = unixTimestampSeconds().toString();
101
- }
102
- if (type === 'sender' && (isPnUser(jid) || isLidUser(jid))) {
103
- node.attrs.recipient = jid;
104
- node.attrs.to = participant;
105
- } else {
106
- node.attrs.to = jid;
107
- if (participant) {
108
- node.attrs.participant = participant;
109
- }
110
- }
111
- if (type) {
112
- node.attrs.type = type;
113
- }
114
- const remainingMessageIds = messageIds.slice(1);
115
- if (remainingMessageIds.length) {
116
- node.content = [{
117
- tag: 'list',
118
- attrs: {},
119
- content: remainingMessageIds.map(id => ({
120
- tag: 'item',
121
- attrs: {
122
- id
123
- }
124
- }))
125
- }];
126
- }
127
- logger.debug({
128
- attrs: node.attrs,
129
- messageIds
130
- }, 'sending receipt for messages');
131
- await sendNode(node);
132
- };
133
- const sendReceipts = async (keys, type) => {
134
- const recps = aggregateMessageKeysNotFromMe(keys);
135
- for (const {
136
- jid,
137
- participant,
138
- messageIds
139
- } of recps) {
140
- await sendReceipt(jid, participant, messageIds, type);
141
- }
142
- };
143
- const readMessages = async keys => {
144
- const privacySettings = await fetchPrivacySettings();
145
- const readType = privacySettings.readreceipts === 'all' ? 'read' : 'read-self';
146
- await sendReceipts(keys, readType);
147
- };
148
- const getUSyncDevices = async (jids, useCache, ignoreZeroDevices) => {
149
- const deviceResults = [];
150
- if (!useCache) {
151
- logger.debug('not using cache for devices');
152
- }
153
- const toFetch = [];
154
- const jidsWithUser = jids.map(jid => {
155
- const decoded = jidDecode(jid);
156
- const user = decoded?.user;
157
- const device = decoded?.device;
158
- const isExplicitDevice = typeof device === 'number' && device >= 0;
159
- if (isExplicitDevice && user) {
160
- deviceResults.push({
161
- user,
162
- device,
163
- jid
164
- });
165
- return null;
166
- }
167
- jid = jidNormalizedUser(jid);
168
- return {
169
- jid,
170
- user
171
- };
172
- }).filter(jid => jid !== null);
173
- let mgetDevices;
174
- if (useCache && userDevicesCache.mget) {
175
- const usersToFetch = jidsWithUser.map(j => j?.user).filter(Boolean);
176
- mgetDevices = await userDevicesCache.mget(usersToFetch);
177
- }
178
- for (const {
179
- jid,
180
- user
181
- } of jidsWithUser) {
182
- if (useCache) {
183
- const devices = mgetDevices?.[user] || (userDevicesCache.mget ? undefined : await userDevicesCache.get(user));
184
- if (devices) {
185
- const devicesWithJid = devices.map(d => ({
186
- ...d,
187
- jid: jidEncode(d.user, d.server, d.device)
188
- }));
189
- deviceResults.push(...devicesWithJid);
190
- logger.trace({
191
- user
192
- }, 'using cache for devices');
193
- } else {
194
- toFetch.push(jid);
195
- }
196
- } else {
197
- toFetch.push(jid);
198
- }
199
- }
200
- if (!toFetch.length) {
201
- return deviceResults;
202
- }
203
- const requestedLidUsers = new Set();
204
- for (const jid of toFetch) {
205
- if (isLidUser(jid) || isHostedLidUser(jid)) {
206
- const user = jidDecode(jid)?.user;
207
- if (user) requestedLidUsers.add(user);
208
- }
209
- }
210
- const query = new USyncQuery().withContext('message').withDeviceProtocol().withLIDProtocol();
211
- for (const jid of toFetch) {
212
- query.withUser(new USyncUser().withId(jid));
213
- }
214
- const result = await sock.executeUSyncQuery(query);
215
- if (result) {
216
- const lidResults = result.list.filter(a => !!a.lid);
217
- if (lidResults.length > 0) {
218
- logger.trace('Storing LID maps from device call');
219
- await signalRepository.lidMapping.storeLIDPNMappings(lidResults.map(a => ({
220
- lid: a.lid,
221
- pn: a.id
222
- })));
223
- try {
224
- const lids = lidResults.map(a => a.lid);
225
- if (lids.length) {
226
- await assertSessions(lids, true);
227
- }
228
- } catch (e) {
229
- logger.warn({
230
- e,
231
- count: lidResults.length
232
- }, 'failed to assert sessions for newly mapped LIDs');
64
+
65
+ const sendReceipt = async (jid, participant, messageIds, type) => {
66
+ if (!messageIds || messageIds.length === 0) {
67
+ throw new Boom('missing ids in receipt');
233
68
  }
234
- }
235
- const extracted = extractDeviceJids(result?.list, authState.creds.me.id, authState.creds.me.lid, ignoreZeroDevices);
236
- const deviceMap = {};
237
- for (const item of extracted) {
238
- deviceMap[item.user] = deviceMap[item.user] || [];
239
- deviceMap[item.user]?.push(item);
240
- }
241
- for (const [user, userDevices] of Object.entries(deviceMap)) {
242
- const isLidUser = requestedLidUsers.has(user);
243
- for (const item of userDevices) {
244
- const finalJid = isLidUser ? jidEncode(user, item.server, item.device) : jidEncode(item.user, item.server, item.device);
245
- deviceResults.push({
246
- ...item,
247
- jid: finalJid
248
- });
249
- logger.debug({
250
- user: item.user,
251
- device: item.device,
252
- finalJid,
253
- usedLid: isLidUser
254
- }, 'Processed device with LID priority');
69
+ const node = {
70
+ tag: 'receipt',
71
+ attrs: {
72
+ id: messageIds[0]
73
+ }
74
+ };
75
+ const isReadReceipt = type === 'read' || type === 'read-self';
76
+ if (isReadReceipt) {
77
+ node.attrs.t = unixTimestampSeconds().toString();
255
78
  }
256
- }
257
- await devicesMutex.mutex(async () => {
258
- if (userDevicesCache.mset) {
259
- await userDevicesCache.mset(Object.entries(deviceMap).map(([key, value]) => ({
260
- key,
261
- value
262
- })));
263
- } else {
264
- for (const key in deviceMap) {
265
- if (deviceMap[key]) await userDevicesCache.set(key, deviceMap[key]);
266
- }
79
+ if (type === 'sender' && (isPnUser(jid) || isLidUser(jid))) {
80
+ node.attrs.recipient = jid;
81
+ node.attrs.to = participant;
267
82
  }
268
- });
269
- const userDeviceUpdates = {};
270
- for (const [userId, devices] of Object.entries(deviceMap)) {
271
- if (devices && devices.length > 0) {
272
- userDeviceUpdates[userId] = devices.map(d => d.device?.toString() || '0');
83
+ else {
84
+ node.attrs.to = jid;
85
+ if (participant) {
86
+ node.attrs.participant = participant;
87
+ }
273
88
  }
274
- }
275
- if (Object.keys(userDeviceUpdates).length > 0) {
276
- try {
277
- await authState.keys.set({
278
- 'device-list': userDeviceUpdates
279
- });
280
- logger.debug({
281
- userCount: Object.keys(userDeviceUpdates).length
282
- }, 'stored user device lists for bulk migration');
283
- } catch (error) {
284
- logger.warn({
285
- error
286
- }, 'failed to store user device lists');
89
+ if (type) {
90
+ node.attrs.type = type;
287
91
  }
288
- }
289
- }
290
- return deviceResults;
291
- };
292
- const updateMemberLabel = (jid, memberLabel) => {
293
- return relayMessage(jid, {
294
- protocolMessage: {
295
- type: proto.Message.ProtocolMessage.Type.GROUP_MEMBER_LABEL_CHANGE,
296
- memberLabel: {
297
- label: memberLabel?.slice(0, 30),
298
- labelTimestamp: unixTimestampSeconds()
92
+ const remainingMessageIds = messageIds.slice(1);
93
+ if (remainingMessageIds.length) {
94
+ node.content = [
95
+ {
96
+ tag: 'list',
97
+ attrs: {},
98
+ content: remainingMessageIds.map(id => ({
99
+ tag: 'item',
100
+ attrs: { id }
101
+ }))
102
+ }
103
+ ];
299
104
  }
300
- }
301
- }, {
302
- additionalNodes: [{
303
- tag: 'meta',
304
- attrs: {
305
- tag_reason: 'user_update',
306
- appdata: 'member_tag'
307
- },
308
- content: undefined
309
- }]
310
- });
311
- };
312
- const assertSessions = async (jids, force) => {
313
- let didFetchNewSession = false;
314
- const uniqueJids = [...new Set(jids)];
315
- const jidsRequiringFetch = [];
316
- logger.debug({
317
- jids
318
- }, 'assertSessions call with jids');
319
- for (const jid of uniqueJids) {
320
- if (!force) {
321
- const sessionValidation = await signalRepository.validateSession(jid);
322
- if (sessionValidation.exists) {
323
- continue;
105
+ logger.debug({ attrs: node.attrs, messageIds }, 'sending receipt for messages');
106
+ await sendNode(node);
107
+ };
108
+
109
+ const sendReceipts = async (keys, type) => {
110
+ const recps = aggregateMessageKeysNotFromMe(keys);
111
+ for (const { jid, participant, messageIds } of recps) {
112
+ await sendReceipt(jid, participant, messageIds, type);
324
113
  }
325
- }
326
- jidsRequiringFetch.push(jid);
327
- }
328
- if (jidsRequiringFetch.length) {
329
- const wireJids = [...jidsRequiringFetch.filter(jid => !!isLidUser(jid) || !!isHostedLidUser(jid)), ...((await signalRepository.lidMapping.getLIDsForPNs(jidsRequiringFetch.filter(jid => !!isPnUser(jid) || !!isHostedPnUser(jid)))) || []).map(a => a.lid)];
330
- logger.debug({
331
- jidsRequiringFetch,
332
- wireJids
333
- }, 'fetching sessions');
334
- const result = await query({
335
- tag: 'iq',
336
- attrs: {
337
- xmlns: 'encrypt',
338
- type: 'get',
339
- to: S_WHATSAPP_NET
340
- },
341
- content: [{
342
- tag: 'key',
343
- attrs: {},
344
- content: wireJids.map(jid => {
345
- const attrs = {
346
- jid
347
- };
348
- if (force) attrs.reason = 'identity';
349
- return {
350
- tag: 'user',
351
- attrs
352
- };
353
- })
354
- }]
355
- });
356
- await parseAndInjectE2ESessions(result, signalRepository);
357
- didFetchNewSession = true;
358
- }
359
- return didFetchNewSession;
360
- };
361
- const sendPeerDataOperationMessage = async pdoMessage => {
362
- if (!authState.creds.me?.id) {
363
- throw new Boom('Not authenticated');
364
- }
365
- const protocolMessage = {
366
- protocolMessage: {
367
- peerDataOperationRequestMessage: pdoMessage,
368
- type: proto.Message.ProtocolMessage.Type.PEER_DATA_OPERATION_REQUEST_MESSAGE
369
- }
370
114
  };
371
- const meJid = jidNormalizedUser(authState.creds.me.id);
372
- const msgId = await relayMessage(meJid, protocolMessage, {
373
- additionalAttributes: {
374
- category: 'peer',
375
- push_priority: 'high_force'
376
- },
377
- additionalNodes: [{
378
- tag: 'meta',
379
- attrs: {
380
- appdata: 'default'
381
- }
382
- }]
383
- });
384
- return msgId;
385
- };
386
- const createParticipantNodes = async (recipientJids, message, extraAttrs, dsmMessage) => {
387
- if (!recipientJids.length) {
388
- return {
389
- nodes: [],
390
- shouldIncludeDeviceIdentity: false
391
- };
392
- }
393
- const patched = await patchMessageBeforeSending(message, recipientJids);
394
- const patchedMessages = Array.isArray(patched) ? patched : recipientJids.map(jid => ({
395
- recipientJid: jid,
396
- message: patched
397
- }));
398
- let shouldIncludeDeviceIdentity = false;
399
- const meId = authState.creds.me.id;
400
- const meLid = authState.creds.me?.lid;
401
- const meLidUser = meLid ? jidDecode(meLid)?.user : null;
402
- const encryptionPromises = patchedMessages.map(async ({
403
- recipientJid: jid,
404
- message: patchedMessage
405
- }) => {
406
- try {
407
- if (!jid) return null;
408
- let msgToEncrypt = patchedMessage;
409
- if (dsmMessage) {
410
- const {
411
- user: targetUser
412
- } = jidDecode(jid);
413
- const {
414
- user: ownPnUser
415
- } = jidDecode(meId);
416
- const ownLidUser = meLidUser;
417
- const isOwnUser = targetUser === ownPnUser || ownLidUser && targetUser === ownLidUser;
418
- const isExactSenderDevice = jid === meId || meLid && jid === meLid;
419
- if (isOwnUser && !isExactSenderDevice) {
420
- msgToEncrypt = dsmMessage;
421
- logger.debug({
422
- jid,
423
- targetUser
424
- }, 'Using DSM for own device');
425
- }
115
+
116
+ const readMessages = async (keys) => {
117
+ const privacySettings = await fetchPrivacySettings();
118
+ const readType = privacySettings.readreceipts === 'all' ? 'read' : 'read-self';
119
+ await sendReceipts(keys, readType);
120
+ };
121
+
122
+ const getUSyncDevices = async (jids, useCache, ignoreZeroDevices) => {
123
+ const deviceResults = [];
124
+ if (!useCache) {
125
+ logger.debug('not using cache for devices');
426
126
  }
427
- const bytes = encodeWAMessage(msgToEncrypt);
428
- const mutexKey = jid;
429
- const node = await encryptionMutex.mutex(mutexKey, async () => {
430
- const {
431
- type,
432
- ciphertext
433
- } = await signalRepository.encryptMessage({
434
- jid,
435
- data: bytes
436
- });
437
- if (type === 'pkmsg') {
438
- shouldIncludeDeviceIdentity = true;
439
- }
440
- return {
441
- tag: 'to',
442
- attrs: {
443
- jid
444
- },
445
- content: [{
446
- tag: 'enc',
447
- attrs: {
448
- v: '2',
449
- type,
450
- ...(extraAttrs || {})
451
- },
452
- content: ciphertext
453
- }]
454
- };
455
- });
456
- return node;
457
- } catch (err) {
458
- logger.error({
459
- jid,
460
- err
461
- }, 'Failed to encrypt for recipient');
462
- return null;
463
- }
464
- });
465
- const nodes = (await Promise.all(encryptionPromises)).filter(node => node !== null);
466
- if (recipientJids.length > 0 && nodes.length === 0) {
467
- throw new Boom('All encryptions failed', {
468
- statusCode: 500
469
- });
470
- }
471
- return {
472
- nodes,
473
- shouldIncludeDeviceIdentity
474
- };
475
- };
476
- const relayMessage = async (jid, message, {
477
- messageId: msgId,
478
- participant,
479
- participants: retryParticipants,
480
- isSecret,
481
- protected: protectedSend,
482
- me: meOnly,
483
- additionalAttributes,
484
- additionalNodes,
485
- useUserDevicesCache,
486
- useCachedGroupMetadata,
487
- statusJidList,
488
- cstoken = true
489
- }) => {
490
- const meId = assertMeId(authState.creds);
491
- const meLid = authState.creds.me?.lid;
492
- const isRetryResend = Boolean(retryParticipants?.jid && retryParticipants?.count != null);
493
- const isRecipientOnly = Boolean(participant?.jid);
494
- let shouldIncludeDeviceIdentity = isRetryResend;
495
- const statusJid = 'status@broadcast';
496
- const {
497
- user,
498
- server
499
- } = jidDecode(jid);
500
- const isGroup = server === 'g.us';
501
- const isStatus = jid === statusJid;
502
- const isLid = server === 'lid';
503
- const isNewsletter = server === 'newsletter';
504
- const isGroupOrStatus = isGroup || isStatus;
505
- const finalJid = jid;
506
- msgId = msgId || generateMessageIDV2(meId);
507
- useUserDevicesCache = useUserDevicesCache !== false;
508
- useCachedGroupMetadata = useCachedGroupMetadata !== false && !isStatus;
509
- const participants = [];
510
- const destinationJid = !isStatus ? finalJid : statusJid;
511
- const binaryNodeContent = [];
512
- const devices = [];
513
- let reportingMessage;
514
- const meMsg = {
515
- deviceSentMessage: {
516
- destinationJid,
517
- message
518
- },
519
- messageContextInfo: message.messageContextInfo
520
- };
521
- const extraAttrs = {};
522
- if (isRetryResend) {
523
- if (!isGroup && !isStatus) {
524
- additionalAttributes = {
525
- ...additionalAttributes,
526
- device_fanout: 'false'
527
- };
528
- }
529
- const {
530
- user,
531
- device
532
- } = jidDecode(retryParticipants.jid);
533
- devices.push({
534
- user,
535
- device,
536
- jid: retryParticipants.jid
537
- });
538
- }
539
- if ((isRecipientOnly || isSecret || protectedSend || meOnly) && !isGroup && !isStatus) {
540
- additionalAttributes = {
541
- ...additionalAttributes,
542
- device_fanout: 'false'
543
- };
544
- }
545
- await authState.keys.transaction(async () => {
546
- const mediaType = getMediaType(message);
547
- if (mediaType) {
548
- extraAttrs['mediatype'] = mediaType;
549
- }
550
- if (isNewsletter) {
551
- const patched = patchMessageBeforeSending ? await patchMessageBeforeSending(message, []) : message;
552
- const bytes = encodeNewsletterMessage(patched);
553
- binaryNodeContent.push({
554
- tag: 'plaintext',
555
- attrs: {},
556
- content: bytes
557
- });
558
- const stanza = {
559
- tag: 'message',
560
- attrs: {
561
- to: jid,
562
- id: msgId,
563
- type: getMessageType(message),
564
- ...(additionalAttributes || {})
565
- },
566
- content: binaryNodeContent
567
- };
568
- logger.debug({
569
- msgId
570
- }, `sending newsletter message to ${jid}`);
571
- await sendNode(stanza);
572
- return;
573
- }
574
- if (normalizeMessageContent(message)?.pinInChatMessage || normalizeMessageContent(message)?.reactionMessage) {
575
- extraAttrs['decrypt-fail'] = 'hide';
576
- }
577
- if (isGroupOrStatus && !isRetryResend) {
578
- const [groupData, senderKeyMap] = await Promise.all([(async () => {
579
- let groupData = useCachedGroupMetadata && cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined;
580
- if (groupData && Array.isArray(groupData?.participants)) {
581
- logger.trace({
582
- jid,
583
- participants: groupData.participants.length
584
- }, 'using cached group metadata');
585
- } else if (!isStatus) {
586
- groupData = await groupMetadata(jid);
587
- }
588
- return groupData;
589
- })(), (async () => {
590
- if (!retryParticipants && !isStatus) {
591
- const result = await authState.keys.get('sender-key-memory', [jid]);
592
- return result[jid] || {};
593
- }
594
- return {};
595
- })()]);
596
- const participantsList = groupData ? groupData.participants.map(p => p.id) : [];
597
- if (groupData?.ephemeralDuration && groupData.ephemeralDuration > 0) {
598
- additionalAttributes = {
599
- ...additionalAttributes,
600
- expiration: groupData.ephemeralDuration.toString()
601
- };
127
+ const toFetch = [];
128
+ const jidsWithUser = jids
129
+ .map(jid => {
130
+ const decoded = jidDecode(jid);
131
+ const user = decoded?.user;
132
+ const device = decoded?.device;
133
+ const isExplicitDevice = typeof device === 'number' && device >= 0;
134
+ if (isExplicitDevice && user) {
135
+ deviceResults.push({
136
+ user,
137
+ device,
138
+ jid
139
+ });
140
+ return null;
141
+ }
142
+ jid = jidNormalizedUser(jid);
143
+ return { jid, user };
144
+ })
145
+ .filter(jid => jid !== null);
146
+ let mgetDevices;
147
+ if (useCache && userDevicesCache.mget) {
148
+ const usersToFetch = jidsWithUser.map(j => j?.user).filter(Boolean);
149
+ mgetDevices = await userDevicesCache.mget(usersToFetch);
602
150
  }
603
- if (isStatus && statusJidList) {
604
- participantsList.push(...statusJidList);
151
+ for (const { jid, user } of jidsWithUser) {
152
+ if (useCache) {
153
+ const devices = mgetDevices?.[user] ||
154
+ (userDevicesCache.mget ? undefined : (await userDevicesCache.get(user)));
155
+ if (devices) {
156
+ const devicesWithJid = devices.map(d => ({
157
+ ...d,
158
+ jid: jidEncode(d.user, d.server, d.device)
159
+ }));
160
+ deviceResults.push(...devicesWithJid);
161
+ logger.trace({ user }, 'using cache for devices');
162
+ }
163
+ else {
164
+ toFetch.push(jid);
165
+ }
166
+ }
167
+ else {
168
+ toFetch.push(jid);
169
+ }
605
170
  }
606
- const additionalDevices = await getUSyncDevices(participantsList, !!useUserDevicesCache, false);
607
- devices.push(...additionalDevices);
608
- if (isGroup) {
609
- additionalAttributes = {
610
- ...additionalAttributes,
611
- addressing_mode: groupData?.addressingMode || 'lid'
612
- };
171
+ if (!toFetch.length) {
172
+ return deviceResults;
613
173
  }
614
- const patched = await patchMessageBeforeSending(message);
615
- if (Array.isArray(patched)) {
616
- throw new Boom('Per-jid patching is not supported in groups');
174
+ const requestedLidUsers = new Set();
175
+ for (const jid of toFetch) {
176
+ if (isLidUser(jid) || isHostedLidUser(jid)) {
177
+ const user = jidDecode(jid)?.user;
178
+ if (user)
179
+ requestedLidUsers.add(user);
180
+ }
617
181
  }
618
- const bytes = encodeWAMessage(patched);
619
- reportingMessage = patched;
620
- const groupAddressingMode = additionalAttributes?.['addressing_mode'] || groupData?.addressingMode || 'lid';
621
- const groupSenderIdentity = groupAddressingMode === 'lid' && meLid ? meLid : meId;
622
- const {
623
- ciphertext,
624
- senderKeyDistributionMessage
625
- } = await signalRepository.encryptGroupMessage({
626
- group: destinationJid,
627
- data: bytes,
628
- meId: groupSenderIdentity
629
- });
630
- const senderKeyRecipients = [];
631
- for (const device of devices) {
632
- const deviceJid = device.jid;
633
- const hasKey = !!senderKeyMap[deviceJid];
634
- if ((!hasKey || !!retryParticipants) && !isHostedLidUser(deviceJid) && !isHostedPnUser(deviceJid) && device.device !== 99) {
635
- senderKeyRecipients.push(deviceJid);
636
- senderKeyMap[deviceJid] = true;
637
- }
182
+ const queryObj = new USyncQuery().withContext('message').withDeviceProtocol().withLIDProtocol();
183
+ for (const jid of toFetch) {
184
+ queryObj.withUser(new USyncUser().withId(jid));
638
185
  }
639
- if (senderKeyRecipients.length) {
640
- logger.debug({
641
- senderKeyJids: senderKeyRecipients
642
- }, 'sending new sender key');
643
- const senderKeyMsg = {
644
- senderKeyDistributionMessage: {
645
- axolotlSenderKeyDistributionMessage: senderKeyDistributionMessage,
646
- groupId: destinationJid
186
+ const result = await sock.executeUSyncQuery(queryObj);
187
+ if (result) {
188
+ const lidResults = result.list.filter(a => !!a.lid);
189
+ if (lidResults.length > 0) {
190
+ logger.trace('Storing LID maps from device call');
191
+ await signalRepository.lidMapping.storeLIDPNMappings(lidResults.map(a => ({ lid: a.lid, pn: a.id })));
192
+ try {
193
+ const lids = lidResults.map(a => a.lid);
194
+ if (lids.length) {
195
+ await assertSessions(lids, true);
196
+ }
197
+ }
198
+ catch (e) {
199
+ logger.warn({ e, count: lidResults.length }, 'failed to assert sessions for newly mapped LIDs');
200
+ }
647
201
  }
648
- };
649
- const senderKeySessionTargets = senderKeyRecipients;
650
- await assertSessions(senderKeySessionTargets);
651
- const result = await createParticipantNodes(senderKeyRecipients, senderKeyMsg, extraAttrs);
652
- shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || result.shouldIncludeDeviceIdentity;
653
- participants.push(...result.nodes);
654
- }
655
- binaryNodeContent.push({
656
- tag: 'enc',
657
- attrs: {
658
- v: '2',
659
- type: 'skmsg',
660
- ...extraAttrs
661
- },
662
- content: ciphertext
663
- });
664
- await authState.keys.set({
665
- 'sender-key-memory': {
666
- [jid]: senderKeyMap
667
- }
668
- });
669
- } else {
670
- let ownId = meId;
671
- if (isLid && meLid) {
672
- ownId = meLid;
673
- logger.debug({
674
- to: jid,
675
- ownId
676
- }, 'Using LID identity for @lid conversation');
677
- } else {
678
- logger.debug({
679
- to: jid,
680
- ownId
681
- }, 'Using PN identity for @s.whatsapp.net conversation');
682
- }
683
- const {
684
- user: ownUser
685
- } = jidDecode(ownId);
686
- if (!isRetryResend) {
687
- const patchedForReporting = await patchMessageBeforeSending(message, [jid]);
688
- reportingMessage = Array.isArray(patchedForReporting) ? patchedForReporting.find(item => item.recipientJid === jid) || patchedForReporting[0] : patchedForReporting;
689
- }
690
- if (!isRetryResend) {
691
- const targetUserServer = isLid ? 'lid' : 's.whatsapp.net';
692
- devices.push({
693
- user,
694
- device: 0,
695
- jid: jidEncode(user, targetUserServer, 0)
696
- });
697
- if (user !== ownUser) {
698
- const ownUserServer = isLid ? 'lid' : 's.whatsapp.net';
699
- const ownUserForAddressing = isLid && meLid ? jidDecode(meLid).user : jidDecode(meId).user;
700
- devices.push({
701
- user: ownUserForAddressing,
702
- device: 0,
703
- jid: jidEncode(ownUserForAddressing, ownUserServer, 0)
202
+ const extracted = extractDeviceJids(result?.list, authState.creds.me.id, authState.creds.me.lid, ignoreZeroDevices);
203
+ const deviceMap = {};
204
+ for (const item of extracted) {
205
+ deviceMap[item.user] = deviceMap[item.user] || [];
206
+ deviceMap[item.user]?.push(item);
207
+ }
208
+ for (const [user, userDevices] of Object.entries(deviceMap)) {
209
+ const isLidUser = requestedLidUsers.has(user);
210
+ for (const item of userDevices) {
211
+ const finalJid = isLidUser
212
+ ? jidEncode(user, item.server, item.device)
213
+ : jidEncode(item.user, item.server, item.device);
214
+ deviceResults.push({
215
+ ...item,
216
+ jid: finalJid
217
+ });
218
+ logger.debug({
219
+ user: item.user,
220
+ device: item.device,
221
+ finalJid,
222
+ usedLid: isLidUser
223
+ }, 'Processed device with LID priority');
224
+ }
225
+ }
226
+ await devicesMutex.mutex(async () => {
227
+ if (userDevicesCache.mset) {
228
+ await userDevicesCache.mset(Object.entries(deviceMap).map(([key, value]) => ({ key, value })));
229
+ }
230
+ else {
231
+ for (const key in deviceMap) {
232
+ if (deviceMap[key])
233
+ await userDevicesCache.set(key, deviceMap[key]);
234
+ }
235
+ }
704
236
  });
705
- }
706
- if (additionalAttributes?.['category'] !== 'peer') {
707
- devices.length = 0;
708
- const senderIdentity = isLid && meLid ? jidEncode(jidDecode(meLid)?.user, 'lid', undefined) : jidEncode(jidDecode(meId)?.user, 's.whatsapp.net', undefined);
709
- const sessionDevices = await getUSyncDevices([senderIdentity, jid], true, false);
710
- devices.push(...sessionDevices);
711
- logger.debug({
712
- deviceCount: devices.length,
713
- devices: devices.map(d => `${d.user}:${d.device}@${jidDecode(d.jid)?.server}`)
714
- }, 'Device enumeration complete with unified addressing');
715
- }
716
- }
717
- const allRecipients = [];
718
- const meRecipients = [];
719
- const otherRecipients = [];
720
- const {
721
- user: mePnUser
722
- } = jidDecode(meId);
723
- const {
724
- user: meLidUser
725
- } = meLid ? jidDecode(meLid) : {
726
- user: null
727
- };
728
- for (const {
729
- user,
730
- jid
731
- } of devices) {
732
- const isExactSenderDevice = jid === meId || meLid && jid === meLid;
733
- if (isExactSenderDevice) continue;
734
- const isMe = user === mePnUser || user === meLidUser;
735
- const hasDevice = jid.includes(':');
736
- if (isRecipientOnly && isMe) continue;
737
- if (isSecret && !(!isMe && !hasDevice)) continue;
738
- if (protectedSend && !isMe && hasDevice) continue;
739
- if (meOnly && !isMe) continue;
740
- if (isMe) meRecipients.push(jid);else otherRecipients.push(jid);
741
- allRecipients.push(jid);
742
- }
743
- await assertSessions(allRecipients);
744
- const [{
745
- nodes: meNodes,
746
- shouldIncludeDeviceIdentity: s1
747
- }, {
748
- nodes: otherNodes,
749
- shouldIncludeDeviceIdentity: s2
750
- }] = await Promise.all([createParticipantNodes(meRecipients, meMsg || message, extraAttrs), createParticipantNodes(otherRecipients, message, extraAttrs, meMsg)]);
751
- participants.push(...meNodes);
752
- participants.push(...otherNodes);
753
- if (meRecipients.length > 0 || otherRecipients.length > 0) {
754
- extraAttrs['phash'] = generateParticipantHashV2([...meRecipients, ...otherRecipients]);
755
- }
756
- shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || s1 || s2;
757
- }
758
- if (isRetryResend) {
759
- const isParticipantLid = isLidUser(retryParticipants.jid);
760
- const isMe = areJidsSameUser(retryParticipants.jid, isParticipantLid ? meLid : meId);
761
- let messageToSend = message;
762
- if (isGroupOrStatus) {
763
- let groupSenderIdentity;
764
- if (meLid && (await signalRepository.hasSenderKey({
765
- group: destinationJid,
766
- meId: meLid
767
- }))) {
768
- groupSenderIdentity = meLid;
769
- } else if (await signalRepository.hasSenderKey({
770
- group: destinationJid,
771
- meId
772
- })) {
773
- groupSenderIdentity = meId;
774
- }
775
- if (groupSenderIdentity) {
776
- try {
777
- const skdm = await signalRepository.getSenderKeyDistributionMessage({
778
- group: destinationJid,
779
- meId: groupSenderIdentity
780
- });
781
- messageToSend = {
782
- ...message,
783
- senderKeyDistributionMessage: {
784
- groupId: destinationJid,
785
- axolotlSenderKeyDistributionMessage: skdm
786
- }
787
- };
788
- } catch (err) {
789
- logger.warn({
790
- err,
791
- jid: destinationJid
792
- }, 'failed to build SKDM for retry, sending without it');
237
+ const userDeviceUpdates = {};
238
+ for (const [userId, devices] of Object.entries(deviceMap)) {
239
+ if (devices && devices.length > 0) {
240
+ userDeviceUpdates[userId] = devices.map(d => d.device?.toString() || '0');
241
+ }
242
+ }
243
+ if (Object.keys(userDeviceUpdates).length > 0) {
244
+ try {
245
+ await authState.keys.set({ 'device-list': userDeviceUpdates });
246
+ logger.debug({ userCount: Object.keys(userDeviceUpdates).length }, 'stored user device lists for bulk migration');
247
+ }
248
+ catch (error) {
249
+ logger.warn({ error }, 'failed to store user device lists');
250
+ }
793
251
  }
794
- }
795
252
  }
796
- const encodedMessageToSend = isMe ? encodeWAMessage({
797
- deviceSentMessage: {
798
- destinationJid,
799
- message: messageToSend
800
- }
801
- }) : encodeWAMessage(messageToSend);
802
- const {
803
- type,
804
- ciphertext: encryptedContent
805
- } = await signalRepository.encryptMessage({
806
- data: encodedMessageToSend,
807
- jid: retryParticipants.jid
808
- });
809
- binaryNodeContent.push({
810
- tag: 'enc',
811
- attrs: {
812
- v: '2',
813
- type,
814
- count: retryParticipants.count.toString()
815
- },
816
- content: encryptedContent
253
+ return deviceResults;
254
+ };
255
+
256
+ const updateMemberLabel = (jid, memberLabel) => {
257
+ return relayMessage(jid, {
258
+ protocolMessage: {
259
+ type: proto.Message.ProtocolMessage.Type.GROUP_MEMBER_LABEL_CHANGE,
260
+ memberLabel: {
261
+ label: memberLabel?.slice(0, 30),
262
+ labelTimestamp: unixTimestampSeconds()
263
+ }
264
+ }
265
+ }, {
266
+ additionalNodes: [
267
+ {
268
+ tag: 'meta',
269
+ attrs: {
270
+ tag_reason: 'user_update',
271
+ appdata: 'member_tag'
272
+ },
273
+ content: undefined
274
+ }
275
+ ]
817
276
  });
818
- }
819
- if (participants.length) {
820
- if (additionalAttributes?.['category'] === 'peer') {
821
- const peerNode = participants[0]?.content?.[0];
822
- if (peerNode) {
823
- binaryNodeContent.push(peerNode);
824
- }
825
- } else {
826
- binaryNodeContent.push({
827
- tag: 'participants',
828
- attrs: {},
829
- content: participants
830
- });
277
+ };
278
+
279
+ const assertSessions = async (jids, force) => {
280
+ let didFetchNewSession = false;
281
+ const uniqueJids = [...new Set(jids)];
282
+ const jidsRequiringFetch = [];
283
+ logger.debug({ jids }, 'assertSessions call with jids');
284
+ for (const jid of uniqueJids) {
285
+ if (!force) {
286
+ const sessionValidation = await signalRepository.validateSession(jid);
287
+ if (sessionValidation.exists) {
288
+ continue;
289
+ }
290
+ }
291
+ jidsRequiringFetch.push(jid);
831
292
  }
832
- }
833
- const stanza = {
834
- tag: 'message',
835
- attrs: {
836
- id: msgId,
837
- to: destinationJid,
838
- type: getMessageType(message),
839
- ...(additionalAttributes || {})
840
- },
841
- content: binaryNodeContent
842
- };
843
- if (isRetryResend) {
844
- if (isJidGroup(destinationJid)) {
845
- stanza.attrs.to = destinationJid;
846
- stanza.attrs.participant = retryParticipants.jid;
847
- } else if (areJidsSameUser(retryParticipants.jid, meId)) {
848
- stanza.attrs.to = retryParticipants.jid;
849
- stanza.attrs.recipient = destinationJid;
850
- } else {
851
- stanza.attrs.to = retryParticipants.jid;
852
- }
853
- } else {
854
- stanza.attrs.to = destinationJid;
855
- }
856
- if (shouldIncludeDeviceIdentity) {
857
- ;
858
- stanza.content.push({
859
- tag: 'device-identity',
860
- attrs: {},
861
- content: encodeSignedDeviceIdentity(authState.creds.account, true)
862
- });
863
- logger.debug({
864
- jid
865
- }, 'adding device identity');
866
- }
867
- if (!isNewsletter && !isRetryResend && reportingMessage?.messageContextInfo?.messageSecret && shouldIncludeReportingToken(reportingMessage)) {
868
- try {
869
- const encoded = encodeWAMessage(reportingMessage);
870
- const reportingKey = {
871
- id: msgId,
872
- fromMe: true,
873
- remoteJid: destinationJid,
874
- participant: retryParticipants?.jid
875
- };
876
- const reportingNode = await getMessageReportingToken(encoded, reportingMessage, reportingKey);
877
- if (reportingNode) {
878
- ;
879
- stanza.content.push(reportingNode);
880
- logger.trace({
881
- jid
882
- }, 'added reporting token to message');
883
- }
884
- } catch (error) {
885
- logger.warn({
886
- jid,
887
- trace: error?.stack
888
- }, 'failed to attach reporting token');
293
+ if (jidsRequiringFetch.length) {
294
+ const wireJids = [
295
+ ...jidsRequiringFetch.filter(jid => !!isLidUser(jid) || !!isHostedLidUser(jid)),
296
+ ...((await signalRepository.lidMapping.getLIDsForPNs(jidsRequiringFetch.filter(jid => !!isPnUser(jid) || !!isHostedPnUser(jid)))) || []).map(a => a.lid)
297
+ ];
298
+ logger.debug({ jidsRequiringFetch, wireJids }, 'fetching sessions');
299
+ const result = await query({
300
+ tag: 'iq',
301
+ attrs: {
302
+ xmlns: 'encrypt',
303
+ type: 'get',
304
+ to: S_WHATSAPP_NET
305
+ },
306
+ content: [
307
+ {
308
+ tag: 'key',
309
+ attrs: {},
310
+ content: wireJids.map(jid => {
311
+ const attrs = { jid };
312
+ if (force)
313
+ attrs.reason = 'identity';
314
+ return { tag: 'user', attrs };
315
+ })
316
+ }
317
+ ]
318
+ });
319
+ await parseAndInjectE2ESessions(result, signalRepository);
320
+ didFetchNewSession = true;
889
321
  }
890
- }
891
- const isPeerMessage = additionalAttributes?.['category'] === 'peer';
892
- const is1on1Send = !isGroup && !isRetryResend && !isStatus && !isNewsletter && !isPeerMessage;
893
- const tcTokenJid = is1on1Send ? await resolveTcTokenJid(destinationJid, getLIDForPN) : destinationJid;
894
- const contactTcTokenData = is1on1Send ? await authState.keys.get('tctoken', [tcTokenJid]) : {};
895
- const existingTokenEntry = contactTcTokenData[tcTokenJid];
896
- let tcTokenBuffer = existingTokenEntry?.token;
897
- if (tcTokenBuffer?.length && isTcTokenExpired(existingTokenEntry?.timestamp)) {
898
- logger.debug({
899
- jid: destinationJid,
900
- timestamp: existingTokenEntry?.timestamp
901
- }, 'tctoken expired, clearing');
902
- tcTokenBuffer = undefined;
903
- const cleared = existingTokenEntry?.senderTimestamp !== undefined ? {
904
- token: Buffer.alloc(0),
905
- senderTimestamp: existingTokenEntry.senderTimestamp
906
- } : null;
907
- try {
908
- await authState.keys.set({
909
- tctoken: {
910
- [tcTokenJid]: cleared
911
- }
912
- });
913
- } catch (err) {
914
- logger.debug({
915
- jid: destinationJid,
916
- err: err?.message
917
- }, 'failed to persist tctoken expiry cleanup');
322
+ return didFetchNewSession;
323
+ };
324
+
325
+ const sendPeerDataOperationMessage = async (pdoMessage) => {
326
+ if (!authState.creds.me?.id) {
327
+ throw new Boom('Not authenticated');
918
328
  }
919
- }
920
- if (tcTokenBuffer?.length && sock.serverProps.privacyTokenOn1to1) {
921
- ;
922
- stanza.content.push({
923
- tag: 'tctoken',
924
- attrs: {},
925
- content: tcTokenBuffer
926
- });
927
- }
928
- if (cstoken && is1on1Send && !tcTokenBuffer?.length) {
929
- try {
930
- const lidJid = await resolveTcTokenJid(destinationJid, getLIDForPN);
931
- if (isLidUser(lidJid)) {
932
- const saltRes = await authState.keys.get('nct-salt', ['default']);
933
- const nctSalt = saltRes?.default;
934
- if (nctSalt?.length) {
935
- const cs = createHmac('sha256', Buffer.from(nctSalt)).update(Buffer.from(lidJid, 'utf8')).digest();
936
- stanza.content.push({
937
- tag: 'cstoken',
938
- attrs: {},
939
- content: cs
940
- });
941
- logger.debug({
942
- jid: destinationJid,
943
- lid: lidJid
944
- }, 'attached cstoken (nct fallback)');
945
- } else {
946
- logger.debug({
947
- jid: destinationJid
948
- }, 'cstoken requested but no nct salt stored yet');
329
+ const protocolMessage = {
330
+ protocolMessage: {
331
+ peerDataOperationRequestMessage: pdoMessage,
332
+ type: proto.Message.ProtocolMessage.Type.PEER_DATA_OPERATION_REQUEST_MESSAGE
949
333
  }
950
- } else {
951
- logger.debug({
952
- jid: destinationJid
953
- }, 'cstoken requested but recipient has no LID');
954
- }
955
- } catch (err) {
956
- logger.debug({
957
- jid: destinationJid,
958
- err: err?.message
959
- }, 'cstoken attach failed');
960
- }
961
- }
962
- if (additionalNodes && additionalNodes.length > 0) {
963
- ;
964
- stanza.content.push(...additionalNodes);
965
- }
966
- let inner = message;
967
- let hasWrapper = false;
968
- let hasSpecial = false;
969
- while (inner && typeof inner === "object") {
970
- const entry = Object.entries(inner).find(([, v]) => v && typeof v === "object" && (v.message || v.templateMessage || v.buttonsMessage || v.interactiveMessage));
971
- if (!entry) break;
972
- const [, value] = entry;
973
- if (value.message) {
974
- hasWrapper = true;
975
- }
976
- if (value.templateMessage || value.buttonsMessage || value.interactiveMessage) {
977
- hasSpecial = true;
978
- }
979
- inner = value.message || value.templateMessage || value.buttonsMessage || value.interactiveMessage;
980
- }
981
- if (hasWrapper || hasSpecial) {
982
- stanza.content.push({
983
- tag: 'biz',
984
- attrs: {},
985
- content: [{
986
- tag: 'interactive',
987
- attrs: {
988
- type: 'native_flow',
989
- v: '1'
334
+ };
335
+ const meJid = jidNormalizedUser(authState.creds.me.id);
336
+ const msgId = await relayMessage(meJid, protocolMessage, {
337
+ additionalAttributes: {
338
+ category: 'peer',
339
+ push_priority: 'high_force'
990
340
  },
991
- content: [{
992
- tag: 'native_flow',
993
- attrs: {
994
- name: 'quick_reply'
995
- }
996
- }]
997
- }]
998
- });
999
- }
1000
- const buttonType = getButtonType(message);
1001
- if (buttonType) {
1002
- stanza.content.push({
1003
- tag: 'biz',
1004
- attrs: {},
1005
- content: [{
1006
- tag: buttonType,
1007
- attrs: getButtonArgs(message)
1008
- }]
1009
- });
1010
- logger.debug({
1011
- jid
1012
- }, 'adding business node');
1013
- }
1014
- logger.debug({
1015
- msgId
1016
- }, `sending message to ${participants.length} devices`);
1017
- await sendNode(stanza);
1018
- const isProtocolMsg = !!normalizeMessageContent(message)?.protocolMessage;
1019
- const isBotOrPSA = destinationJid === PSA_WID || isJidBot(destinationJid) || isJidMetaAI(destinationJid);
1020
- if (is1on1Send && !isProtocolMsg && !isBotOrPSA && shouldSendNewTcToken(existingTokenEntry?.senderTimestamp) && !inFlightTcTokenIssuance.has(tcTokenJid)) {
1021
- inFlightTcTokenIssuance.add(tcTokenJid);
1022
- const issueTimestamp = unixTimestampSeconds();
1023
- const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping);
1024
- resolveIssuanceJid(destinationJid, sock.serverProps.lidTrustedTokenIssueToLid, getLIDForPN, getPNForLID).then(issueJid => issuePrivacyTokens([issueJid], issueTimestamp)).then(async result => {
1025
- await storeTcTokensFromIqResult({
1026
- result,
1027
- fallbackJid: tcTokenJid,
1028
- keys: authState.keys,
1029
- getLIDForPN
1030
- });
1031
- const currentData = await authState.keys.get('tctoken', [tcTokenJid]);
1032
- const currentEntry = currentData[tcTokenJid];
1033
- const indexWrite = await buildMergedTcTokenIndexWrite(authState.keys, [tcTokenJid]);
1034
- await authState.keys.set({
1035
- tctoken: {
1036
- [tcTokenJid]: {
1037
- token: Buffer.alloc(0),
1038
- ...currentEntry,
1039
- senderTimestamp: issueTimestamp
1040
- },
1041
- ...indexWrite
1042
- }
1043
- });
1044
- }).catch(err => {
1045
- logger.debug({
1046
- jid: destinationJid,
1047
- err: err?.message
1048
- }, 'fire-and-forget tctoken issuance failed');
1049
- }).finally(() => {
1050
- inFlightTcTokenIssuance.delete(tcTokenJid);
341
+ additionalNodes: [
342
+ {
343
+ tag: 'meta',
344
+ attrs: { appdata: 'default' }
345
+ }
346
+ ]
1051
347
  });
1052
- }
1053
- if (messageRetryManager && !retryParticipants) {
1054
- messageRetryManager.addRecentMessage(destinationJid, msgId, message);
1055
- }
1056
- }, meId);
1057
- return msgId;
1058
- };
1059
- const getMessageType = message => {
1060
- const normalizedMessage = normalizeMessageContent(message);
1061
- if (!normalizedMessage) return 'text';
1062
- if (normalizedMessage.reactionMessage || normalizedMessage.encReactionMessage) {
1063
- return 'reaction';
1064
- }
1065
- if (normalizedMessage.pollCreationMessage || normalizedMessage.pollCreationMessageV2 || normalizedMessage.pollCreationMessageV3 || normalizedMessage.pollUpdateMessage) {
1066
- return 'poll';
1067
- }
1068
- if (normalizedMessage.eventMessage) {
1069
- return 'event';
1070
- }
1071
- if (getMediaType(normalizedMessage) !== '') {
1072
- return 'media';
1073
- }
1074
- return 'text';
1075
- };
1076
- const getMediaType = message => {
1077
- if (message.imageMessage) {
1078
- return 'image';
1079
- } else if (message.videoMessage) {
1080
- return message.videoMessage.gifPlayback ? 'gif' : 'video';
1081
- } else if (message.audioMessage) {
1082
- return message.audioMessage.ptt ? 'ptt' : 'audio';
1083
- } else if (message.contactMessage) {
1084
- return 'vcard';
1085
- } else if (message.documentMessage) {
1086
- return 'document';
1087
- } else if (message.contactsArrayMessage) {
1088
- return 'contact_array';
1089
- } else if (message.liveLocationMessage) {
1090
- return 'livelocation';
1091
- } else if (message.stickerMessage) {
1092
- return 'sticker';
1093
- } else if (message.listMessage) {
1094
- return 'list';
1095
- } else if (message.listResponseMessage) {
1096
- return 'list_response';
1097
- } else if (message.buttonsResponseMessage) {
1098
- return 'buttons_response';
1099
- } else if (message.orderMessage) {
1100
- return 'order';
1101
- } else if (message.productMessage) {
1102
- return 'product';
1103
- } else if (message.interactiveResponseMessage) {
1104
- return 'native_flow_response';
1105
- } else if (message.groupInviteMessage) {
1106
- return 'url';
1107
- }
1108
- return '';
1109
- };
1110
- const getButtonType = message => {
1111
- if (message.buttonsMessage) {
1112
- return 'buttons';
1113
- } else if (message.buttonsResponseMessage) {
1114
- return 'buttons_response';
1115
- } else if (message.interactiveResponseMessage) {
1116
- return 'interactive_response';
1117
- } else if (message.listMessage) {
1118
- return 'list';
1119
- } else if (message.listResponseMessage) {
1120
- return 'list_response';
1121
- }
1122
- };
1123
- const getButtonArgs = message => {
1124
- if (message.templateMessage) {
1125
- return {};
1126
- } else if (message.listMessage) {
1127
- const type = message.listMessage.listType;
1128
- if (!type) {
1129
- throw new Boom('Expected list type inside message');
1130
- }
1131
- return {
1132
- v: '2',
1133
- type: proto.ListMessage.ListType[type].toLowerCase()
1134
- };
1135
- } else {
1136
- return {};
1137
- }
1138
- };
1139
- const issuePrivacyTokens = async (jids, timestamp) => {
1140
- const t = (timestamp ?? unixTimestampSeconds()).toString();
1141
- const result = await query({
1142
- tag: 'iq',
1143
- attrs: {
1144
- to: S_WHATSAPP_NET,
1145
- type: 'set',
1146
- xmlns: 'privacy'
1147
- },
1148
- content: [{
1149
- tag: 'tokens',
1150
- attrs: {},
1151
- content: jids.map(jid => ({
1152
- tag: 'token',
1153
- attrs: {
1154
- jid: jidNormalizedUser(jid),
1155
- t,
1156
- type: 'trusted_contact'
1157
- }
1158
- }))
1159
- }]
1160
- });
1161
- return result;
1162
- };
1163
- const waUploadToServer = getWAUploadToServer(config, refreshMediaConn);
1164
- const waitForMsgMediaUpdate = bindWaitForEvent(ev, 'messages.media-update');
1165
- registerSocketEndHandler(() => {
1166
- if (!config.userDevicesCache && userDevicesCache.close) {
1167
- userDevicesCache.close();
1168
- }
1169
- mediaConn = undefined;
1170
- if (messageRetryManager) {
1171
- messageRetryManager.clear();
1172
- }
1173
- });
1174
- return {
1175
- ...sock,
1176
- userDevicesCache,
1177
- devicesMutex,
1178
- issuePrivacyTokens,
1179
- assertSessions,
1180
- relayMessage,
1181
- sendReceipt,
1182
- sendReceipts,
1183
- readMessages,
1184
- refreshMediaConn,
1185
- getMediaHost: () => mediaHost,
1186
- waUploadToServer,
1187
- fetchPrivacySettings,
1188
- sendPeerDataOperationMessage,
1189
- createParticipantNodes,
1190
- getUSyncDevices,
1191
- messageRetryManager,
1192
- updateMemberLabel,
1193
- updateMediaMessage: async message => {
1194
- const content = assertMediaContent(message.message);
1195
- const mediaKey = content.mediaKey;
1196
- const meId = authState.creds.me.id;
1197
- const node = encryptMediaRetryRequest(message.key, mediaKey, meId);
1198
- let error = undefined;
1199
- await Promise.all([sendNode(node), waitForMsgMediaUpdate(async update => {
1200
- const result = update.find(c => c.key.id === message.key.id);
1201
- if (result) {
1202
- if (result.error) {
1203
- error = result.error;
1204
- } else {
348
+ return msgId;
349
+ };
350
+
351
+ const createParticipantNodes = async (recipientJids, message, extraAttrs, dsmMessage) => {
352
+ if (!recipientJids.length) {
353
+ return { nodes: [], shouldIncludeDeviceIdentity: false };
354
+ }
355
+ const patched = await patchMessageBeforeSending(message, recipientJids);
356
+ const patchedMessages = Array.isArray(patched)
357
+ ? patched
358
+ : recipientJids.map(jid => ({ recipientJid: jid, message: patched }));
359
+ let shouldIncludeDeviceIdentity = false;
360
+ const meId = authState.creds.me.id;
361
+ const meLid = authState.creds.me?.lid;
362
+ const meLidUser = meLid ? jidDecode(meLid)?.user : null;
363
+ const encryptionPromises = patchedMessages.map(async ({ recipientJid: jid, message: patchedMessage }) => {
1205
364
  try {
1206
- const media = decryptMediaRetryData(result.media, mediaKey, result.key.id);
1207
- if (media.result !== proto.MediaRetryNotification.ResultType.SUCCESS) {
1208
- const resultStr = proto.MediaRetryNotification.ResultType[media.result];
1209
- throw new Boom(`Media re-upload failed by device (${resultStr})`, {
1210
- data: media,
1211
- statusCode: getStatusCodeForMediaRetry(media.result) || 404
365
+ if (!jid)
366
+ return null;
367
+ let msgToEncrypt = patchedMessage;
368
+ if (dsmMessage) {
369
+ const { user: targetUser } = jidDecode(jid);
370
+ const { user: ownPnUser } = jidDecode(meId);
371
+ const ownLidUser = meLidUser;
372
+ const isOwnUser = targetUser === ownPnUser || (ownLidUser && targetUser === ownLidUser);
373
+ const isExactSenderDevice = jid === meId || (meLid && jid === meLid);
374
+ if (isOwnUser && !isExactSenderDevice) {
375
+ msgToEncrypt = dsmMessage;
376
+ logger.debug({ jid, targetUser }, 'Using DSM for own device');
377
+ }
378
+ }
379
+ const bytes = encodeWAMessage(msgToEncrypt);
380
+ const mutexKey = jid;
381
+ const node = await encryptionMutex.mutex(mutexKey, async () => {
382
+ const { type, ciphertext } = await signalRepository.encryptMessage({ jid, data: bytes });
383
+ if (type === 'pkmsg') {
384
+ shouldIncludeDeviceIdentity = true;
385
+ }
386
+ return {
387
+ tag: 'to',
388
+ attrs: { jid },
389
+ content: [
390
+ {
391
+ tag: 'enc',
392
+ attrs: { v: '2', type, ...(extraAttrs || {}) },
393
+ content: ciphertext
394
+ }
395
+ ]
396
+ };
1212
397
  });
1213
- }
1214
- content.directPath = media.directPath;
1215
- content.url = getUrlFromDirectPath(content.directPath, mediaHost);
1216
- logger.debug({
1217
- directPath: media.directPath,
1218
- key: result.key
1219
- }, 'media update successful');
1220
- } catch (err) {
1221
- error = err;
398
+ return node;
1222
399
  }
1223
- }
1224
- return true;
400
+ catch (err) {
401
+ logger.error({ jid, err }, 'Failed to encrypt for recipient');
402
+ return null;
403
+ }
404
+ });
405
+ const nodes = (await Promise.all(encryptionPromises)).filter(node => node !== null);
406
+ if (recipientJids.length > 0 && nodes.length === 0) {
407
+ throw new Boom('All encryptions failed', { statusCode: 500 });
1225
408
  }
1226
- })]);
1227
- if (error) {
1228
- throw error;
1229
- }
1230
- ev.emit('messages.update', [{
1231
- key: message.key,
1232
- update: {
1233
- message: message.message
1234
- }
1235
- }]);
1236
- return message;
1237
- },
1238
- richMenu: async (target, content = {}) => {
1239
- let header = content?.header;
1240
- let body = content?.body;
1241
- let footer = content?.footer;
1242
- let dihh = {};
1243
- let sex = [];
1244
- if (header) {
1245
- const {
1246
- disclaimer = false,
1247
- disclaimerText = ' ',
1248
- image = { inline: false },
1249
- title = ''
1250
- } = header ?? {};
1251
- if (disclaimer) {
1252
- dihh = {
1253
- messageContextInfo: {
1254
- botMetadata: {
1255
- messageDisclaimerText: disclaimerText
1256
- }
409
+ return { nodes, shouldIncludeDeviceIdentity };
410
+ };
411
+
412
+ const relayMessage = async (jid, message, { messageId: msgId, participant, additionalAttributes, additionalNodes, useUserDevicesCache, useCachedGroupMetadata, statusJidList }) => {
413
+ const normalizedForStatus = normalizeMessageContent(message);
414
+ const hasGroupStatusWrapper = Boolean(normalizedForStatus?.groupStatusMessageV2 || normalizedForStatus?.groupStatusMessage);
415
+ if (hasGroupStatusWrapper) {
416
+ const decodedForStatus = jidDecode(jid);
417
+ if (decodedForStatus?.server === 'g.us') {
418
+ try {
419
+ if (!statusJidList?.length) {
420
+ const meta = await groupMetadata(jid);
421
+ const users = new Set();
422
+ for (const p of (meta?.participants || [])) {
423
+ const pj = jidNormalizedUser(p.id);
424
+ if (pj) users.add(pj);
425
+ }
426
+ const ownJid = jidNormalizedUser(authState.creds.me.id);
427
+ if (ownJid) users.add(ownJid);
428
+ statusJidList = [...users];
429
+ }
430
+ if (statusJidList?.length) {
431
+ logger.debug({ jid, audience: statusJidList.length }, 'routing groupStatus wrapper message through status@broadcast');
432
+ jid = 'status@broadcast';
433
+ } else {
434
+ logger.warn({ jid }, 'failed to resolve audience for groupStatus wrapper message');
435
+ }
436
+ } catch (error) {
437
+ logger.warn({ jid, trace: error?.stack || String(error) }, 'failed to resolve audience for groupStatus wrapper message');
438
+ }
1257
439
  }
1258
- };
1259
440
  }
1260
- if (title) {
1261
- sex.push({
1262
- __typename: 'GenAIUnifiedResponseSection',
1263
- view_model: {
1264
- __typename: 'GenAISingleLayoutViewModel',
1265
- primitive: {
1266
- __typename: 'FOATextPrimitive',
1267
- text: '# ' + title
1268
- }
441
+ const meId = assertMeId(authState.creds);
442
+ const meLid = authState.creds.me?.lid;
443
+ const isRetryResend = Boolean(participant?.jid);
444
+ let shouldIncludeDeviceIdentity = isRetryResend;
445
+ const statusJid = 'status@broadcast';
446
+ const { user, server } = jidDecode(jid);
447
+ const isGroup = server === 'g.us';
448
+ const isStatus = jid === statusJid;
449
+ const isLid = server === 'lid';
450
+ const isNewsletter = server === 'newsletter';
451
+ const isGroupOrStatus = isGroup || isStatus;
452
+ const finalJid = jid;
453
+ msgId = msgId || generateMessageIDV2(meId);
454
+ useUserDevicesCache = useUserDevicesCache !== false;
455
+ useCachedGroupMetadata = useCachedGroupMetadata !== false && !isStatus;
456
+ const participants = [];
457
+ const destinationJid = !isStatus ? finalJid : statusJid;
458
+ const binaryNodeContent = [];
459
+ const devices = [];
460
+ let reportingMessage;
461
+ const meMsg = {
462
+ deviceSentMessage: {
463
+ destinationJid,
464
+ message
465
+ },
466
+ messageContextInfo: message.messageContextInfo
467
+ };
468
+ const extraAttrs = {};
469
+ if (participant) {
470
+ if (!isGroup && !isStatus) {
471
+ additionalAttributes = { ...additionalAttributes, device_fanout: 'false' };
1269
472
  }
1270
- });
473
+ const { user, device } = jidDecode(participant.jid);
474
+ devices.push({
475
+ user,
476
+ device,
477
+ jid: participant.jid
478
+ });
1271
479
  }
1272
- if (image?.url) {
1273
- if (image?.inline) {
1274
- sex.push({
1275
- __typename: 'GenAIUnifiedResponseSection',
1276
- view_model: {
1277
- __typename: 'GenAISingleLayoutViewModel',
1278
- primitive: {
1279
- __typename: 'GenAIMarkdownTextUXPrimitive',
1280
- text: '{{header}}.{{/header}}',
1281
- inline_entities: [{
1282
- __typename: 'GenAITextInlineEntity',
1283
- key: 'header',
1284
- metadata: {
1285
- __typename: 'GenAILatexItem',
1286
- latex_expression: '.',
1287
- font_height: 24,
1288
- padding: 4,
1289
- latex_image: {
1290
- __typename: 'GenAIMediaItem',
1291
- mime_type: image.mime_type || 'image/png',
1292
- url: image.url,
1293
- url_fallback: image.url,
1294
- width: image.width || 500,
1295
- height: image.height || 500,
1296
- expiration_timestamp_ms: Date.now() + 86400000
1297
- }
1298
- }
1299
- }]
480
+ await authState.keys.transaction(async () => {
481
+ const mediaType = getMediaType(message);
482
+ if (mediaType) {
483
+ extraAttrs['mediatype'] = mediaType;
484
+ }
485
+ if (isNewsletter) {
486
+ const patched = patchMessageBeforeSending ? await patchMessageBeforeSending(message, []) : message;
487
+ const bytes = encodeNewsletterMessage(patched);
488
+ binaryNodeContent.push({
489
+ tag: 'plaintext',
490
+ attrs: {},
491
+ content: bytes
492
+ });
493
+ const stanza = {
494
+ tag: 'message',
495
+ attrs: {
496
+ to: jid,
497
+ id: msgId,
498
+ type: getMessageType(message),
499
+ ...(additionalAttributes || {})
500
+ },
501
+ content: binaryNodeContent
502
+ };
503
+ logger.debug({ msgId }, `sending newsletter message to ${jid}`);
504
+ await sendNode(stanza);
505
+ return;
506
+ }
507
+ if (normalizeMessageContent(message)?.richResponseMessage) {
508
+ const richMsg = normalizeMessageContent(message).richResponseMessage;
509
+ const stanza = {
510
+ tag: 'message',
511
+ attrs: {
512
+ to: jid,
513
+ id: msgId,
514
+ type: 'rich_response',
515
+ ...(additionalAttributes || {})
516
+ },
517
+ content: [{
518
+ tag: 'rich_response',
519
+ attrs: {},
520
+ content: [{
521
+ tag: 'unified_response',
522
+ attrs: {},
523
+ content: richMsg.unifiedResponse?.data || ''
524
+ }]
525
+ }]
526
+ };
527
+ if (richMsg.submessages) {
528
+ stanza.content[0].content.push({
529
+ tag: 'submessages',
530
+ attrs: {},
531
+ content: richMsg.submessages.map(sm => ({
532
+ tag: 'submessage',
533
+ attrs: {
534
+ type: sm.messageType?.toString() || '2'
535
+ },
536
+ content: sm.messageText || ''
537
+ }))
538
+ });
1300
539
  }
1301
- }
1302
- });
1303
- } else {
1304
- sex.push({
1305
- __typename: 'GenAIUnifiedResponseSection',
540
+ if (richMsg.contextInfo) {
541
+ stanza.content[0].attrs.contextInfo = JSON.stringify(richMsg.contextInfo);
542
+ }
543
+ logger.debug({ msgId }, `sending rich response message to ${jid}`);
544
+ await sendNode(stanza);
545
+ return;
546
+ }
547
+ if (normalizeMessageContent(message)?.pinInChatMessage || normalizeMessageContent(message)?.reactionMessage) {
548
+ extraAttrs['decrypt-fail'] = 'hide';
549
+ }
550
+ if (isGroupOrStatus && !isRetryResend) {
551
+ const [groupData, senderKeyMap] = await Promise.all([
552
+ (async () => {
553
+ let groupData = useCachedGroupMetadata && cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined;
554
+ if (groupData && Array.isArray(groupData?.participants)) {
555
+ logger.trace({ jid, participants: groupData.participants.length }, 'using cached group metadata');
556
+ }
557
+ else if (!isStatus) {
558
+ groupData = await groupMetadata(jid);
559
+ }
560
+ return groupData;
561
+ })(),
562
+ (async () => {
563
+ if (!participant && !isStatus) {
564
+ const result = await authState.keys.get('sender-key-memory', [jid]);
565
+ return result[jid] || {};
566
+ }
567
+ return {};
568
+ })()
569
+ ]);
570
+ const participantsList = groupData ? groupData.participants.map(p => p.id) : [];
571
+ if (groupData?.ephemeralDuration && groupData.ephemeralDuration > 0) {
572
+ additionalAttributes = {
573
+ ...additionalAttributes,
574
+ expiration: groupData.ephemeralDuration.toString()
575
+ };
576
+ }
577
+ if (isStatus && statusJidList) {
578
+ participantsList.push(...statusJidList);
579
+ }
580
+ const additionalDevices = await getUSyncDevices(participantsList, !!useUserDevicesCache, false);
581
+ devices.push(...additionalDevices);
582
+ if (isGroup) {
583
+ additionalAttributes = {
584
+ ...additionalAttributes,
585
+ addressing_mode: groupData?.addressingMode || 'lid'
586
+ };
587
+ }
588
+ const patched = await patchMessageBeforeSending(message);
589
+ if (Array.isArray(patched)) {
590
+ throw new Boom('Per-jid patching is not supported in groups');
591
+ }
592
+ const bytes = encodeWAMessage(patched);
593
+ reportingMessage = patched;
594
+ const groupAddressingMode = additionalAttributes?.['addressing_mode'] || groupData?.addressingMode || 'lid';
595
+ const groupSenderIdentity = groupAddressingMode === 'lid' && meLid ? meLid : meId;
596
+ const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage({
597
+ group: destinationJid,
598
+ data: bytes,
599
+ meId: groupSenderIdentity
600
+ });
601
+ const senderKeyRecipients = [];
602
+ for (const device of devices) {
603
+ const deviceJid = device.jid;
604
+ const hasKey = !!senderKeyMap[deviceJid];
605
+ if ((!hasKey || !!participant) &&
606
+ !isHostedLidUser(deviceJid) &&
607
+ !isHostedPnUser(deviceJid) &&
608
+ device.device !== 99) {
609
+ senderKeyRecipients.push(deviceJid);
610
+ senderKeyMap[deviceJid] = true;
611
+ }
612
+ }
613
+ if (senderKeyRecipients.length) {
614
+ logger.debug({ senderKeyJids: senderKeyRecipients }, 'sending new sender key');
615
+ const senderKeyMsg = {
616
+ senderKeyDistributionMessage: {
617
+ axolotlSenderKeyDistributionMessage: senderKeyDistributionMessage,
618
+ groupId: destinationJid
619
+ }
620
+ };
621
+ const senderKeySessionTargets = senderKeyRecipients;
622
+ await assertSessions(senderKeySessionTargets);
623
+ const result = await createParticipantNodes(senderKeyRecipients, senderKeyMsg, extraAttrs);
624
+ shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || result.shouldIncludeDeviceIdentity;
625
+ participants.push(...result.nodes);
626
+ }
627
+ binaryNodeContent.push({
628
+ tag: 'enc',
629
+ attrs: { v: '2', type: 'skmsg', ...extraAttrs },
630
+ content: ciphertext
631
+ });
632
+ await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } });
633
+ }
634
+ else {
635
+ let ownId = meId;
636
+ if (isLid && meLid) {
637
+ ownId = meLid;
638
+ logger.debug({ to: jid, ownId }, 'Using LID identity for @lid conversation');
639
+ }
640
+ else {
641
+ logger.debug({ to: jid, ownId }, 'Using PN identity for @s.whatsapp.net conversation');
642
+ }
643
+ const { user: ownUser } = jidDecode(ownId);
644
+ if (!participant) {
645
+ const patchedForReporting = await patchMessageBeforeSending(message, [jid]);
646
+ reportingMessage = Array.isArray(patchedForReporting)
647
+ ? patchedForReporting.find(item => item.recipientJid === jid) || patchedForReporting[0]
648
+ : patchedForReporting;
649
+ }
650
+ if (!isRetryResend) {
651
+ const targetUserServer = isLid ? 'lid' : 's.whatsapp.net';
652
+ devices.push({
653
+ user,
654
+ device: 0,
655
+ jid: jidEncode(user, targetUserServer, 0)
656
+ });
657
+ if (user !== ownUser) {
658
+ const ownUserServer = isLid ? 'lid' : 's.whatsapp.net';
659
+ const ownUserForAddressing = isLid && meLid ? jidDecode(meLid).user : jidDecode(meId).user;
660
+ devices.push({
661
+ user: ownUserForAddressing,
662
+ device: 0,
663
+ jid: jidEncode(ownUserForAddressing, ownUserServer, 0)
664
+ });
665
+ }
666
+ if (additionalAttributes?.['category'] !== 'peer') {
667
+ devices.length = 0;
668
+ const senderIdentity = isLid && meLid
669
+ ? jidEncode(jidDecode(meLid)?.user, 'lid', undefined)
670
+ : jidEncode(jidDecode(meId)?.user, 's.whatsapp.net', undefined);
671
+ const sessionDevices = await getUSyncDevices([senderIdentity, jid], true, false);
672
+ devices.push(...sessionDevices);
673
+ logger.debug({
674
+ deviceCount: devices.length,
675
+ devices: devices.map(d => `${d.user}:${d.device}@${jidDecode(d.jid)?.server}`)
676
+ }, 'Device enumeration complete with unified addressing');
677
+ }
678
+ }
679
+ const allRecipients = [];
680
+ const meRecipients = [];
681
+ const otherRecipients = [];
682
+ const { user: mePnUser } = jidDecode(meId);
683
+ const { user: meLidUser } = meLid ? jidDecode(meLid) : { user: null };
684
+ for (const { user, jid } of devices) {
685
+ const isExactSenderDevice = jid === meId || (meLid && jid === meLid);
686
+ if (isExactSenderDevice) {
687
+ logger.debug({ jid, meId, meLid }, 'Skipping exact sender device (whatsmeow pattern)');
688
+ continue;
689
+ }
690
+ const isMe = user === mePnUser || user === meLidUser;
691
+ if (isMe) {
692
+ meRecipients.push(jid);
693
+ }
694
+ else {
695
+ otherRecipients.push(jid);
696
+ }
697
+ allRecipients.push(jid);
698
+ }
699
+ await assertSessions(allRecipients);
700
+ const [{ nodes: meNodes, shouldIncludeDeviceIdentity: s1 }, { nodes: otherNodes, shouldIncludeDeviceIdentity: s2 }] = await Promise.all([
701
+ createParticipantNodes(meRecipients, meMsg || message, extraAttrs),
702
+ createParticipantNodes(otherRecipients, message, extraAttrs, meMsg)
703
+ ]);
704
+ participants.push(...meNodes);
705
+ participants.push(...otherNodes);
706
+ if (meRecipients.length > 0 || otherRecipients.length > 0) {
707
+ extraAttrs['phash'] = generateParticipantHashV2([...meRecipients, ...otherRecipients]);
708
+ }
709
+ shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || s1 || s2;
710
+ }
711
+ if (isRetryResend) {
712
+ const isParticipantLid = isLidUser(participant.jid);
713
+ const isMe = areJidsSameUser(participant.jid, isParticipantLid ? meLid : meId);
714
+ let messageToSend = message;
715
+ if (isGroupOrStatus) {
716
+ let groupSenderIdentity;
717
+ if (meLid && (await signalRepository.hasSenderKey({ group: destinationJid, meId: meLid }))) {
718
+ groupSenderIdentity = meLid;
719
+ }
720
+ else if (await signalRepository.hasSenderKey({ group: destinationJid, meId })) {
721
+ groupSenderIdentity = meId;
722
+ }
723
+ if (groupSenderIdentity) {
724
+ try {
725
+ const skdm = await signalRepository.getSenderKeyDistributionMessage({
726
+ group: destinationJid,
727
+ meId: groupSenderIdentity
728
+ });
729
+ messageToSend = {
730
+ ...message,
731
+ senderKeyDistributionMessage: {
732
+ groupId: destinationJid,
733
+ axolotlSenderKeyDistributionMessage: skdm
734
+ }
735
+ };
736
+ }
737
+ catch (err) {
738
+ logger.warn({ err, jid: destinationJid }, 'failed to build SKDM for retry, sending without it');
739
+ }
740
+ }
741
+ }
742
+ const encodedMessageToSend = isMe
743
+ ? encodeWAMessage({
744
+ deviceSentMessage: {
745
+ destinationJid,
746
+ message: messageToSend
747
+ }
748
+ })
749
+ : encodeWAMessage(messageToSend);
750
+ const { type, ciphertext: encryptedContent } = await signalRepository.encryptMessage({
751
+ data: encodedMessageToSend,
752
+ jid: participant.jid
753
+ });
754
+ binaryNodeContent.push({
755
+ tag: 'enc',
756
+ attrs: {
757
+ v: '2',
758
+ type,
759
+ count: participant.count.toString()
760
+ },
761
+ content: encryptedContent
762
+ });
763
+ }
764
+ if (participants.length) {
765
+ if (additionalAttributes?.['category'] === 'peer') {
766
+ const peerNode = participants[0]?.content?.[0];
767
+ if (peerNode) {
768
+ binaryNodeContent.push(peerNode);
769
+ }
770
+ }
771
+ else {
772
+ binaryNodeContent.push({
773
+ tag: 'participants',
774
+ attrs: {},
775
+ content: participants
776
+ });
777
+ }
778
+ }
779
+ const stanza = {
780
+ tag: 'message',
781
+ attrs: {
782
+ id: msgId,
783
+ to: destinationJid,
784
+ type: getMessageType(message),
785
+ ...(additionalAttributes || {})
786
+ },
787
+ content: binaryNodeContent
788
+ };
789
+ if (participant) {
790
+ if (isJidGroup(destinationJid)) {
791
+ stanza.attrs.to = destinationJid;
792
+ stanza.attrs.participant = participant.jid;
793
+ }
794
+ else if (areJidsSameUser(participant.jid, meId)) {
795
+ stanza.attrs.to = participant.jid;
796
+ stanza.attrs.recipient = destinationJid;
797
+ }
798
+ else {
799
+ stanza.attrs.to = participant.jid;
800
+ }
801
+ }
802
+ else {
803
+ stanza.attrs.to = destinationJid;
804
+ }
805
+ if (shouldIncludeDeviceIdentity) {
806
+ stanza.content.push({
807
+ tag: 'device-identity',
808
+ attrs: {},
809
+ content: encodeSignedDeviceIdentity(authState.creds.account, true)
810
+ });
811
+ logger.debug({ jid }, 'adding device identity');
812
+ }
813
+ if (!isNewsletter &&
814
+ !isRetryResend &&
815
+ reportingMessage?.messageContextInfo?.messageSecret &&
816
+ shouldIncludeReportingToken(reportingMessage)) {
817
+ try {
818
+ const encoded = encodeWAMessage(reportingMessage);
819
+ const reportingKey = {
820
+ id: msgId,
821
+ fromMe: true,
822
+ remoteJid: destinationJid,
823
+ participant: participant?.jid
824
+ };
825
+ const reportingNode = await getMessageReportingToken(encoded, reportingMessage, reportingKey);
826
+ if (reportingNode) {
827
+ stanza.content.push(reportingNode);
828
+ logger.trace({ jid }, 'added reporting token to message');
829
+ }
830
+ }
831
+ catch (error) {
832
+ logger.warn({ jid, trace: error?.stack }, 'failed to attach reporting token');
833
+ }
834
+ }
835
+ const isPeerMessage = additionalAttributes?.['category'] === 'peer';
836
+ const is1on1Send = !isGroup && !isRetryResend && !isStatus && !isNewsletter && !isPeerMessage;
837
+ const tcTokenJid = is1on1Send ? await resolveTcTokenJid(destinationJid, getLIDForPN) : destinationJid;
838
+ const contactTcTokenData = is1on1Send ? await authState.keys.get('tctoken', [tcTokenJid]) : {};
839
+ const existingTokenEntry = contactTcTokenData[tcTokenJid];
840
+ let tcTokenBuffer = existingTokenEntry?.token;
841
+ if (tcTokenBuffer?.length && isTcTokenExpired(existingTokenEntry?.timestamp)) {
842
+ logger.debug({ jid: destinationJid, timestamp: existingTokenEntry?.timestamp }, 'tctoken expired, clearing');
843
+ tcTokenBuffer = undefined;
844
+ const cleared = existingTokenEntry?.senderTimestamp !== undefined
845
+ ? { token: Buffer.alloc(0), senderTimestamp: existingTokenEntry.senderTimestamp }
846
+ : null;
847
+ try {
848
+ await authState.keys.set({ tctoken: { [tcTokenJid]: cleared } });
849
+ }
850
+ catch (err) {
851
+ logger.debug({ jid: destinationJid, err: err?.message }, 'failed to persist tctoken expiry cleanup');
852
+ }
853
+ }
854
+ if (tcTokenBuffer?.length && sock.serverProps.privacyTokenOn1to1) {
855
+ stanza.content.push({
856
+ tag: 'tctoken',
857
+ attrs: {},
858
+ content: tcTokenBuffer
859
+ });
860
+ }
861
+ if (additionalNodes && additionalNodes.length > 0) {
862
+ stanza.content.push(...additionalNodes);
863
+ }
864
+ logger.debug({ msgId }, `sending message to ${participants.length} devices`);
865
+ await sendNode(stanza);
866
+ const isProtocolMsg = !!normalizeMessageContent(message)?.protocolMessage;
867
+ const isBotOrPSA = destinationJid === PSA_WID || isJidBot(destinationJid) || isJidMetaAI(destinationJid);
868
+ if (is1on1Send &&
869
+ !isProtocolMsg &&
870
+ !isBotOrPSA &&
871
+ shouldSendNewTcToken(existingTokenEntry?.senderTimestamp) &&
872
+ !inFlightTcTokenIssuance.has(tcTokenJid)) {
873
+ inFlightTcTokenIssuance.add(tcTokenJid);
874
+ const issueTimestamp = unixTimestampSeconds();
875
+ const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping);
876
+ resolveIssuanceJid(destinationJid, sock.serverProps.lidTrustedTokenIssueToLid, getLIDForPN, getPNForLID)
877
+ .then(issueJid => issuePrivacyTokens([issueJid], issueTimestamp))
878
+ .then(async (result) => {
879
+ await storeTcTokensFromIqResult({
880
+ result,
881
+ fallbackJid: tcTokenJid,
882
+ keys: authState.keys,
883
+ getLIDForPN
884
+ });
885
+ const currentData = await authState.keys.get('tctoken', [tcTokenJid]);
886
+ const currentEntry = currentData[tcTokenJid];
887
+ const indexWrite = await buildMergedTcTokenIndexWrite(authState.keys, [tcTokenJid]);
888
+ await authState.keys.set({
889
+ tctoken: {
890
+ [tcTokenJid]: {
891
+ token: Buffer.alloc(0),
892
+ ...currentEntry,
893
+ senderTimestamp: issueTimestamp
894
+ },
895
+ ...indexWrite
896
+ }
897
+ });
898
+ })
899
+ .catch(err => {
900
+ logger.debug({ jid: destinationJid, err: err?.message }, 'fire-and-forget tctoken issuance failed');
901
+ })
902
+ .finally(() => {
903
+ inFlightTcTokenIssuance.delete(tcTokenJid);
904
+ });
905
+ }
906
+ if (messageRetryManager && !participant) {
907
+ messageRetryManager.addRecentMessage(destinationJid, msgId, message);
908
+ }
909
+ }, meId);
910
+ return msgId;
911
+ };
912
+
913
+ const getMessageType = (message) => {
914
+ const normalizedMessage = normalizeMessageContent(message);
915
+ if (!normalizedMessage)
916
+ return 'text';
917
+ if (normalizedMessage.reactionMessage || normalizedMessage.encReactionMessage) {
918
+ return 'reaction';
919
+ }
920
+ if (normalizedMessage.pollCreationMessage ||
921
+ normalizedMessage.pollCreationMessageV2 ||
922
+ normalizedMessage.pollCreationMessageV3 ||
923
+ normalizedMessage.pollUpdateMessage) {
924
+ return 'poll';
925
+ }
926
+ if (normalizedMessage.eventMessage) {
927
+ return 'event';
928
+ }
929
+ if (normalizedMessage.groupStatusMessage) {
930
+ return 'group_status';
931
+ }
932
+ if (normalizedMessage.richResponseMessage) {
933
+ return 'rich_response';
934
+ }
935
+ if (getMediaType(normalizedMessage) !== '') {
936
+ return 'media';
937
+ }
938
+ return 'text';
939
+ };
940
+
941
+ const getMediaType = (message) => {
942
+ if (message.imageMessage) {
943
+ return 'image';
944
+ }
945
+ else if (message.videoMessage) {
946
+ return message.videoMessage.gifPlayback ? 'gif' : 'video';
947
+ }
948
+ else if (message.audioMessage) {
949
+ return message.audioMessage.ptt ? 'ptt' : 'audio';
950
+ }
951
+ else if (message.contactMessage) {
952
+ return 'vcard';
953
+ }
954
+ else if (message.documentMessage) {
955
+ return 'document';
956
+ }
957
+ else if (message.contactsArrayMessage) {
958
+ return 'contact_array';
959
+ }
960
+ else if (message.liveLocationMessage) {
961
+ return 'livelocation';
962
+ }
963
+ else if (message.stickerMessage) {
964
+ return 'sticker';
965
+ }
966
+ else if (message.listMessage) {
967
+ return 'list';
968
+ }
969
+ else if (message.listResponseMessage) {
970
+ return 'list_response';
971
+ }
972
+ else if (message.buttonsResponseMessage) {
973
+ return 'buttons_response';
974
+ }
975
+ else if (message.orderMessage) {
976
+ return 'order';
977
+ }
978
+ else if (message.productMessage) {
979
+ return 'product';
980
+ }
981
+ else if (message.interactiveResponseMessage) {
982
+ return 'native_flow_response';
983
+ }
984
+ else if (message.groupInviteMessage) {
985
+ return 'url';
986
+ }
987
+ else if (message.richResponseMessage) {
988
+ return 'rich_response';
989
+ }
990
+ else if (message.groupStatusMessage) {
991
+ if (message.groupStatusMessage.image) return 'image';
992
+ if (message.groupStatusMessage.video) return 'video';
993
+ if (message.groupStatusMessage.audio) return 'audio';
994
+ if (message.groupStatusMessage.sticker) return 'sticker';
995
+ return 'text';
996
+ }
997
+ return '';
998
+ };
999
+
1000
+ const issuePrivacyTokens = async (jids, timestamp) => {
1001
+ const t = (timestamp ?? unixTimestampSeconds()).toString();
1002
+ const result = await query({
1003
+ tag: 'iq',
1004
+ attrs: {
1005
+ to: S_WHATSAPP_NET,
1006
+ type: 'set',
1007
+ xmlns: 'privacy'
1008
+ },
1009
+ content: [
1010
+ {
1011
+ tag: 'tokens',
1012
+ attrs: {},
1013
+ content: jids.map(jid => ({
1014
+ tag: 'token',
1015
+ attrs: {
1016
+ jid: jidNormalizedUser(jid),
1017
+ t,
1018
+ type: 'trusted_contact'
1019
+ }
1020
+ }))
1021
+ }
1022
+ ]
1023
+ });
1024
+ return result;
1025
+ };
1026
+
1027
+ const waUploadToServer = getWAUploadToServer(config, refreshMediaConn);
1028
+ const waitForMsgMediaUpdate = bindWaitForEvent(ev, 'messages.media-update');
1029
+
1030
+ const createQuickReplyButton = (displayText, id) => ({
1031
+ buttonId: id || `btn_${Date.now()}`,
1032
+ buttonText: { displayText },
1033
+ type: 1
1034
+ });
1035
+
1036
+ const createUrlButton = (displayText, url) => ({
1037
+ buttonText: { displayText },
1038
+ type: 2,
1039
+ url
1040
+ });
1041
+
1042
+ const createCallButton = (displayText, phoneNumber) => ({
1043
+ buttonText: { displayText },
1044
+ type: 3,
1045
+ phoneNumber
1046
+ });
1047
+
1048
+ const createLocationButton = (displayText) => ({
1049
+ buttonText: { displayText },
1050
+ type: 4
1051
+ });
1052
+
1053
+ const createContactButton = (displayText) => ({
1054
+ buttonText: { displayText },
1055
+ type: 5
1056
+ });
1057
+
1058
+ const createPollButton = (displayText) => ({
1059
+ buttonText: { displayText },
1060
+ type: 6
1061
+ });
1062
+
1063
+ const createInteractiveReplyButton = (id, title) => ({
1064
+ type: 'reply',
1065
+ reply: {
1066
+ id,
1067
+ title
1068
+ }
1069
+ });
1070
+
1071
+ const createInteractiveUrlButton = (displayText, url) => ({
1072
+ type: 'url',
1073
+ url: {
1074
+ displayText,
1075
+ url
1076
+ }
1077
+ });
1078
+
1079
+ const createInteractiveCallButton = (displayText, phoneNumber) => ({
1080
+ type: 'call',
1081
+ call: {
1082
+ displayText,
1083
+ phoneNumber
1084
+ }
1085
+ });
1086
+
1087
+ const createInteractiveCatalogButton = (displayText, catalogId, businessOwnerJid) => ({
1088
+ type: 'catalog',
1089
+ catalog: {
1090
+ displayText,
1091
+ catalogId,
1092
+ businessOwnerJid
1093
+ }
1094
+ });
1095
+
1096
+ const createListMessage = (title, description, buttonText, sections) => ({
1097
+ listMessage: {
1098
+ title,
1099
+ description,
1100
+ buttonText,
1101
+ listType: 1,
1102
+ sections: sections || []
1103
+ }
1104
+ });
1105
+
1106
+ const createListSection = (title, rows) => ({
1107
+ title,
1108
+ rows: rows.map(row => ({
1109
+ title: row.title,
1110
+ description: row.description || '',
1111
+ rowId: row.rowId || `row_${Date.now()}_${Math.random()}`
1112
+ }))
1113
+ });
1114
+
1115
+ const createButtonsMessage = (text, footer, buttons, headerType = 1) => ({
1116
+ buttonsMessage: {
1117
+ contentText: text,
1118
+ footerText: footer || '',
1119
+ buttons: buttons || [],
1120
+ headerType
1121
+ }
1122
+ });
1123
+
1124
+ const createButtonsMessageWithHeader = (text, footer, headerTitle, buttons) => ({
1125
+ buttonsMessage: {
1126
+ contentText: text,
1127
+ footerText: footer || '',
1128
+ headerType: 4,
1129
+ headerTitle,
1130
+ buttons: buttons || []
1131
+ }
1132
+ });
1133
+
1134
+ const createButtonsMessageWithImageHeader = (text, footer, imageUrl, buttons) => ({
1135
+ buttonsMessage: {
1136
+ contentText: text,
1137
+ footerText: footer || '',
1138
+ headerType: 2,
1139
+ headerImage: { url: imageUrl },
1140
+ buttons: buttons || []
1141
+ }
1142
+ });
1143
+
1144
+ const createButtonsMessageWithDocumentHeader = (text, footer, documentUrl, fileName, buttons) => ({
1145
+ buttonsMessage: {
1146
+ contentText: text,
1147
+ footerText: footer || '',
1148
+ headerType: 3,
1149
+ headerDocument: { url: documentUrl, fileName },
1150
+ buttons: buttons || []
1151
+ }
1152
+ });
1153
+
1154
+ const createTemplateMessage = (text, footer, templateId, buttons) => ({
1155
+ templateMessage: {
1156
+ hydratedTemplate: {
1157
+ hydratedContentText: text,
1158
+ hydratedFooterText: footer || '',
1159
+ hydratedButtons: buttons || []
1160
+ }
1161
+ }
1162
+ });
1163
+
1164
+ const createCatalogMessage = (businessOwnerJid, catalogItems) => ({
1165
+ catalogMessage: {
1166
+ businessOwnerJid,
1167
+ catalogItems: catalogItems || []
1168
+ }
1169
+ });
1170
+
1171
+ const createProductMessage = (productId, businessOwnerJid, catalogId) => ({
1172
+ productMessage: {
1173
+ productId,
1174
+ businessOwnerJid,
1175
+ catalogId
1176
+ }
1177
+ });
1178
+
1179
+ const createCollectionMessage = (businessOwnerJid, catalogId) => ({
1180
+ collectionMessage: {
1181
+ businessOwnerJid,
1182
+ catalogId
1183
+ }
1184
+ });
1185
+
1186
+ const createOrderMessage = (orderId, items, total, currency) => ({
1187
+ orderMessage: {
1188
+ orderId,
1189
+ items: items || [],
1190
+ total: total || 0,
1191
+ currency: currency || 'PKR'
1192
+ }
1193
+ });
1194
+
1195
+ const createInteractiveMessage = (body, footer, header, action, contextInfo) => ({
1196
+ interactiveMessage: {
1197
+ body: { text: body },
1198
+ footer: footer ? { text: footer } : undefined,
1199
+ header: header || undefined,
1200
+ action: action || {
1201
+ buttons: [],
1202
+ sections: []
1203
+ },
1204
+ contextInfo: contextInfo || undefined
1205
+ }
1206
+ });
1207
+
1208
+ const createInteractiveButtonsAction = (buttons) => ({
1209
+ buttons: buttons.map(btn => ({
1210
+ type: btn.type,
1211
+ reply: btn.reply,
1212
+ url: btn.url,
1213
+ call: btn.call,
1214
+ catalog: btn.catalog,
1215
+ copy: btn.copy,
1216
+ flow: btn.flow,
1217
+ optout: btn.optout,
1218
+ onetime: btn.onetime,
1219
+ auth: btn.auth,
1220
+ otp: btn.otp,
1221
+ payment: btn.payment,
1222
+ schedule: btn.schedule,
1223
+ feedback: btn.feedback,
1224
+ share: btn.share,
1225
+ download: btn.download,
1226
+ upload: btn.upload,
1227
+ verify: btn.verify,
1228
+ approve: btn.approve,
1229
+ reject: btn.reject,
1230
+ forward: btn.forward,
1231
+ mute: btn.mute,
1232
+ unmute: btn.unmute,
1233
+ archive: btn.archive,
1234
+ unarchive: btn.unarchive,
1235
+ pin: btn.pin,
1236
+ unpin: btn.unpin,
1237
+ bookmark: btn.bookmark,
1238
+ unbookmark: btn.unbookmark,
1239
+ flag: btn.flag,
1240
+ unflag: btn.unflag,
1241
+ lock: btn.lock,
1242
+ unlock: btn.unlock,
1243
+ address: btn.address,
1244
+ location: btn.location,
1245
+ voice: btn.voice,
1246
+ video: btn.video,
1247
+ image: btn.image,
1248
+ document: btn.document,
1249
+ audio: btn.audio,
1250
+ sticker: btn.sticker
1251
+ }))
1252
+ });
1253
+
1254
+ const createInteractiveListAction = (title, buttonText, sections) => ({
1255
+ sections: sections.map(section => ({
1256
+ title: section.title,
1257
+ rows: section.rows.map(row => ({
1258
+ id: row.id,
1259
+ title: row.title,
1260
+ description: row.description || ''
1261
+ }))
1262
+ })),
1263
+ button: buttonText || 'Select Option'
1264
+ });
1265
+
1266
+ const createInteractiveCatalogAction = (catalogId, businessOwnerJid, displayText) => ({
1267
+ catalog: {
1268
+ catalogId,
1269
+ businessOwnerJid,
1270
+ displayText
1271
+ }
1272
+ });
1273
+
1274
+ const sendRichResponseMessage = async (jid, richResponseData, options = {}) => {
1275
+ const message = {
1276
+ richResponseMessage: richResponseData
1277
+ };
1278
+ return await sendMessage(jid, message, options);
1279
+ };
1280
+
1281
+ const richMenu = async (target, content = {}) => {
1282
+ let header = content?.header;
1283
+ let body = content?.body;
1284
+ let footer = content?.footer;
1285
+ let dihh = {};
1286
+ let sex = [];
1287
+ if (header) {
1288
+ const {
1289
+ disclaimer = false,
1290
+ disclaimerText = ' ',
1291
+ image = { inline: false },
1292
+ title = ''
1293
+ } = header ?? {};
1294
+ if (disclaimer) {
1295
+ dihh = {
1296
+ messageContextInfo: {
1297
+ botMetadata: {
1298
+ messageDisclaimerText: disclaimerText
1299
+ }
1300
+ }
1301
+ };
1302
+ }
1303
+ if (title) {
1304
+ sex.push({
1305
+ __typename: 'GenAIUnifiedResponseSection',
1306
+ view_model: {
1307
+ __typename: 'GenAISingleLayoutViewModel',
1308
+ primitive: {
1309
+ __typename: 'FOATextPrimitive',
1310
+ text: '# ' + title
1311
+ }
1312
+ }
1313
+ });
1314
+ }
1315
+ if (image?.url) {
1316
+ if (image?.inline) {
1317
+ sex.push({
1318
+ __typename: 'GenAIUnifiedResponseSection',
1319
+ view_model: {
1320
+ __typename: 'GenAISingleLayoutViewModel',
1321
+ primitive: {
1322
+ __typename: 'GenAIMarkdownTextUXPrimitive',
1323
+ text: '{{header}}.{{/header}}',
1324
+ inline_entities: [{
1325
+ __typename: 'GenAITextInlineEntity',
1326
+ key: 'header',
1327
+ metadata: {
1328
+ __typename: 'GenAILatexItem',
1329
+ latex_expression: '.',
1330
+ font_height: 24,
1331
+ padding: 4,
1332
+ latex_image: {
1333
+ __typename: 'GenAIMediaItem',
1334
+ mime_type: image.mime_type || 'image/png',
1335
+ url: image.url,
1336
+ url_fallback: image.url,
1337
+ width: image.width || 500,
1338
+ height: image.height || 500,
1339
+ expiration_timestamp_ms: Date.now() + 86400000
1340
+ }
1341
+ }
1342
+ }]
1343
+ }
1344
+ }
1345
+ });
1346
+ } else {
1347
+ sex.push({
1348
+ __typename: 'GenAIUnifiedResponseSection',
1306
1349
  view_model: {
1307
1350
  __typename: 'GenAISingleLayoutViewModel',
1308
1351
  primitive: {
@@ -1461,133 +1504,1214 @@ export const makeMessagesSocket = config => {
1461
1504
  messageId: waMsg.key.id
1462
1505
  });
1463
1506
  return waMsg;
1464
- },
1465
- sendjson: async (target, json = {}, config = {}) => {
1466
- const msg = generateWAMessageFromContent(target, json, {});
1467
- return relayMessage(target, msg.message, { messageId: msg.key.id, ...config });
1468
- },
1469
- decodeJid: jid => {
1470
- if (!jid) return jid;
1471
- if (/\:\d+@/gi.test(jid)) {
1472
- const decode = jidDecode(jid) || {};
1473
- return (decode.user && decode.server && `${decode.user}@${decode.server}`) || jid;
1474
- }
1475
- return jid;
1476
- },
1477
- makeFakeCommand: async (m, text, chatUpdate) => {
1478
- const x = {};
1479
- x.chat = m.key?.remoteJid;
1480
- x.fromMe = m.key?.fromMe;
1481
- if ((m.key?.participant?.endsWith('@lid')) && (m.key?.participant === sock.user?.lid)) {
1482
- x.sender = sock.user?.lid;
1483
- } else {
1484
- x.sender = sock.decodeJid((x.fromMe && sock.user?.id) || x.participant || m.key?.participant || x.chat || '');
1485
- }
1486
- m.sender = x.sender;
1487
- let recipient;
1488
- if (m.key?.remoteJid?.includes(':')) {
1489
- recipient = m.key.remoteJid.split(':')[0] + '@lid';
1490
- } else {
1491
- recipient = m.key?.remoteJid;
1492
- }
1493
- const messages = await generateWAMessageFromContent(
1494
- recipient,
1495
- { extendedTextMessage: { text: text || '' } },
1496
- {}
1497
- );
1498
- messages.key.fromMe = true;
1499
- messages.key.id = 'INTERNAL-CMD';
1500
- messages.pushName = m.pushName;
1501
- if (chatUpdate) {
1502
- messages.key.participant = chatUpdate[0]?.update?.pollUpdates?.[0]?.pollUpdateMessageKey?.participant || '';
1503
- }
1504
- return ev.emit('messages.upsert', { messages: [messages], type: 'append' });
1505
- },
1506
- pollMenu: async (jid, name = '', pollOptions = [], context = {}, selectableCount = 1) => {
1507
- const values = pollOptions.map(o => typeof o === 'string' ? o : o.vote);
1508
- const pollMsg = await sock.sendMessage(jid, {
1509
- poll: { name, values, selectableCount },
1510
- ...(context && typeof context === 'object' ? context : {})
1511
- });
1512
- sock.tempPollStore.push({ id: pollMsg.key.id, cmds: pollOptions });
1513
- return pollMsg;
1514
- },
1515
- tempPollStore: [],
1516
- sendMessage: async (jid, content, options = {}) => {
1517
- const userJid = authState.creds.me.id;
1518
- if (typeof content === 'object' && 'disappearingMessagesInChat' in content && typeof content['disappearingMessagesInChat'] !== 'undefined' && isJidGroup(jid)) {
1519
- const {
1520
- disappearingMessagesInChat
1521
- } = content;
1522
- const value = typeof disappearingMessagesInChat === 'boolean' ? disappearingMessagesInChat ? WA_DEFAULT_EPHEMERAL : 0 : disappearingMessagesInChat;
1523
- await groupToggleEphemeral(jid, value);
1524
- } else {
1525
- const fullMsg = await generateWAMessage(jid, content, {
1526
- logger,
1527
- userJid,
1528
- getUrlInfo: text => getUrlInfo(text, {
1529
- thumbnailWidth: linkPreviewImageThumbnailWidth,
1530
- fetchOpts: {
1531
- timeout: 3000,
1532
- ...(httpRequestOptions || {})
1533
- },
1534
- logger,
1535
- uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined
1536
- }),
1537
- getProfilePicUrl: sock.profilePictureUrl,
1538
- getCallLink: sock.createCallLink,
1539
- upload: waUploadToServer,
1540
- mediaCache: config.mediaCache,
1541
- options: config.options,
1542
- messageId: generateMessageIDV2(sock.user?.id),
1543
- ...options
1544
- });
1545
- const isEventMsg = 'event' in content && !!content.event;
1546
- const isDeleteMsg = 'delete' in content && !!content.delete;
1547
- const isEditMsg = 'edit' in content && !!content.edit;
1548
- const isPinMsg = 'pin' in content && !!content.pin;
1549
- const isPollMessage = 'poll' in content && !!content.poll;
1550
- const additionalAttributes = {};
1551
- const additionalNodes = [];
1552
- if (isDeleteMsg) {
1553
- if (isJidGroup(content.delete?.remoteJid) && !content.delete?.fromMe) {
1554
- additionalAttributes.edit = '8';
1555
- } else {
1556
- additionalAttributes.edit = '7';
1557
- }
1558
- } else if (isEditMsg) {
1559
- additionalAttributes.edit = '1';
1560
- } else if (isPinMsg) {
1561
- additionalAttributes.edit = '2';
1562
- } else if (isPollMessage) {
1563
- additionalNodes.push({
1564
- tag: 'meta',
1565
- attrs: {
1566
- polltype: 'creation'
1507
+ };
1508
+ const sendMessage = async (jid, content, options = {}) => {
1509
+ if (content.groupStatusMessage || content.groupStatus) {
1510
+ const statusContent = content.groupStatusMessage || content.groupStatus;
1511
+ const fullMsg = await generateWAMessage(jid, {
1512
+ groupStatusMessage: statusContent
1513
+ }, {
1514
+ logger,
1515
+ userJid: authState.creds.me.id,
1516
+ upload: waUploadToServer,
1517
+ mediaCache: config.mediaCache,
1518
+ messageId: generateMessageIDV2(sock.user?.id)
1519
+ });
1520
+ await relayMessage(jid, fullMsg.message, {
1521
+ messageId: fullMsg.key.id,
1522
+ additionalAttributes: { type: 'group_status' },
1523
+ additionalNodes: [{ tag: 'meta', attrs: { type: 'group_status' } }]
1524
+ });
1525
+ if (config.emitOwnEvents) {
1526
+ process.nextTick(async () => {
1527
+ await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
1528
+ });
1567
1529
  }
1568
- });
1569
- } else if (isEventMsg) {
1570
- additionalNodes.push({
1571
- tag: 'meta',
1572
- attrs: {
1573
- event_type: 'creation'
1530
+ return fullMsg;
1531
+ }
1532
+ const userJid = authState.creds.me.id;
1533
+ if (typeof content === 'object' &&
1534
+ 'disappearingMessagesInChat' in content &&
1535
+ typeof content['disappearingMessagesInChat'] !== 'undefined' &&
1536
+ isJidGroup(jid)) {
1537
+ const { disappearingMessagesInChat } = content;
1538
+ const value = typeof disappearingMessagesInChat === 'boolean'
1539
+ ? disappearingMessagesInChat
1540
+ ? WA_DEFAULT_EPHEMERAL
1541
+ : 0
1542
+ : disappearingMessagesInChat;
1543
+ await groupToggleEphemeral(jid, value);
1544
+ }
1545
+ else {
1546
+ const fullMsg = await generateWAMessage(jid, content, {
1547
+ logger,
1548
+ userJid,
1549
+ getUrlInfo: text => getUrlInfo(text, {
1550
+ thumbnailWidth: linkPreviewImageThumbnailWidth,
1551
+ fetchOpts: {
1552
+ timeout: 3000,
1553
+ ...(httpRequestOptions || {})
1554
+ },
1555
+ logger,
1556
+ uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined
1557
+ }),
1558
+ getProfilePicUrl: sock.profilePictureUrl,
1559
+ getCallLink: sock.createCallLink,
1560
+ upload: waUploadToServer,
1561
+ mediaCache: config.mediaCache,
1562
+ options: config.options,
1563
+ messageId: generateMessageIDV2(sock.user?.id),
1564
+ ...options
1565
+ });
1566
+ const isEventMsg = 'event' in content && !!content.event;
1567
+ const isDeleteMsg = 'delete' in content && !!content.delete;
1568
+ const isEditMsg = 'edit' in content && !!content.edit;
1569
+ const isPinMsg = 'pin' in content && !!content.pin;
1570
+ const isPollMessage = 'poll' in content && !!content.poll;
1571
+ const additionalAttributes = {};
1572
+ const additionalNodes = [];
1573
+ if (isDeleteMsg) {
1574
+ if (isJidGroup(content.delete?.remoteJid) && !content.delete?.fromMe) {
1575
+ additionalAttributes.edit = '8';
1576
+ }
1577
+ else {
1578
+ additionalAttributes.edit = '7';
1579
+ }
1574
1580
  }
1575
- });
1581
+ else if (isEditMsg) {
1582
+ additionalAttributes.edit = '1';
1583
+ }
1584
+ else if (isPinMsg) {
1585
+ additionalAttributes.edit = '2';
1586
+ }
1587
+ else if (isPollMessage) {
1588
+ additionalNodes.push({
1589
+ tag: 'meta',
1590
+ attrs: {
1591
+ polltype: 'creation'
1592
+ }
1593
+ });
1594
+ }
1595
+ else if (isEventMsg) {
1596
+ additionalNodes.push({
1597
+ tag: 'meta',
1598
+ attrs: {
1599
+ event_type: 'creation'
1600
+ }
1601
+ });
1602
+ }
1603
+ await relayMessage(jid, fullMsg.message, {
1604
+ messageId: fullMsg.key.id,
1605
+ useCachedGroupMetadata: options.useCachedGroupMetadata,
1606
+ additionalAttributes,
1607
+ statusJidList: options.statusJidList,
1608
+ additionalNodes
1609
+ });
1610
+ if (config.emitOwnEvents) {
1611
+ process.nextTick(async () => {
1612
+ await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
1613
+ });
1614
+ }
1615
+ return fullMsg;
1576
1616
  }
1577
- await relayMessage(jid, fullMsg.message, {
1578
- messageId: fullMsg.key.id,
1579
- useCachedGroupMetadata: options.useCachedGroupMetadata,
1580
- additionalAttributes,
1581
- statusJidList: options.statusJidList,
1582
- additionalNodes
1583
- });
1584
- if (config.emitOwnEvents) {
1585
- process.nextTick(async () => {
1586
- await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
1587
- });
1617
+ };
1618
+
1619
+ const sendQuickReplyButtons = async (jid, text, buttons, footer = '') => {
1620
+ const message = createButtonsMessage(text, footer, buttons);
1621
+ return await sendMessage(jid, message);
1622
+ };
1623
+
1624
+ const sendUrlButtons = async (jid, text, urlButtons, footer = '') => {
1625
+ const message = createButtonsMessage(text, footer, urlButtons);
1626
+ return await sendMessage(jid, message);
1627
+ };
1628
+
1629
+ const sendMixedButtons = async (jid, text, buttons, footer = '') => {
1630
+ const message = createButtonsMessage(text, footer, buttons);
1631
+ return await sendMessage(jid, message);
1632
+ };
1633
+
1634
+ const sendButtonsWithHeader = async (jid, text, headerTitle, buttons, footer = '') => {
1635
+ const message = createButtonsMessageWithHeader(text, footer, headerTitle, buttons);
1636
+ return await sendMessage(jid, message);
1637
+ };
1638
+
1639
+ const sendButtonsWithImageHeader = async (jid, text, imageUrl, buttons, footer = '') => {
1640
+ const message = createButtonsMessageWithImageHeader(text, footer, imageUrl, buttons);
1641
+ return await sendMessage(jid, message);
1642
+ };
1643
+
1644
+ const sendButtonsWithDocumentHeader = async (jid, text, documentUrl, fileName, buttons, footer = '') => {
1645
+ const message = createButtonsMessageWithDocumentHeader(text, footer, documentUrl, fileName, buttons);
1646
+ return await sendMessage(jid, message);
1647
+ };
1648
+
1649
+ const sendListMessage = async (jid, title, description, buttonText, sections) => {
1650
+ const message = createListMessage(title, description, buttonText, sections);
1651
+ return await sendMessage(jid, message);
1652
+ };
1653
+
1654
+ const sendInteractiveButtons = async (jid, body, footer, buttons, header = null) => {
1655
+ const action = createInteractiveButtonsAction(buttons);
1656
+ const message = createInteractiveMessage(body, footer, header, action);
1657
+ return await sendMessage(jid, message);
1658
+ };
1659
+
1660
+ const sendInteractiveList = async (jid, body, footer, title, buttonText, sections, header = null) => {
1661
+ const action = createInteractiveListAction(title, buttonText, sections);
1662
+ const message = createInteractiveMessage(body, footer, header, action);
1663
+ return await sendMessage(jid, message);
1664
+ };
1665
+
1666
+ const sendInteractiveCatalog = async (jid, body, footer, catalogId, businessOwnerJid, displayText, header = null) => {
1667
+ const action = createInteractiveCatalogAction(catalogId, businessOwnerJid, displayText);
1668
+ const message = createInteractiveMessage(body, footer, header, action);
1669
+ return await sendMessage(jid, message);
1670
+ };
1671
+
1672
+ const sendPollMessage = async (jid, question, options, optionsType = 0) => {
1673
+ const message = {
1674
+ pollCreationMessage: {
1675
+ name: question,
1676
+ options: options.map(opt => ({
1677
+ optionName: opt
1678
+ })),
1679
+ selectableOptionsCount: 1,
1680
+ optionsType: optionsType
1681
+ }
1682
+ };
1683
+ return await sendMessage(jid, message);
1684
+ };
1685
+
1686
+ const sendCallButton = async (jid, text, phoneNumber, displayText, footer = '') => {
1687
+ const callButton = createCallButton(displayText, phoneNumber);
1688
+ const message = createButtonsMessage(text, footer, [callButton]);
1689
+ return await sendMessage(jid, message);
1690
+ };
1691
+
1692
+ const sendLocationButton = async (jid, text, displayText, footer = '') => {
1693
+ const locationButton = createLocationButton(displayText);
1694
+ const message = createButtonsMessage(text, footer, [locationButton]);
1695
+ return await sendMessage(jid, message);
1696
+ };
1697
+
1698
+ const sendContactButton = async (jid, text, displayText, footer = '') => {
1699
+ const contactButton = createContactButton(displayText);
1700
+ const message = createButtonsMessage(text, footer, [contactButton]);
1701
+ return await sendMessage(jid, message);
1702
+ };
1703
+
1704
+ const sendTemplateMessage = async (jid, text, templateId, buttons, footer = '') => {
1705
+ const message = createTemplateMessage(text, footer, templateId, buttons);
1706
+ return await sendMessage(jid, message);
1707
+ };
1708
+
1709
+ const sendCatalogMessage = async (jid, businessOwnerJid, catalogItems) => {
1710
+ const message = createCatalogMessage(businessOwnerJid, catalogItems);
1711
+ return await sendMessage(jid, message);
1712
+ };
1713
+
1714
+ const sendProductMessage = async (jid, productId, businessOwnerJid, catalogId) => {
1715
+ const message = createProductMessage(productId, businessOwnerJid, catalogId);
1716
+ return await sendMessage(jid, message);
1717
+ };
1718
+
1719
+ const sendCollectionMessage = async (jid, businessOwnerJid, catalogId) => {
1720
+ const message = createCollectionMessage(businessOwnerJid, catalogId);
1721
+ return await sendMessage(jid, message);
1722
+ };
1723
+
1724
+ const createNativeFlowButton = (flowId, displayText, flowData) => ({
1725
+ type: 'flow',
1726
+ flow: {
1727
+ flowId: flowId,
1728
+ displayText: displayText || 'Continue',
1729
+ flowData: flowData || {}
1588
1730
  }
1589
- return fullMsg;
1590
- }
1591
- }
1592
- };
1593
- };
1731
+ });
1732
+
1733
+ const sendNativeFlow = async (jid, body, footer, flowId, displayText, flowData = {}, header = null) => {
1734
+ const action = {
1735
+ buttons: [{
1736
+ type: 'flow',
1737
+ flow: {
1738
+ flowId: flowId,
1739
+ displayText: displayText || 'Continue',
1740
+ flowData: flowData
1741
+ }
1742
+ }]
1743
+ };
1744
+ const message = createInteractiveMessage(body, footer, header, action);
1745
+ return await sendMessage(jid, message);
1746
+ };
1747
+
1748
+ const createCarouselCard = (cardId, title, subtitle, body, footer, buttons, media = null) => ({
1749
+ cardId: cardId || `card_${Date.now()}`,
1750
+ header: {
1751
+ title: title || '',
1752
+ subtitle: subtitle || '',
1753
+ hasMedia: !!media,
1754
+ media: media || null
1755
+ },
1756
+ body: { text: body || '' },
1757
+ footer: footer ? { text: footer } : undefined,
1758
+ buttons: buttons || []
1759
+ });
1760
+
1761
+ const sendCarousel = async (jid, body, footer, cards, header = null) => {
1762
+ const action = {
1763
+ carousel: {
1764
+ cards: cards.map(card => ({
1765
+ cardId: card.cardId,
1766
+ header: {
1767
+ title: card.header.title,
1768
+ subtitle: card.header.subtitle || '',
1769
+ hasMedia: card.header.hasMedia || false,
1770
+ media: card.header.media || null
1771
+ },
1772
+ body: { text: card.body.text },
1773
+ footer: card.footer || undefined,
1774
+ buttons: card.buttons || []
1775
+ }))
1776
+ }
1777
+ };
1778
+ const message = createInteractiveMessage(body, footer, header, action);
1779
+ return await sendMessage(jid, message);
1780
+ };
1781
+
1782
+ const sendMultiProductMessage = async (jid, businessOwnerJid, catalogId, products, body = 'Browse our products', footer = '', header = null) => {
1783
+ const action = {
1784
+ catalog: {
1785
+ businessOwnerJid: businessOwnerJid,
1786
+ catalogId: catalogId,
1787
+ products: products.map(p => ({
1788
+ productId: p.id,
1789
+ title: p.title,
1790
+ description: p.description || '',
1791
+ price: p.price || '0',
1792
+ currency: p.currency || 'PKR',
1793
+ image: p.image || null
1794
+ }))
1795
+ }
1796
+ };
1797
+ const message = createInteractiveMessage(body, footer, header, action);
1798
+ return await sendMessage(jid, message);
1799
+ };
1800
+
1801
+ const sendSingleProductMessage = async (jid, productId, businessOwnerJid, catalogId) => {
1802
+ const message = {
1803
+ productMessage: {
1804
+ productId: productId,
1805
+ businessOwnerJid: businessOwnerJid,
1806
+ catalogId: catalogId
1807
+ }
1808
+ };
1809
+ return await sendMessage(jid, message);
1810
+ };
1811
+
1812
+ const sendSectionList = async (jid, title, buttonText, sections, body = 'Choose an option', footer = '', header = null) => {
1813
+ const action = {
1814
+ sections: sections.map(section => ({
1815
+ title: section.title,
1816
+ rows: section.rows.map(row => ({
1817
+ id: row.id || `row_${Date.now()}`,
1818
+ title: row.title,
1819
+ description: row.description || ''
1820
+ }))
1821
+ })),
1822
+ button: buttonText || 'Select'
1823
+ };
1824
+ const message = createInteractiveMessage(body, footer, header, action);
1825
+ return await sendMessage(jid, message);
1826
+ };
1827
+
1828
+ const sendReplyButton = async (jid, text, buttonText, buttonId, footer = '', header = null) => {
1829
+ const action = {
1830
+ buttons: [{
1831
+ type: 'reply',
1832
+ reply: {
1833
+ id: buttonId || `reply_${Date.now()}`,
1834
+ title: buttonText
1835
+ }
1836
+ }]
1837
+ };
1838
+ const message = createInteractiveMessage(text, footer, header, action);
1839
+ return await sendMessage(jid, message);
1840
+ };
1841
+
1842
+ const sendCTAUrl = async (jid, text, displayText, url, footer = '', header = null) => {
1843
+ const action = {
1844
+ buttons: [{
1845
+ type: 'url',
1846
+ url: {
1847
+ displayText: displayText,
1848
+ url: url
1849
+ }
1850
+ }]
1851
+ };
1852
+ const message = createInteractiveMessage(text, footer, header, action);
1853
+ return await sendMessage(jid, message);
1854
+ };
1855
+
1856
+ const sendCTAPhone = async (jid, text, displayText, phoneNumber, footer = '', header = null) => {
1857
+ const action = {
1858
+ buttons: [{
1859
+ type: 'call',
1860
+ call: {
1861
+ displayText: displayText,
1862
+ phoneNumber: phoneNumber
1863
+ }
1864
+ }]
1865
+ };
1866
+ const message = createInteractiveMessage(text, footer, header, action);
1867
+ return await sendMessage(jid, message);
1868
+ };
1869
+
1870
+ const sendCopyCodeButton = async (jid, text, code, buttonText = 'Copy Code', footer = '', header = null) => {
1871
+ const action = {
1872
+ buttons: [{
1873
+ type: 'copy',
1874
+ copy: {
1875
+ displayText: buttonText,
1876
+ code: code
1877
+ }
1878
+ }]
1879
+ };
1880
+ const message = createInteractiveMessage(text, footer, header, action);
1881
+ return await sendMessage(jid, message);
1882
+ };
1883
+
1884
+ const sendFlowButton = async (jid, text, flowId, flowData = {}, buttonText = 'Continue', footer = '', header = null) => {
1885
+ const action = {
1886
+ buttons: [{
1887
+ type: 'flow',
1888
+ flow: {
1889
+ flowId: flowId,
1890
+ displayText: buttonText,
1891
+ flowData: flowData
1892
+ }
1893
+ }]
1894
+ };
1895
+ const message = createInteractiveMessage(text, footer, header, action);
1896
+ return await sendMessage(jid, message);
1897
+ };
1898
+
1899
+ const sendOptOutButton = async (jid, text, footer = '', header = null) => {
1900
+ const action = {
1901
+ buttons: [{
1902
+ type: 'optout',
1903
+ optout: {
1904
+ displayText: 'Unsubscribe'
1905
+ }
1906
+ }]
1907
+ };
1908
+ const message = createInteractiveMessage(text, footer, header, action);
1909
+ return await sendMessage(jid, message);
1910
+ };
1911
+
1912
+ const sendOneTapButton = async (jid, text, authCode, buttonText = 'Authenticate', footer = '', header = null) => {
1913
+ const action = {
1914
+ buttons: [{
1915
+ type: 'onetime',
1916
+ onetime: {
1917
+ displayText: buttonText,
1918
+ code: authCode
1919
+ }
1920
+ }]
1921
+ };
1922
+ const message = createInteractiveMessage(text, footer, header, action);
1923
+ return await sendMessage(jid, message);
1924
+ };
1925
+
1926
+ const sendZeroTapButton = async (jid, text, authToken, footer = '', header = null) => {
1927
+ const message = {
1928
+ protocolMessage: {
1929
+ type: proto.Message.ProtocolMessage.Type.PEER_DATA_OPERATION_REQUEST_MESSAGE,
1930
+ peerDataOperationRequestMessage: {
1931
+ zeroTap: {
1932
+ displayText: text,
1933
+ token: authToken
1934
+ }
1935
+ }
1936
+ }
1937
+ };
1938
+ return await sendMessage(jid, message);
1939
+ };
1940
+
1941
+ const sendAuthButton = async (jid, text, authType, authData, buttonText = 'Authenticate', footer = '', header = null) => {
1942
+ const action = {
1943
+ buttons: [{
1944
+ type: 'auth',
1945
+ auth: {
1946
+ displayText: buttonText,
1947
+ authType: authType,
1948
+ authData: authData
1949
+ }
1950
+ }]
1951
+ };
1952
+ const message = createInteractiveMessage(text, footer, header, action);
1953
+ return await sendMessage(jid, message);
1954
+ };
1955
+
1956
+ const sendOTPButton = async (jid, text, otp, buttonText = 'Verify OTP', footer = '', header = null) => {
1957
+ const action = {
1958
+ buttons: [{
1959
+ type: 'otp',
1960
+ otp: {
1961
+ displayText: buttonText,
1962
+ otp: otp
1963
+ }
1964
+ }]
1965
+ };
1966
+ const message = createInteractiveMessage(text, footer, header, action);
1967
+ return await sendMessage(jid, message);
1968
+ };
1969
+
1970
+ const sendPaymentButton = async (jid, text, amount, currency = 'PKR', paymentId, buttonText = 'Pay Now', footer = '', header = null) => {
1971
+ const action = {
1972
+ buttons: [{
1973
+ type: 'payment',
1974
+ payment: {
1975
+ displayText: buttonText,
1976
+ amount: amount,
1977
+ currency: currency,
1978
+ paymentId: paymentId || `pay_${Date.now()}`
1979
+ }
1980
+ }]
1981
+ };
1982
+ const message = createInteractiveMessage(text, footer, header, action);
1983
+ return await sendMessage(jid, message);
1984
+ };
1985
+
1986
+ const sendScheduleButton = async (jid, text, scheduleId, startTime, endTime, buttonText = 'Schedule', footer = '', header = null) => {
1987
+ const action = {
1988
+ buttons: [{
1989
+ type: 'schedule',
1990
+ schedule: {
1991
+ displayText: buttonText,
1992
+ scheduleId: scheduleId || `sch_${Date.now()}`,
1993
+ startTime: startTime,
1994
+ endTime: endTime
1995
+ }
1996
+ }]
1997
+ };
1998
+ const message = createInteractiveMessage(text, footer, header, action);
1999
+ return await sendMessage(jid, message);
2000
+ };
2001
+
2002
+ const sendFeedbackButton = async (jid, text, feedbackId, ratingType = 'stars', buttonText = 'Give Feedback', footer = '', header = null) => {
2003
+ const action = {
2004
+ buttons: [{
2005
+ type: 'feedback',
2006
+ feedback: {
2007
+ displayText: buttonText,
2008
+ feedbackId: feedbackId || `fb_${Date.now()}`,
2009
+ ratingType: ratingType
2010
+ }
2011
+ }]
2012
+ };
2013
+ const message = createInteractiveMessage(text, footer, header, action);
2014
+ return await sendMessage(jid, message);
2015
+ };
2016
+
2017
+ const sendShareButton = async (jid, text, shareTarget, shareData, buttonText = 'Share', footer = '', header = null) => {
2018
+ const action = {
2019
+ buttons: [{
2020
+ type: 'share',
2021
+ share: {
2022
+ displayText: buttonText,
2023
+ shareTarget: shareTarget,
2024
+ shareData: shareData
2025
+ }
2026
+ }]
2027
+ };
2028
+ const message = createInteractiveMessage(text, footer, header, action);
2029
+ return await sendMessage(jid, message);
2030
+ };
2031
+
2032
+ const sendDownloadButton = async (jid, text, fileId, fileType, fileSize, buttonText = 'Download', footer = '', header = null) => {
2033
+ const action = {
2034
+ buttons: [{
2035
+ type: 'download',
2036
+ download: {
2037
+ displayText: buttonText,
2038
+ fileId: fileId || `dl_${Date.now()}`,
2039
+ fileType: fileType,
2040
+ fileSize: fileSize
2041
+ }
2042
+ }]
2043
+ };
2044
+ const message = createInteractiveMessage(text, footer, header, action);
2045
+ return await sendMessage(jid, message);
2046
+ };
2047
+
2048
+ const sendUploadButton = async (jid, text, uploadType, maxSize, acceptedFormats, buttonText = 'Upload', footer = '', header = null) => {
2049
+ const action = {
2050
+ buttons: [{
2051
+ type: 'upload',
2052
+ upload: {
2053
+ displayText: buttonText,
2054
+ uploadType: uploadType,
2055
+ maxSize: maxSize,
2056
+ acceptedFormats: acceptedFormats
2057
+ }
2058
+ }]
2059
+ };
2060
+ const message = createInteractiveMessage(text, footer, header, action);
2061
+ return await sendMessage(jid, message);
2062
+ };
2063
+
2064
+ const sendVerifyButton = async (jid, text, verifyType, verificationId, buttonText = 'Verify', footer = '', header = null) => {
2065
+ const action = {
2066
+ buttons: [{
2067
+ type: 'verify',
2068
+ verify: {
2069
+ displayText: buttonText,
2070
+ verifyType: verifyType,
2071
+ verificationId: verificationId || `vf_${Date.now()}`
2072
+ }
2073
+ }]
2074
+ };
2075
+ const message = createInteractiveMessage(text, footer, header, action);
2076
+ return await sendMessage(jid, message);
2077
+ };
2078
+
2079
+ const sendApproveRejectButton = async (jid, text, approvalId, approvalType, approveText = 'Approve', rejectText = 'Reject', footer = '', header = null) => {
2080
+ const action = {
2081
+ buttons: [
2082
+ {
2083
+ type: 'approve',
2084
+ approve: {
2085
+ displayText: approveText,
2086
+ approvalId: approvalId || `app_${Date.now()}`,
2087
+ approvalType: approvalType
2088
+ }
2089
+ },
2090
+ {
2091
+ type: 'reject',
2092
+ reject: {
2093
+ displayText: rejectText,
2094
+ approvalId: approvalId || `app_${Date.now()}`,
2095
+ reason: ''
2096
+ }
2097
+ }
2098
+ ]
2099
+ };
2100
+ const message = createInteractiveMessage(text, footer, header, action);
2101
+ return await sendMessage(jid, message);
2102
+ };
2103
+
2104
+ const sendForwardButton = async (jid, text, forwardId, forwardType, buttonText = 'Forward', footer = '', header = null) => {
2105
+ const action = {
2106
+ buttons: [{
2107
+ type: 'forward',
2108
+ forward: {
2109
+ displayText: buttonText,
2110
+ forwardId: forwardId || `fw_${Date.now()}`,
2111
+ forwardType: forwardType
2112
+ }
2113
+ }]
2114
+ };
2115
+ const message = createInteractiveMessage(text, footer, header, action);
2116
+ return await sendMessage(jid, message);
2117
+ };
2118
+
2119
+ const sendMuteUnmuteButton = async (jid, text, conversationId, muteText = 'Mute', unmuteText = 'Unmute', footer = '', header = null) => {
2120
+ const action = {
2121
+ buttons: [
2122
+ {
2123
+ type: 'mute',
2124
+ mute: {
2125
+ displayText: muteText,
2126
+ conversationId: conversationId || `conv_${Date.now()}`,
2127
+ duration: 3600
2128
+ }
2129
+ },
2130
+ {
2131
+ type: 'unmute',
2132
+ unmute: {
2133
+ displayText: unmuteText,
2134
+ conversationId: conversationId || `conv_${Date.now()}`
2135
+ }
2136
+ }
2137
+ ]
2138
+ };
2139
+ const message = createInteractiveMessage(text, footer, header, action);
2140
+ return await sendMessage(jid, message);
2141
+ };
2142
+
2143
+ const sendArchiveUnarchiveButton = async (jid, text, archiveId, archiveType, archiveText = 'Archive', unarchiveText = 'Unarchive', footer = '', header = null) => {
2144
+ const action = {
2145
+ buttons: [
2146
+ {
2147
+ type: 'archive',
2148
+ archive: {
2149
+ displayText: archiveText,
2150
+ archiveId: archiveId || `arch_${Date.now()}`,
2151
+ archiveType: archiveType
2152
+ }
2153
+ },
2154
+ {
2155
+ type: 'unarchive',
2156
+ unarchive: {
2157
+ displayText: unarchiveText,
2158
+ archiveId: archiveId || `arch_${Date.now()}`
2159
+ }
2160
+ }
2161
+ ]
2162
+ };
2163
+ const message = createInteractiveMessage(text, footer, header, action);
2164
+ return await sendMessage(jid, message);
2165
+ };
2166
+
2167
+ const sendPinUnpinButton = async (jid, text, pinId, pinType, pinText = 'Pin', unpinText = 'Unpin', duration = 86400, footer = '', header = null) => {
2168
+ const action = {
2169
+ buttons: [
2170
+ {
2171
+ type: 'pin',
2172
+ pin: {
2173
+ displayText: pinText,
2174
+ pinId: pinId || `pin_${Date.now()}`,
2175
+ pinType: pinType,
2176
+ duration: duration
2177
+ }
2178
+ },
2179
+ {
2180
+ type: 'unpin',
2181
+ unpin: {
2182
+ displayText: unpinText,
2183
+ pinId: pinId || `pin_${Date.now()}`
2184
+ }
2185
+ }
2186
+ ]
2187
+ };
2188
+ const message = createInteractiveMessage(text, footer, header, action);
2189
+ return await sendMessage(jid, message);
2190
+ };
2191
+
2192
+ const sendBookmarkButton = async (jid, text, bookmarkId, bookmarkCategory, bookmarkText = 'Bookmark', unbookmarkText = 'Remove', footer = '', header = null) => {
2193
+ const action = {
2194
+ buttons: [
2195
+ {
2196
+ type: 'bookmark',
2197
+ bookmark: {
2198
+ displayText: bookmarkText,
2199
+ bookmarkId: bookmarkId || `bm_${Date.now()}`,
2200
+ bookmarkCategory: bookmarkCategory
2201
+ }
2202
+ },
2203
+ {
2204
+ type: 'unbookmark',
2205
+ unbookmark: {
2206
+ displayText: unbookmarkText,
2207
+ bookmarkId: bookmarkId || `bm_${Date.now()}`
2208
+ }
2209
+ }
2210
+ ]
2211
+ };
2212
+ const message = createInteractiveMessage(text, footer, header, action);
2213
+ return await sendMessage(jid, message);
2214
+ };
2215
+
2216
+ const sendFlagUnflagButton = async (jid, text, flagId, flagCategory, flagText = 'Flag', unflagText = 'Unflag', priority = 'normal', footer = '', header = null) => {
2217
+ const action = {
2218
+ buttons: [
2219
+ {
2220
+ type: 'flag',
2221
+ flag: {
2222
+ displayText: flagText,
2223
+ flagId: flagId || `fl_${Date.now()}`,
2224
+ flagCategory: flagCategory,
2225
+ priority: priority
2226
+ }
2227
+ },
2228
+ {
2229
+ type: 'unflag',
2230
+ unflag: {
2231
+ displayText: unflagText,
2232
+ flagId: flagId || `fl_${Date.now()}`
2233
+ }
2234
+ }
2235
+ ]
2236
+ };
2237
+ const message = createInteractiveMessage(text, footer, header, action);
2238
+ return await sendMessage(jid, message);
2239
+ };
2240
+
2241
+ const sendLockUnlockButton = async (jid, text, lockId, lockType, lockText = 'Lock', unlockText = 'Unlock', duration = 3600, footer = '', header = null) => {
2242
+ const action = {
2243
+ buttons: [
2244
+ {
2245
+ type: 'lock',
2246
+ lock: {
2247
+ displayText: lockText,
2248
+ lockId: lockId || `lk_${Date.now()}`,
2249
+ lockType: lockType,
2250
+ duration: duration
2251
+ }
2252
+ },
2253
+ {
2254
+ type: 'unlock',
2255
+ unlock: {
2256
+ displayText: unlockText,
2257
+ lockId: lockId || `lk_${Date.now()}`
2258
+ }
2259
+ }
2260
+ ]
2261
+ };
2262
+ const message = createInteractiveMessage(text, footer, header, action);
2263
+ return await sendMessage(jid, message);
2264
+ };
2265
+
2266
+ const sendAddressRequestButton = async (jid, body, buttonText = 'Share Address', footer = '', header = null) => {
2267
+ const action = {
2268
+ buttons: [{
2269
+ type: 'address',
2270
+ address: {
2271
+ displayText: buttonText
2272
+ }
2273
+ }]
2274
+ };
2275
+ const message = createInteractiveMessage(body, footer, header, action);
2276
+ return await sendMessage(jid, message);
2277
+ };
2278
+
2279
+ const sendLocationRequestButton = async (jid, body, buttonText = 'Share Location', footer = '', header = null) => {
2280
+ const action = {
2281
+ buttons: [{
2282
+ type: 'location',
2283
+ location: {
2284
+ displayText: buttonText
2285
+ }
2286
+ }]
2287
+ };
2288
+ const message = createInteractiveMessage(body, footer, header, action);
2289
+ return await sendMessage(jid, message);
2290
+ };
2291
+
2292
+ const sendVoiceCallButton = async (jid, body, phoneNumber, buttonText = 'Voice Call', footer = '', header = null) => {
2293
+ const action = {
2294
+ buttons: [{
2295
+ type: 'voice',
2296
+ voice: {
2297
+ displayText: buttonText,
2298
+ phoneNumber: phoneNumber
2299
+ }
2300
+ }]
2301
+ };
2302
+ const message = createInteractiveMessage(body, footer, header, action);
2303
+ return await sendMessage(jid, message);
2304
+ };
2305
+
2306
+ const sendVideoCallButton = async (jid, body, phoneNumber, buttonText = 'Video Call', footer = '', header = null) => {
2307
+ const action = {
2308
+ buttons: [{
2309
+ type: 'video',
2310
+ video: {
2311
+ displayText: buttonText,
2312
+ phoneNumber: phoneNumber
2313
+ }
2314
+ }]
2315
+ };
2316
+ const message = createInteractiveMessage(body, footer, header, action);
2317
+ return await sendMessage(jid, message);
2318
+ };
2319
+
2320
+ const sendImageButton = async (jid, body, imageUrl, buttonText = 'View Image', footer = '', header = null) => {
2321
+ const action = {
2322
+ buttons: [{
2323
+ type: 'image',
2324
+ image: {
2325
+ displayText: buttonText,
2326
+ url: imageUrl
2327
+ }
2328
+ }]
2329
+ };
2330
+ const message = createInteractiveMessage(body, footer, header, action);
2331
+ return await sendMessage(jid, message);
2332
+ };
2333
+
2334
+ const sendDocumentButton = async (jid, body, documentUrl, fileName, buttonText = 'View Document', footer = '', header = null) => {
2335
+ const action = {
2336
+ buttons: [{
2337
+ type: 'document',
2338
+ document: {
2339
+ displayText: buttonText,
2340
+ url: documentUrl,
2341
+ fileName: fileName
2342
+ }
2343
+ }]
2344
+ };
2345
+ const message = createInteractiveMessage(body, footer, header, action);
2346
+ return await sendMessage(jid, message);
2347
+ };
2348
+
2349
+ const sendAudioButton = async (jid, body, audioUrl, buttonText = 'Play Audio', footer = '', header = null) => {
2350
+ const action = {
2351
+ buttons: [{
2352
+ type: 'audio',
2353
+ audio: {
2354
+ displayText: buttonText,
2355
+ url: audioUrl
2356
+ }
2357
+ }]
2358
+ };
2359
+ const message = createInteractiveMessage(body, footer, header, action);
2360
+ return await sendMessage(jid, message);
2361
+ };
2362
+
2363
+ const sendStickerButton = async (jid, body, stickerUrl, buttonText = 'View Sticker', footer = '', header = null) => {
2364
+ const action = {
2365
+ buttons: [{
2366
+ type: 'sticker',
2367
+ sticker: {
2368
+ displayText: buttonText,
2369
+ url: stickerUrl
2370
+ }
2371
+ }]
2372
+ };
2373
+ const message = createInteractiveMessage(body, footer, header, action);
2374
+ return await sendMessage(jid, message);
2375
+ };
2376
+
2377
+ const sendInteractiveNativeFlow = async (jid, body, footer, flowId, flowData = {}, buttonText = 'Continue', header = null) => {
2378
+ const action = {
2379
+ buttons: [{
2380
+ type: 'flow',
2381
+ flow: {
2382
+ flowId: flowId,
2383
+ displayText: buttonText,
2384
+ flowData: flowData
2385
+ }
2386
+ }]
2387
+ };
2388
+ const message = createInteractiveMessage(body, footer, header, action);
2389
+ return await sendMessage(jid, message);
2390
+ };
2391
+
2392
+ const sendCollectShoppingInterest = async (jid, productCategory, buttonText = 'I\'m Interested', footer = '', header = null) => {
2393
+ const action = {
2394
+ buttons: [{
2395
+ type: 'interest',
2396
+ interest: {
2397
+ displayText: buttonText,
2398
+ category: productCategory
2399
+ }
2400
+ }]
2401
+ };
2402
+ const message = createInteractiveMessage(`Are you interested in ${productCategory}?`, footer, header, action);
2403
+ return await sendMessage(jid, message);
2404
+ };
2405
+
2406
+ const sendGetFeedback = async (jid, feedbackType, buttonText = 'Give Feedback', footer = '', header = null) => {
2407
+ const action = {
2408
+ buttons: [{
2409
+ type: 'feedback',
2410
+ feedback: {
2411
+ displayText: buttonText,
2412
+ feedbackType: feedbackType
2413
+ }
2414
+ }]
2415
+ };
2416
+ const message = createInteractiveMessage(`Please provide your feedback on ${feedbackType}`, footer, header, action);
2417
+ return await sendMessage(jid, message);
2418
+ };
2419
+
2420
+ const sendSurveyButton = async (jid, surveyId, surveyQuestion, buttonText = 'Take Survey', footer = '', header = null) => {
2421
+ const action = {
2422
+ buttons: [{
2423
+ type: 'survey',
2424
+ survey: {
2425
+ displayText: buttonText,
2426
+ surveyId: surveyId || `sv_${Date.now()}`,
2427
+ question: surveyQuestion
2428
+ }
2429
+ }]
2430
+ };
2431
+ const message = createInteractiveMessage(surveyQuestion, footer, header, action);
2432
+ return await sendMessage(jid, message);
2433
+ };
2434
+
2435
+ const sendCustomerSupportButton = async (jid, supportTopic, buttonText = 'Contact Support', footer = '', header = null) => {
2436
+ const action = {
2437
+ buttons: [{
2438
+ type: 'support',
2439
+ support: {
2440
+ displayText: buttonText,
2441
+ topic: supportTopic
2442
+ }
2443
+ }]
2444
+ };
2445
+ const message = createInteractiveMessage(`Need help with ${supportTopic}?`, footer, header, action);
2446
+ return await sendMessage(jid, message);
2447
+ };
2448
+
2449
+ const sendReminderButton = async (jid, reminderText, buttonText = 'Set Reminder', footer = '', header = null) => {
2450
+ const action = {
2451
+ buttons: [{
2452
+ type: 'reminder',
2453
+ reminder: {
2454
+ displayText: buttonText,
2455
+ text: reminderText
2456
+ }
2457
+ }]
2458
+ };
2459
+ const message = createInteractiveMessage(`Reminder: ${reminderText}`, footer, header, action);
2460
+ return await sendMessage(jid, message);
2461
+ };
2462
+
2463
+ const sendConfirmButton = async (jid, actionText, buttonText = 'Confirm', footer = '', header = null) => {
2464
+ const action = {
2465
+ buttons: [{
2466
+ type: 'confirm',
2467
+ confirm: {
2468
+ displayText: buttonText,
2469
+ action: actionText
2470
+ }
2471
+ }]
2472
+ };
2473
+ const message = createInteractiveMessage(`Confirm: ${actionText}?`, footer, header, action);
2474
+ return await sendMessage(jid, message);
2475
+ };
2476
+
2477
+ const sendCancelButton = async (jid, actionText, buttonText = 'Cancel', footer = '', header = null) => {
2478
+ const action = {
2479
+ buttons: [{
2480
+ type: 'cancel',
2481
+ cancel: {
2482
+ displayText: buttonText,
2483
+ action: actionText
2484
+ }
2485
+ }]
2486
+ };
2487
+ const message = createInteractiveMessage(`Cancel: ${actionText}?`, footer, header, action);
2488
+ return await sendMessage(jid, message);
2489
+ };
2490
+
2491
+ const sendOrderStatusButton = async (jid, orderId, buttonText = 'Check Status', footer = '', header = null) => {
2492
+ const action = {
2493
+ buttons: [{
2494
+ type: 'orderstatus',
2495
+ orderstatus: {
2496
+ displayText: buttonText,
2497
+ orderId: orderId
2498
+ }
2499
+ }]
2500
+ };
2501
+ const message = createInteractiveMessage(`Check status for order #${orderId}`, footer, header, action);
2502
+ return await sendMessage(jid, message);
2503
+ };
2504
+
2505
+ const sendTrackOrderButton = async (jid, orderId, buttonText = 'Track Order', footer = '', header = null) => {
2506
+ const action = {
2507
+ buttons: [{
2508
+ type: 'track',
2509
+ track: {
2510
+ displayText: buttonText,
2511
+ orderId: orderId
2512
+ }
2513
+ }]
2514
+ };
2515
+ const message = createInteractiveMessage(`Track order #${orderId}`, footer, header, action);
2516
+ return await sendMessage(jid, message);
2517
+ };
2518
+
2519
+ const sendReOrderButton = async (jid, orderId, buttonText = 'Reorder', footer = '', header = null) => {
2520
+ const action = {
2521
+ buttons: [{
2522
+ type: 'reorder',
2523
+ reorder: {
2524
+ displayText: buttonText,
2525
+ orderId: orderId
2526
+ }
2527
+ }]
2528
+ };
2529
+ const message = createInteractiveMessage(`Reorder #${orderId}?`, footer, header, action);
2530
+ return await sendMessage(jid, message);
2531
+ };
2532
+
2533
+ const sendCancelOrderButton = async (jid, orderId, buttonText = 'Cancel Order', footer = '', header = null) => {
2534
+ const action = {
2535
+ buttons: [{
2536
+ type: 'cancelorder',
2537
+ cancelorder: {
2538
+ displayText: buttonText,
2539
+ orderId: orderId
2540
+ }
2541
+ }]
2542
+ };
2543
+ const message = createInteractiveMessage(`Cancel order #${orderId}?`, footer, header, action);
2544
+ return await sendMessage(jid, message);
2545
+ };
2546
+
2547
+ registerSocketEndHandler(() => {
2548
+ if (!config.userDevicesCache && userDevicesCache.close) {
2549
+ userDevicesCache.close();
2550
+ }
2551
+ mediaConn = undefined;
2552
+ if (messageRetryManager) {
2553
+ messageRetryManager.clear();
2554
+ }
2555
+ });
2556
+
2557
+ return {
2558
+ ...sock,
2559
+ userDevicesCache,
2560
+ devicesMutex,
2561
+ issuePrivacyTokens,
2562
+ assertSessions,
2563
+ relayMessage,
2564
+ sendReceipt,
2565
+ sendReceipts,
2566
+ readMessages,
2567
+ refreshMediaConn,
2568
+ getMediaHost: () => mediaHost,
2569
+ waUploadToServer,
2570
+ fetchPrivacySettings,
2571
+ sendPeerDataOperationMessage,
2572
+ createParticipantNodes,
2573
+ getUSyncDevices,
2574
+ messageRetryManager,
2575
+ updateMemberLabel,
2576
+ updateMediaMessage: async (message) => {
2577
+ const content = assertMediaContent(message.message);
2578
+ const mediaKey = content.mediaKey;
2579
+ const meId = authState.creds.me.id;
2580
+ const node = encryptMediaRetryRequest(message.key, mediaKey, meId);
2581
+ let error = undefined;
2582
+ await Promise.all([
2583
+ sendNode(node),
2584
+ waitForMsgMediaUpdate(async (update) => {
2585
+ const result = update.find(c => c.key.id === message.key.id);
2586
+ if (result) {
2587
+ if (result.error) {
2588
+ error = result.error;
2589
+ }
2590
+ else {
2591
+ try {
2592
+ const media = decryptMediaRetryData(result.media, mediaKey, result.key.id);
2593
+ if (media.result !== proto.MediaRetryNotification.ResultType.SUCCESS) {
2594
+ const resultStr = proto.MediaRetryNotification.ResultType[media.result];
2595
+ throw new Boom(`Media re-upload failed by device (${resultStr})`, {
2596
+ data: media,
2597
+ statusCode: getStatusCodeForMediaRetry(media.result) || 404
2598
+ });
2599
+ }
2600
+ content.directPath = media.directPath;
2601
+ content.url = getUrlFromDirectPath(content.directPath, mediaHost);
2602
+ logger.debug({ directPath: media.directPath, key: result.key }, 'media update successful');
2603
+ }
2604
+ catch (err) {
2605
+ error = err;
2606
+ }
2607
+ }
2608
+ return true;
2609
+ }
2610
+ })
2611
+ ]);
2612
+ if (error) {
2613
+ throw error;
2614
+ }
2615
+ ev.emit('messages.update', [{ key: message.key, update: { message: message.message } }]);
2616
+ return message;
2617
+ },
2618
+ richMenu,
2619
+ sendRichResponseMessage,
2620
+ createQuickReplyButton,
2621
+ createUrlButton,
2622
+ createCallButton,
2623
+ createLocationButton,
2624
+ createContactButton,
2625
+ createPollButton,
2626
+ createInteractiveReplyButton,
2627
+ createInteractiveUrlButton,
2628
+ createInteractiveCallButton,
2629
+ createInteractiveCatalogButton,
2630
+ createListMessage,
2631
+ createListSection,
2632
+ createButtonsMessage,
2633
+ createButtonsMessageWithHeader,
2634
+ createButtonsMessageWithImageHeader,
2635
+ createButtonsMessageWithDocumentHeader,
2636
+ createTemplateMessage,
2637
+ createCatalogMessage,
2638
+ createProductMessage,
2639
+ createCollectionMessage,
2640
+ createOrderMessage,
2641
+ createInteractiveMessage,
2642
+ createInteractiveButtonsAction,
2643
+ createInteractiveListAction,
2644
+ createInteractiveCatalogAction,
2645
+ sendQuickReplyButtons,
2646
+ sendUrlButtons,
2647
+ sendMixedButtons,
2648
+ sendButtonsWithHeader,
2649
+ sendButtonsWithImageHeader,
2650
+ sendButtonsWithDocumentHeader,
2651
+ sendListMessage,
2652
+ sendInteractiveButtons,
2653
+ sendInteractiveList,
2654
+ sendInteractiveCatalog,
2655
+ sendPollMessage,
2656
+ sendCallButton,
2657
+ sendLocationButton,
2658
+ sendContactButton,
2659
+ sendTemplateMessage,
2660
+ sendCatalogMessage,
2661
+ sendProductMessage,
2662
+ sendCollectionMessage,
2663
+ sendMessage,
2664
+ createNativeFlowButton,
2665
+ sendNativeFlow,
2666
+ createCarouselCard,
2667
+ sendCarousel,
2668
+ sendMultiProductMessage,
2669
+ sendSingleProductMessage,
2670
+ sendSectionList,
2671
+ sendReplyButton,
2672
+ sendCTAUrl,
2673
+ sendCTAPhone,
2674
+ sendCopyCodeButton,
2675
+ sendFlowButton,
2676
+ sendOptOutButton,
2677
+ sendOneTapButton,
2678
+ sendZeroTapButton,
2679
+ sendAuthButton,
2680
+ sendOTPButton,
2681
+ sendPaymentButton,
2682
+ sendScheduleButton,
2683
+ sendFeedbackButton,
2684
+ sendShareButton,
2685
+ sendDownloadButton,
2686
+ sendUploadButton,
2687
+ sendVerifyButton,
2688
+ sendApproveRejectButton,
2689
+ sendForwardButton,
2690
+ sendMuteUnmuteButton,
2691
+ sendArchiveUnarchiveButton,
2692
+ sendPinUnpinButton,
2693
+ sendBookmarkButton,
2694
+ sendFlagUnflagButton,
2695
+ sendLockUnlockButton,
2696
+ sendAddressRequestButton,
2697
+ sendLocationRequestButton,
2698
+ sendVoiceCallButton,
2699
+ sendVideoCallButton,
2700
+ sendImageButton,
2701
+ sendDocumentButton,
2702
+ sendAudioButton,
2703
+ sendStickerButton,
2704
+ sendInteractiveNativeFlow,
2705
+ sendCollectShoppingInterest,
2706
+ sendGetFeedback,
2707
+ sendSurveyButton,
2708
+ sendCustomerSupportButton,
2709
+ sendReminderButton,
2710
+ sendConfirmButton,
2711
+ sendCancelButton,
2712
+ sendOrderStatusButton,
2713
+ sendTrackOrderButton,
2714
+ sendReOrderButton,
2715
+ sendCancelOrderButton
2716
+ };
2717
+ };