@mrlegendbot/baileys 1.2.4 → 1.2.6

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 (214) 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/Menu/index.d.ts +42 -0
  8. package/lib/Menu/index.js +283 -0
  9. package/lib/Signal/Group/ciphertext-message.d.ts +10 -0
  10. package/lib/Signal/Group/ciphertext-message.js +10 -9
  11. package/lib/Signal/Group/group-session-builder.d.ts +15 -0
  12. package/lib/Signal/Group/group-session-builder.js +22 -21
  13. package/lib/Signal/Group/group_cipher.d.ts +17 -0
  14. package/lib/Signal/Group/group_cipher.js +69 -75
  15. package/lib/Signal/Group/index.d.ts +12 -0
  16. package/lib/Signal/Group/index.js +1 -0
  17. package/lib/Signal/Group/keyhelper.d.ts +11 -0
  18. package/lib/Signal/Group/keyhelper.js +10 -9
  19. package/lib/Signal/Group/sender-chain-key.d.ts +14 -0
  20. package/lib/Signal/Group/sender-chain-key.js +22 -21
  21. package/lib/Signal/Group/sender-key-distribution-message.d.ts +17 -0
  22. package/lib/Signal/Group/sender-key-distribution-message.js +60 -51
  23. package/lib/Signal/Group/sender-key-message.d.ts +19 -0
  24. package/lib/Signal/Group/sender-key-message.js +62 -56
  25. package/lib/Signal/Group/sender-key-name.d.ts +18 -0
  26. package/lib/Signal/Group/sender-key-name.js +40 -38
  27. package/lib/Signal/Group/sender-key-record.d.ts +31 -0
  28. package/lib/Signal/Group/sender-key-record.js +34 -33
  29. package/lib/Signal/Group/sender-key-state.d.ts +39 -0
  30. package/lib/Signal/Group/sender-key-state.js +76 -72
  31. package/lib/Signal/Group/sender-message-key.d.ts +12 -0
  32. package/lib/Signal/Group/sender-message-key.js +23 -22
  33. package/lib/Signal/libsignal.d.ts +5 -0
  34. package/lib/Signal/libsignal.js +405 -506
  35. package/lib/Signal/lid-mapping.d.ts +23 -0
  36. package/lib/Signal/lid-mapping.js +258 -276
  37. package/lib/Socket/Client/index.d.ts +3 -0
  38. package/lib/Socket/Client/index.js +1 -0
  39. package/lib/Socket/Client/types.d.ts +16 -0
  40. package/lib/Socket/Client/types.js +7 -6
  41. package/lib/Socket/Client/websocket.d.ts +13 -0
  42. package/lib/Socket/Client/websocket.js +49 -48
  43. package/lib/Socket/business.d.ts +217 -0
  44. package/lib/Socket/business.js +369 -349
  45. package/lib/Socket/chats.d.ts +125 -0
  46. package/lib/Socket/chats.js +1148 -1172
  47. package/lib/Socket/communities.d.ts +273 -0
  48. package/lib/Socket/communities.js +413 -473
  49. package/lib/Socket/groups.d.ts +161 -0
  50. package/lib/Socket/groups.js +331 -387
  51. package/lib/Socket/index.d.ts +267 -0
  52. package/lib/Socket/index.js +19 -6
  53. package/lib/Socket/messages-recv.d.ts +203 -0
  54. package/lib/Socket/messages-recv.js +1719 -1782
  55. package/lib/Socket/messages-send.d.ts +199 -0
  56. package/lib/Socket/messages-send.js +2451 -1504
  57. package/lib/Socket/mex.d.ts +3 -0
  58. package/lib/Socket/mex.js +35 -40
  59. package/lib/Socket/newsletter.d.ts +170 -0
  60. package/lib/Socket/newsletter.js +191 -239
  61. package/lib/Socket/socket.d.ts +59 -0
  62. package/lib/Socket/socket.js +923 -992
  63. package/lib/Types/Auth.d.ts +117 -0
  64. package/lib/Types/Auth.js +1 -0
  65. package/lib/Types/Bussines.d.ts +25 -0
  66. package/lib/Types/Bussines.js +1 -0
  67. package/lib/Types/Call.d.ts +15 -0
  68. package/lib/Types/Call.js +1 -0
  69. package/lib/Types/Chat.d.ts +124 -0
  70. package/lib/Types/Chat.js +8 -1
  71. package/lib/Types/Contact.d.ts +26 -0
  72. package/lib/Types/Contact.js +1 -0
  73. package/lib/Types/Events.d.ts +256 -0
  74. package/lib/Types/Events.js +2 -1
  75. package/lib/Types/GroupMetadata.d.ts +71 -0
  76. package/lib/Types/GroupMetadata.js +1 -0
  77. package/lib/Types/Label.d.ts +47 -0
  78. package/lib/Types/Label.js +22 -20
  79. package/lib/Types/LabelAssociation.d.ts +30 -0
  80. package/lib/Types/LabelAssociation.js +4 -2
  81. package/lib/Types/Message.d.ts +320 -0
  82. package/lib/Types/Message.js +5 -3
  83. package/lib/Types/Mex.d.ts +141 -0
  84. package/lib/Types/Mex.js +31 -26
  85. package/lib/Types/Product.d.ts +79 -0
  86. package/lib/Types/Product.js +1 -0
  87. package/lib/Types/Signal.d.ts +87 -0
  88. package/lib/Types/Signal.js +2 -1
  89. package/lib/Types/Socket.d.ts +136 -0
  90. package/lib/Types/Socket.js +3 -2
  91. package/lib/Types/State.d.ts +97 -0
  92. package/lib/Types/State.js +52 -4
  93. package/lib/Types/USync.d.ts +26 -0
  94. package/lib/Types/USync.js +1 -0
  95. package/lib/Types/index.d.ts +66 -0
  96. package/lib/Types/index.js +11 -10
  97. package/lib/Utils/auth-utils.d.ts +24 -0
  98. package/lib/Utils/auth-utils.js +277 -240
  99. package/lib/Utils/browser-utils.d.ts +4 -0
  100. package/lib/Utils/browser-utils.js +23 -20
  101. package/lib/Utils/business.d.ts +23 -0
  102. package/lib/Utils/business.js +204 -200
  103. package/lib/Utils/chat-utils.d.ts +100 -0
  104. package/lib/Utils/chat-utils.js +810 -836
  105. package/lib/Utils/companion-reg-client-utils.d.ts +17 -0
  106. package/lib/Utils/companion-reg-client-utils.js +25 -23
  107. package/lib/Utils/crypto.d.ts +37 -0
  108. package/lib/Utils/crypto.js +86 -70
  109. package/lib/Utils/decode-wa-message.d.ts +66 -0
  110. package/lib/Utils/decode-wa-message.js +284 -277
  111. package/lib/Utils/event-buffer.d.ts +36 -0
  112. package/lib/Utils/event-buffer.js +595 -592
  113. package/lib/Utils/generics.d.ts +92 -0
  114. package/lib/Utils/generics.js +335 -312
  115. package/lib/Utils/history.d.ts +24 -0
  116. package/lib/Utils/history.js +115 -123
  117. package/lib/Utils/identity-change-handler.d.ts +44 -0
  118. package/lib/Utils/identity-change-handler.js +50 -0
  119. package/lib/Utils/index.d.ts +22 -0
  120. package/lib/Utils/index.js +2 -1
  121. package/lib/Utils/link-preview.d.ts +21 -0
  122. package/lib/Utils/link-preview.js +73 -74
  123. package/lib/Utils/logger.d.ts +12 -0
  124. package/lib/Utils/logger.js +2 -3
  125. package/lib/Utils/lt-hash.d.ts +8 -0
  126. package/lib/Utils/lt-hash.js +6 -0
  127. package/lib/Utils/make-mutex.d.ts +9 -0
  128. package/lib/Utils/make-mutex.js +27 -27
  129. package/lib/Utils/message-retry-manager.d.ts +115 -0
  130. package/lib/Utils/message-retry-manager.js +254 -213
  131. package/lib/Utils/messages-media.d.ts +133 -0
  132. package/lib/Utils/messages-media.js +715 -741
  133. package/lib/Utils/messages.d.ts +91 -0
  134. package/lib/Utils/messages.js +827 -791
  135. package/lib/Utils/noise-handler.d.ts +20 -0
  136. package/lib/Utils/noise-handler.js +187 -215
  137. package/lib/Utils/offline-node-processor.d.ts +17 -0
  138. package/lib/Utils/offline-node-processor.js +40 -0
  139. package/lib/Utils/pre-key-manager.d.ts +28 -0
  140. package/lib/Utils/pre-key-manager.js +98 -69
  141. package/lib/Utils/process-message.d.ts +60 -0
  142. package/lib/Utils/process-message.js +586 -617
  143. package/lib/Utils/reporting-utils.d.ts +11 -0
  144. package/lib/Utils/reporting-utils.js +234 -553
  145. package/lib/Utils/signal.d.ts +47 -0
  146. package/lib/Utils/signal.js +170 -217
  147. package/lib/Utils/stanza-ack.d.ts +11 -0
  148. package/lib/Utils/stanza-ack.js +35 -27
  149. package/lib/Utils/sync-action-utils.d.ts +19 -0
  150. package/lib/Utils/sync-action-utils.js +41 -37
  151. package/lib/Utils/tc-token-utils.d.ts +37 -0
  152. package/lib/Utils/tc-token-utils.js +141 -120
  153. package/lib/Utils/use-multi-file-auth-state.d.ts +13 -0
  154. package/lib/Utils/use-multi-file-auth-state.js +105 -87
  155. package/lib/Utils/validate-connection.d.ts +11 -0
  156. package/lib/Utils/validate-connection.js +188 -190
  157. package/lib/WABinary/constants.d.ts +28 -0
  158. package/lib/WABinary/constants.js +1294 -28
  159. package/lib/WABinary/decode.d.ts +7 -0
  160. package/lib/WABinary/decode.js +249 -245
  161. package/lib/WABinary/encode.d.ts +3 -0
  162. package/lib/WABinary/encode.js +206 -201
  163. package/lib/WABinary/generic-utils.d.ts +15 -0
  164. package/lib/WABinary/generic-utils.js +89 -86
  165. package/lib/WABinary/index.d.ts +6 -0
  166. package/lib/WABinary/index.js +1 -0
  167. package/lib/WABinary/jid-utils.d.ts +48 -0
  168. package/lib/WABinary/jid-utils.js +77 -68
  169. package/lib/WABinary/types.d.ts +19 -0
  170. package/lib/WABinary/types.js +1 -0
  171. package/lib/WAM/BinaryInfo.d.ts +9 -0
  172. package/lib/WAM/BinaryInfo.js +8 -7
  173. package/lib/WAM/constants.d.ts +40 -0
  174. package/lib/WAM/constants.js +22853 -19892
  175. package/lib/WAM/encode.d.ts +3 -0
  176. package/lib/WAM/encode.js +132 -121
  177. package/lib/WAM/index.d.ts +4 -0
  178. package/lib/WAM/index.js +1 -0
  179. package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +10 -0
  180. package/lib/WAUSync/Protocols/USyncContactProtocol.js +43 -46
  181. package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +23 -0
  182. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +48 -47
  183. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +13 -0
  184. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +23 -22
  185. package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +13 -0
  186. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +33 -30
  187. package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts +10 -0
  188. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +20 -19
  189. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts +26 -0
  190. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +44 -53
  191. package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts +10 -0
  192. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +24 -24
  193. package/lib/WAUSync/Protocols/index.d.ts +6 -0
  194. package/lib/WAUSync/Protocols/index.js +1 -0
  195. package/lib/WAUSync/USyncQuery.d.ts +30 -0
  196. package/lib/WAUSync/USyncQuery.js +88 -79
  197. package/lib/WAUSync/USyncUser.d.ts +17 -0
  198. package/lib/WAUSync/USyncUser.js +29 -28
  199. package/lib/WAUSync/index.d.ts +4 -0
  200. package/lib/WAUSync/index.js +1 -0
  201. package/lib/index.d.ts +12 -0
  202. package/lib/index.js +2 -2
  203. package/package.json +86 -22
  204. package/README.upstream.md +0 -1312
  205. package/WAProto/WAProto.json +0 -1
  206. package/WAProto/WAProto.proto +0 -6275
  207. package/WAProto/compiler.js +0 -550
  208. package/lib/Store/index.js +0 -8
  209. package/lib/Store/keyed-db.js +0 -108
  210. package/lib/Store/make-cache-manager-store.js +0 -70
  211. package/lib/Store/make-in-memory-store.js +0 -417
  212. package/lib/Store/make-ordered-dictionary.js +0 -69
  213. package/lib/Store/object-repository.js +0 -27
  214. package/lib/Utils/use-sqlite-auth-state.js +0 -162
@@ -1,8 +1,8 @@
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
- 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';
5
+ import { aggregateMessageKeysNotFromMe, assertMediaContent, assertMeId, bindWaitForEvent, decryptMediaRetryData, DEF_MEDIA_HOST, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, generateMessageIDV2, generateParticipantHashV2, generateWAMessage, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, MessageRetryManager, normalizeMessageContent, parseAndInjectE2ESessions, unixTimestampSeconds } from '../Utils/index.js';
6
6
  import { getUrlInfo } from '../Utils/link-preview.js';
7
7
  import { makeKeyedMutex, makeMutex } from '../Utils/make-mutex.js';
8
8
  import { getMessageReportingToken, shouldIncludeReportingToken } from '../Utils/reporting-utils.js';
@@ -10,1533 +10,2480 @@ 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');
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;
63
+ };
64
+
65
+ const sendReceipt = async (jid, participant, messageIds, type) => {
66
+ if (!messageIds || messageIds.length === 0) {
67
+ throw new Boom('missing ids in receipt');
68
+ }
68
69
  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
- }
97
- };
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');
233
- }
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');
255
- }
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
- }
267
- }
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');
273
- }
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');
287
- }
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()
299
- }
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;
324
- }
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
- };
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
- }
426
- }
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',
70
+ tag: 'receipt',
442
71
  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
72
+ id: messageIds[0]
73
+ }
567
74
  };
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
- };
602
- }
603
- if (isStatus && statusJidList) {
604
- participantsList.push(...statusJidList);
605
- }
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
- };
613
- }
614
- const patched = await patchMessageBeforeSending(message);
615
- if (Array.isArray(patched)) {
616
- throw new Boom('Per-jid patching is not supported in groups');
617
- }
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
- }
638
- }
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
75
+ const isReadReceipt = type === 'read' || type === 'read-self';
76
+ if (isReadReceipt) {
77
+ node.attrs.t = unixTimestampSeconds().toString();
78
+ }
79
+ if (type === 'sender' && (isPnUser(jid) || isLidUser(jid))) {
80
+ node.attrs.recipient = jid;
81
+ node.attrs.to = participant;
82
+ }
83
+ else {
84
+ node.attrs.to = jid;
85
+ if (participant) {
86
+ node.attrs.participant = participant;
647
87
  }
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
- }
88
+ }
89
+ if (type) {
90
+ node.attrs.type = type;
91
+ }
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
+ ];
104
+ }
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);
113
+ }
114
+ };
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');
126
+ }
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);
150
+ }
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
+ }
170
+ }
171
+ if (!toFetch.length) {
172
+ return deviceResults;
173
+ }
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
+ }
181
+ }
182
+ const queryObj = new USyncQuery().withContext('message').withDeviceProtocol().withLIDProtocol();
183
+ for (const jid of toFetch) {
184
+ queryObj.withUser(new USyncUser().withId(jid));
185
+ }
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
+ }
201
+ }
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
+ }
236
+ });
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
+ }
251
+ }
252
+ }
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
+ ]
668
276
  });
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)
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);
292
+ }
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
+ ]
704
318
  });
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
319
+ await parseAndInjectE2ESessions(result, signalRepository);
320
+ didFetchNewSession = true;
321
+ }
322
+ return didFetchNewSession;
323
+ };
324
+
325
+ const sendPeerDataOperationMessage = async (pdoMessage) => {
326
+ if (!authState.creds.me?.id) {
327
+ throw new Boom('Not authenticated');
328
+ }
329
+ const protocolMessage = {
330
+ protocolMessage: {
331
+ peerDataOperationRequestMessage: pdoMessage,
332
+ type: proto.Message.ProtocolMessage.Type.PEER_DATA_OPERATION_REQUEST_MESSAGE
333
+ }
727
334
  };
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) {
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'
340
+ },
341
+ additionalNodes: [
342
+ {
343
+ tag: 'meta',
344
+ attrs: { appdata: 'default' }
345
+ }
346
+ ]
347
+ });
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 }) => {
776
364
  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');
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
+ };
397
+ });
398
+ return node;
793
399
  }
794
- }
795
- }
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
817
- });
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
- });
831
- }
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');
889
- }
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
400
+ catch (err) {
401
+ logger.error({ jid, err }, 'Failed to encrypt for recipient');
402
+ return null;
911
403
  }
912
- });
913
- } catch (err) {
914
- logger.debug({
915
- jid: destinationJid,
916
- err: err?.message
917
- }, 'failed to persist tctoken expiry cleanup');
918
- }
919
- }
920
- if (tcTokenBuffer?.length && sock.serverProps.privacyTokenOn1to1) {
921
- ;
922
- stanza.content.push({
923
- tag: 'tctoken',
924
- attrs: {},
925
- content: tcTokenBuffer
926
404
  });
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');
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 });
408
+ }
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
+ }
949
439
  }
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'
440
+ }
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
990
465
  },
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
466
+ messageContextInfo: message.messageContextInfo
467
+ };
468
+ const extraAttrs = {};
469
+ if (participant) {
470
+ if (!isGroup && !isStatus) {
471
+ additionalAttributes = { ...additionalAttributes, device_fanout: 'false' };
1042
472
  }
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);
1051
- });
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 {
1205
- 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
473
+ const { user, device } = jidDecode(participant.jid);
474
+ devices.push({
475
+ user,
476
+ device,
477
+ jid: participant.jid
478
+ });
479
+ }
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
+ });
539
+ }
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
1212
631
  });
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;
632
+ await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } });
1222
633
  }
1223
- }
1224
- return true;
1225
- }
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
- }
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;
1257
710
  }
1258
- };
1259
- }
1260
- if (title) {
1261
- sex.push({
1262
- __typename: 'GenAIUnifiedResponseSection',
1263
- view_model: {
1264
- __typename: 'GenAISingleLayoutViewModel',
1265
- primitive: {
1266
- __typename: 'FOATextPrimitive',
1267
- text: '# ' + title
1268
- }
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
+ });
1269
763
  }
1270
- });
1271
- }
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
- }
764
+ if (participants.length) {
765
+ if (additionalAttributes?.['category'] === 'peer') {
766
+ const peerNode = participants[0]?.content?.[0];
767
+ if (peerNode) {
768
+ binaryNodeContent.push(peerNode);
1298
769
  }
1299
- }]
1300
770
  }
1301
- }
1302
- });
1303
- } else {
1304
- sex.push({
1305
- __typename: 'GenAIUnifiedResponseSection',
1306
- view_model: {
1307
- __typename: 'GenAISingleLayoutViewModel',
1308
- primitive: {
1309
- __typename: 'GenAIImagePrimitive',
1310
- preview_image: {
1311
- __typename: 'GenAIMediaItem',
1312
- mime_type: image.mime_type || 'image/png',
1313
- url: image.url
1314
- },
1315
- full_image: {
1316
- __typename: 'GenAIMediaItem',
1317
- mime_type: image.mime_type || 'image/png',
1318
- url: image.url
1319
- }
1320
- }
1321
- }
1322
- });
1323
- }
1324
- }
1325
- }
1326
- if (body) {
1327
- const {
1328
- cards = null,
1329
- buttons = null,
1330
- title = '',
1331
- toast = '',
1332
- carousel = false,
1333
- row = false
1334
- } = body ?? {};
1335
- if (carousel || row) {
1336
- if (cards?.length >= 1) {
1337
- sex.push({
1338
- __typename: 'GenAIUnifiedResponseSection',
1339
- view_model: {
1340
- primitives: cards.map((card, cardIndex) => ({
1341
- __typename: 'GenAI3PExtWidgetPrimitive',
1342
- header: {
1343
- __typename: 'GenAI3PExtWidgetStandardHeader',
1344
- title: card?.title || ''
1345
- },
1346
- body: {
1347
- __typename: 'GenAI3PExtCalendarEventList',
1348
- ctas: (card?.buttons || []).map((text, buttonIndex) => ({
1349
- label: text,
1350
- state: 'PENDING',
1351
- kind: 'OTHER',
1352
- tool_call_id: `${cardIndex}${buttonIndex}`,
1353
- toast: {
1354
- label: card?.toast || '',
1355
- __typename: 'GenAI3PExtWidgetToast'
1356
- },
1357
- __typename: 'GenAI3PExtWidgetCTA'
1358
- })),
1359
- sections: []
1360
- }
1361
- })),
1362
- __typename: carousel ? 'GenAIHScrollLayoutViewModel' : 'GenAIActionRowLayoutViewModel'
1363
- }
1364
- });
1365
- }
1366
- } else if (buttons?.length) {
1367
- sex.push({
1368
- __typename: 'GenAIUnifiedResponseSection',
1369
- view_model: {
1370
- primitive: {
1371
- __typename: 'GenAI3PExtWidgetPrimitive',
1372
- header: {
1373
- __typename: 'GenAI3PExtWidgetStandardHeader',
1374
- title: title || ''
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 || {})
1375
786
  },
1376
- body: {
1377
- __typename: 'GenAI3PExtCalendarEventList',
1378
- ctas: buttons.map((text, buttonIndex) => ({
1379
- label: text,
1380
- state: 'PENDING',
1381
- kind: 'OTHER',
1382
- tool_call_id: `${buttonIndex}`,
1383
- toast: {
1384
- label: toast,
1385
- __typename: 'GenAI3PExtWidgetToast'
1386
- },
1387
- __typename: 'GenAI3PExtWidgetCTA'
1388
- })),
1389
- sections: []
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;
1390
800
  }
1391
- },
1392
- __typename: 'GenAISingleLayoutViewModel'
1393
801
  }
1394
- });
1395
- }
1396
- }
1397
- if (footer) {
1398
- const {
1399
- text = '',
1400
- url = '',
1401
- image = {}
1402
- } = footer ?? {};
1403
- let img = [];
1404
- if (image?.url) {
1405
- img.push({
1406
- __typename: 'GenAIMarkdownTextUXPrimitive',
1407
- text: '{{header}}.{{/header}}',
1408
- inline_entities: [{
1409
- __typename: 'GenAITextInlineEntity',
1410
- key: 'header',
1411
- metadata: {
1412
- __typename: 'GenAILatexItem',
1413
- latex_expression: '.',
1414
- font_height: 24,
1415
- padding: -5,
1416
- latex_image: {
1417
- __typename: 'GenAIMediaItem',
1418
- mime_type: image.mime_type || 'image/png',
1419
- url: image.url,
1420
- url_fallback: image.url,
1421
- width: image.width || 100,
1422
- height: image.height || 100,
1423
- expiration_timestamp_ms: Date.now() + 86400000
1424
- }
1425
- }
1426
- }]
1427
- });
1428
- }
1429
- sex.push({
1430
- view_model: {
1431
- primitives: [{
1432
- cta_text: text || 'Tg',
1433
- cta_type: 'OPEN_URL',
1434
- cta_url: url || 't.me/thesatanicmirror',
1435
- __typename: 'GenAIFooterActionPrimitive'
1436
- }, ...img],
1437
- __typename: 'GenAIActionRowLayoutViewModel'
1438
- }
1439
- });
1440
- }
1441
- const waMsg = generateWAMessageFromContent(target, {
1442
- ...dihh,
1443
- botForwardedMessage: {
1444
- message: {
1445
- richResponseMessage: {
1446
- unifiedResponse: {
1447
- data: Buffer.from(JSON.stringify({
1448
- sections: sex
1449
- })).toString('base64')
1450
- },
1451
- contextInfo: {
1452
- isForwarded: true,
1453
- forwardOrigin: 4,
1454
- ...(content?.contextInfo ?? {})
1455
- }
802
+ else {
803
+ stanza.attrs.to = destinationJid;
1456
804
  }
1457
- }
1458
- }
1459
- }, {});
1460
- await relayMessage(target, waMsg.message, {
1461
- messageId: waMsg.key.id
1462
- });
1463
- return waMsg;
1464
- },
1465
- sendMessage: async (jid, content, options = {}) => {
1466
- const userJid = authState.creds.me.id;
1467
- if (typeof content === 'object' && 'disappearingMessagesInChat' in content && typeof content['disappearingMessagesInChat'] !== 'undefined' && isJidGroup(jid)) {
1468
- const {
1469
- disappearingMessagesInChat
1470
- } = content;
1471
- const value = typeof disappearingMessagesInChat === 'boolean' ? disappearingMessagesInChat ? WA_DEFAULT_EPHEMERAL : 0 : disappearingMessagesInChat;
1472
- await groupToggleEphemeral(jid, value);
1473
- } else {
1474
- const fullMsg = await generateWAMessage(jid, content, {
1475
- logger,
1476
- userJid,
1477
- getUrlInfo: text => getUrlInfo(text, {
1478
- thumbnailWidth: linkPreviewImageThumbnailWidth,
1479
- fetchOpts: {
1480
- timeout: 3000,
1481
- ...(httpRequestOptions || {})
1482
- },
1483
- logger,
1484
- uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined
1485
- }),
1486
- getProfilePicUrl: sock.profilePictureUrl,
1487
- getCallLink: sock.createCallLink,
1488
- upload: waUploadToServer,
1489
- mediaCache: config.mediaCache,
1490
- options: config.options,
1491
- messageId: generateMessageIDV2(sock.user?.id),
1492
- ...options
1493
- });
1494
- const isEventMsg = 'event' in content && !!content.event;
1495
- const isDeleteMsg = 'delete' in content && !!content.delete;
1496
- const isEditMsg = 'edit' in content && !!content.edit;
1497
- const isPinMsg = 'pin' in content && !!content.pin;
1498
- const isPollMessage = 'poll' in content && !!content.poll;
1499
- const additionalAttributes = {};
1500
- const additionalNodes = [];
1501
- if (isDeleteMsg) {
1502
- if (isJidGroup(content.delete?.remoteJid) && !content.delete?.fromMe) {
1503
- additionalAttributes.edit = '8';
1504
- } else {
1505
- additionalAttributes.edit = '7';
1506
- }
1507
- } else if (isEditMsg) {
1508
- additionalAttributes.edit = '1';
1509
- } else if (isPinMsg) {
1510
- additionalAttributes.edit = '2';
1511
- } else if (isPollMessage) {
1512
- additionalNodes.push({
1513
- tag: 'meta',
1514
- attrs: {
1515
- polltype: 'creation'
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');
1516
812
  }
1517
- });
1518
- } else if (isEventMsg) {
1519
- additionalNodes.push({
1520
- tag: 'meta',
1521
- attrs: {
1522
- event_type: 'creation'
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);
1523
863
  }
1524
- });
1525
- }
1526
- await relayMessage(jid, fullMsg.message, {
1527
- messageId: fullMsg.key.id,
1528
- useCachedGroupMetadata: options.useCachedGroupMetadata,
1529
- additionalAttributes,
1530
- statusJidList: options.statusJidList,
1531
- additionalNodes
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
+ ]
1532
1023
  });
1533
- if (config.emitOwnEvents) {
1534
- process.nextTick(async () => {
1535
- await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
1536
- });
1537
- }
1538
- return fullMsg;
1539
- }
1540
- }
1541
- };
1542
- };
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 sendMessage = async (jid, content, options = {}) => {
1282
+ if (content.groupStatusMessage || content.groupStatus) {
1283
+ const statusContent = content.groupStatusMessage || content.groupStatus;
1284
+ const fullMsg = await generateWAMessage(jid, {
1285
+ groupStatusMessage: statusContent
1286
+ }, {
1287
+ logger,
1288
+ userJid: authState.creds.me.id,
1289
+ upload: waUploadToServer,
1290
+ mediaCache: config.mediaCache,
1291
+ messageId: generateMessageIDV2(sock.user?.id)
1292
+ });
1293
+ await relayMessage(jid, fullMsg.message, {
1294
+ messageId: fullMsg.key.id,
1295
+ additionalAttributes: { type: 'group_status' },
1296
+ additionalNodes: [{ tag: 'meta', attrs: { type: 'group_status' } }]
1297
+ });
1298
+ if (config.emitOwnEvents) {
1299
+ process.nextTick(async () => {
1300
+ await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
1301
+ });
1302
+ }
1303
+ return fullMsg;
1304
+ }
1305
+ const userJid = authState.creds.me.id;
1306
+ if (typeof content === 'object' &&
1307
+ 'disappearingMessagesInChat' in content &&
1308
+ typeof content['disappearingMessagesInChat'] !== 'undefined' &&
1309
+ isJidGroup(jid)) {
1310
+ const { disappearingMessagesInChat } = content;
1311
+ const value = typeof disappearingMessagesInChat === 'boolean'
1312
+ ? disappearingMessagesInChat
1313
+ ? WA_DEFAULT_EPHEMERAL
1314
+ : 0
1315
+ : disappearingMessagesInChat;
1316
+ await groupToggleEphemeral(jid, value);
1317
+ }
1318
+ else {
1319
+ const fullMsg = await generateWAMessage(jid, content, {
1320
+ logger,
1321
+ userJid,
1322
+ getUrlInfo: text => getUrlInfo(text, {
1323
+ thumbnailWidth: linkPreviewImageThumbnailWidth,
1324
+ fetchOpts: {
1325
+ timeout: 3000,
1326
+ ...(httpRequestOptions || {})
1327
+ },
1328
+ logger,
1329
+ uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined
1330
+ }),
1331
+ getProfilePicUrl: sock.profilePictureUrl,
1332
+ getCallLink: sock.createCallLink,
1333
+ upload: waUploadToServer,
1334
+ mediaCache: config.mediaCache,
1335
+ options: config.options,
1336
+ messageId: generateMessageIDV2(sock.user?.id),
1337
+ ...options
1338
+ });
1339
+ const isEventMsg = 'event' in content && !!content.event;
1340
+ const isDeleteMsg = 'delete' in content && !!content.delete;
1341
+ const isEditMsg = 'edit' in content && !!content.edit;
1342
+ const isPinMsg = 'pin' in content && !!content.pin;
1343
+ const isPollMessage = 'poll' in content && !!content.poll;
1344
+ const additionalAttributes = {};
1345
+ const additionalNodes = [];
1346
+ if (isDeleteMsg) {
1347
+ if (isJidGroup(content.delete?.remoteJid) && !content.delete?.fromMe) {
1348
+ additionalAttributes.edit = '8';
1349
+ }
1350
+ else {
1351
+ additionalAttributes.edit = '7';
1352
+ }
1353
+ }
1354
+ else if (isEditMsg) {
1355
+ additionalAttributes.edit = '1';
1356
+ }
1357
+ else if (isPinMsg) {
1358
+ additionalAttributes.edit = '2';
1359
+ }
1360
+ else if (isPollMessage) {
1361
+ additionalNodes.push({
1362
+ tag: 'meta',
1363
+ attrs: {
1364
+ polltype: 'creation'
1365
+ }
1366
+ });
1367
+ }
1368
+ else if (isEventMsg) {
1369
+ additionalNodes.push({
1370
+ tag: 'meta',
1371
+ attrs: {
1372
+ event_type: 'creation'
1373
+ }
1374
+ });
1375
+ }
1376
+ await relayMessage(jid, fullMsg.message, {
1377
+ messageId: fullMsg.key.id,
1378
+ useCachedGroupMetadata: options.useCachedGroupMetadata,
1379
+ additionalAttributes,
1380
+ statusJidList: options.statusJidList,
1381
+ additionalNodes
1382
+ });
1383
+ if (config.emitOwnEvents) {
1384
+ process.nextTick(async () => {
1385
+ await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
1386
+ });
1387
+ }
1388
+ return fullMsg;
1389
+ }
1390
+ };
1391
+
1392
+ const sendQuickReplyButtons = async (jid, text, buttons, footer = '') => {
1393
+ const message = createButtonsMessage(text, footer, buttons);
1394
+ return await sendMessage(jid, message);
1395
+ };
1396
+
1397
+ const sendUrlButtons = async (jid, text, urlButtons, footer = '') => {
1398
+ const message = createButtonsMessage(text, footer, urlButtons);
1399
+ return await sendMessage(jid, message);
1400
+ };
1401
+
1402
+ const sendMixedButtons = async (jid, text, buttons, footer = '') => {
1403
+ const message = createButtonsMessage(text, footer, buttons);
1404
+ return await sendMessage(jid, message);
1405
+ };
1406
+
1407
+ const sendButtonsWithHeader = async (jid, text, headerTitle, buttons, footer = '') => {
1408
+ const message = createButtonsMessageWithHeader(text, footer, headerTitle, buttons);
1409
+ return await sendMessage(jid, message);
1410
+ };
1411
+
1412
+ const sendButtonsWithImageHeader = async (jid, text, imageUrl, buttons, footer = '') => {
1413
+ const message = createButtonsMessageWithImageHeader(text, footer, imageUrl, buttons);
1414
+ return await sendMessage(jid, message);
1415
+ };
1416
+
1417
+ const sendButtonsWithDocumentHeader = async (jid, text, documentUrl, fileName, buttons, footer = '') => {
1418
+ const message = createButtonsMessageWithDocumentHeader(text, footer, documentUrl, fileName, buttons);
1419
+ return await sendMessage(jid, message);
1420
+ };
1421
+
1422
+ const sendListMessage = async (jid, title, description, buttonText, sections) => {
1423
+ const message = createListMessage(title, description, buttonText, sections);
1424
+ return await sendMessage(jid, message);
1425
+ };
1426
+
1427
+ const sendInteractiveButtons = async (jid, body, footer, buttons, header = null) => {
1428
+ const action = createInteractiveButtonsAction(buttons);
1429
+ const message = createInteractiveMessage(body, footer, header, action);
1430
+ return await sendMessage(jid, message);
1431
+ };
1432
+
1433
+ const sendInteractiveList = async (jid, body, footer, title, buttonText, sections, header = null) => {
1434
+ const action = createInteractiveListAction(title, buttonText, sections);
1435
+ const message = createInteractiveMessage(body, footer, header, action);
1436
+ return await sendMessage(jid, message);
1437
+ };
1438
+
1439
+ const sendInteractiveCatalog = async (jid, body, footer, catalogId, businessOwnerJid, displayText, header = null) => {
1440
+ const action = createInteractiveCatalogAction(catalogId, businessOwnerJid, displayText);
1441
+ const message = createInteractiveMessage(body, footer, header, action);
1442
+ return await sendMessage(jid, message);
1443
+ };
1444
+
1445
+ const sendPollMessage = async (jid, question, options, optionsType = 0) => {
1446
+ const message = {
1447
+ pollCreationMessage: {
1448
+ name: question,
1449
+ options: options.map(opt => ({
1450
+ optionName: opt
1451
+ })),
1452
+ selectableOptionsCount: 1,
1453
+ optionsType: optionsType
1454
+ }
1455
+ };
1456
+ return await sendMessage(jid, message);
1457
+ };
1458
+
1459
+ const sendCallButton = async (jid, text, phoneNumber, displayText, footer = '') => {
1460
+ const callButton = createCallButton(displayText, phoneNumber);
1461
+ const message = createButtonsMessage(text, footer, [callButton]);
1462
+ return await sendMessage(jid, message);
1463
+ };
1464
+
1465
+ const sendLocationButton = async (jid, text, displayText, footer = '') => {
1466
+ const locationButton = createLocationButton(displayText);
1467
+ const message = createButtonsMessage(text, footer, [locationButton]);
1468
+ return await sendMessage(jid, message);
1469
+ };
1470
+
1471
+ const sendContactButton = async (jid, text, displayText, footer = '') => {
1472
+ const contactButton = createContactButton(displayText);
1473
+ const message = createButtonsMessage(text, footer, [contactButton]);
1474
+ return await sendMessage(jid, message);
1475
+ };
1476
+
1477
+ const sendTemplateMessage = async (jid, text, templateId, buttons, footer = '') => {
1478
+ const message = createTemplateMessage(text, footer, templateId, buttons);
1479
+ return await sendMessage(jid, message);
1480
+ };
1481
+
1482
+ const sendCatalogMessage = async (jid, businessOwnerJid, catalogItems) => {
1483
+ const message = createCatalogMessage(businessOwnerJid, catalogItems);
1484
+ return await sendMessage(jid, message);
1485
+ };
1486
+
1487
+ const sendProductMessage = async (jid, productId, businessOwnerJid, catalogId) => {
1488
+ const message = createProductMessage(productId, businessOwnerJid, catalogId);
1489
+ return await sendMessage(jid, message);
1490
+ };
1491
+
1492
+ const sendCollectionMessage = async (jid, businessOwnerJid, catalogId) => {
1493
+ const message = createCollectionMessage(businessOwnerJid, catalogId);
1494
+ return await sendMessage(jid, message);
1495
+ };
1496
+
1497
+ const createNativeFlowButton = (flowId, displayText, flowData) => ({
1498
+ type: 'flow',
1499
+ flow: {
1500
+ flowId: flowId,
1501
+ displayText: displayText || 'Continue',
1502
+ flowData: flowData || {}
1503
+ }
1504
+ });
1505
+
1506
+ const sendNativeFlow = async (jid, body, footer, flowId, displayText, flowData = {}, header = null) => {
1507
+ const action = {
1508
+ buttons: [{
1509
+ type: 'flow',
1510
+ flow: {
1511
+ flowId: flowId,
1512
+ displayText: displayText || 'Continue',
1513
+ flowData: flowData
1514
+ }
1515
+ }]
1516
+ };
1517
+ const message = createInteractiveMessage(body, footer, header, action);
1518
+ return await sendMessage(jid, message);
1519
+ };
1520
+
1521
+ const createCarouselCard = (cardId, title, subtitle, body, footer, buttons, media = null) => ({
1522
+ cardId: cardId || `card_${Date.now()}`,
1523
+ header: {
1524
+ title: title || '',
1525
+ subtitle: subtitle || '',
1526
+ hasMedia: !!media,
1527
+ media: media || null
1528
+ },
1529
+ body: { text: body || '' },
1530
+ footer: footer ? { text: footer } : undefined,
1531
+ buttons: buttons || []
1532
+ });
1533
+
1534
+ const sendCarousel = async (jid, body, footer, cards, header = null) => {
1535
+ const action = {
1536
+ carousel: {
1537
+ cards: cards.map(card => ({
1538
+ cardId: card.cardId,
1539
+ header: {
1540
+ title: card.header.title,
1541
+ subtitle: card.header.subtitle || '',
1542
+ hasMedia: card.header.hasMedia || false,
1543
+ media: card.header.media || null
1544
+ },
1545
+ body: { text: card.body.text },
1546
+ footer: card.footer || undefined,
1547
+ buttons: card.buttons || []
1548
+ }))
1549
+ }
1550
+ };
1551
+ const message = createInteractiveMessage(body, footer, header, action);
1552
+ return await sendMessage(jid, message);
1553
+ };
1554
+
1555
+ const sendMultiProductMessage = async (jid, businessOwnerJid, catalogId, products, body = 'Browse our products', footer = '', header = null) => {
1556
+ const action = {
1557
+ catalog: {
1558
+ businessOwnerJid: businessOwnerJid,
1559
+ catalogId: catalogId,
1560
+ products: products.map(p => ({
1561
+ productId: p.id,
1562
+ title: p.title,
1563
+ description: p.description || '',
1564
+ price: p.price || '0',
1565
+ currency: p.currency || 'PKR',
1566
+ image: p.image || null
1567
+ }))
1568
+ }
1569
+ };
1570
+ const message = createInteractiveMessage(body, footer, header, action);
1571
+ return await sendMessage(jid, message);
1572
+ };
1573
+
1574
+ const sendSingleProductMessage = async (jid, productId, businessOwnerJid, catalogId) => {
1575
+ const message = {
1576
+ productMessage: {
1577
+ productId: productId,
1578
+ businessOwnerJid: businessOwnerJid,
1579
+ catalogId: catalogId
1580
+ }
1581
+ };
1582
+ return await sendMessage(jid, message);
1583
+ };
1584
+
1585
+ const sendSectionList = async (jid, title, buttonText, sections, body = 'Choose an option', footer = '', header = null) => {
1586
+ const action = {
1587
+ sections: sections.map(section => ({
1588
+ title: section.title,
1589
+ rows: section.rows.map(row => ({
1590
+ id: row.id || `row_${Date.now()}`,
1591
+ title: row.title,
1592
+ description: row.description || ''
1593
+ }))
1594
+ })),
1595
+ button: buttonText || 'Select'
1596
+ };
1597
+ const message = createInteractiveMessage(body, footer, header, action);
1598
+ return await sendMessage(jid, message);
1599
+ };
1600
+
1601
+ const sendReplyButton = async (jid, text, buttonText, buttonId, footer = '', header = null) => {
1602
+ const action = {
1603
+ buttons: [{
1604
+ type: 'reply',
1605
+ reply: {
1606
+ id: buttonId || `reply_${Date.now()}`,
1607
+ title: buttonText
1608
+ }
1609
+ }]
1610
+ };
1611
+ const message = createInteractiveMessage(text, footer, header, action);
1612
+ return await sendMessage(jid, message);
1613
+ };
1614
+
1615
+ const sendCTAUrl = async (jid, text, displayText, url, footer = '', header = null) => {
1616
+ const action = {
1617
+ buttons: [{
1618
+ type: 'url',
1619
+ url: {
1620
+ displayText: displayText,
1621
+ url: url
1622
+ }
1623
+ }]
1624
+ };
1625
+ const message = createInteractiveMessage(text, footer, header, action);
1626
+ return await sendMessage(jid, message);
1627
+ };
1628
+
1629
+ const sendCTAPhone = async (jid, text, displayText, phoneNumber, footer = '', header = null) => {
1630
+ const action = {
1631
+ buttons: [{
1632
+ type: 'call',
1633
+ call: {
1634
+ displayText: displayText,
1635
+ phoneNumber: phoneNumber
1636
+ }
1637
+ }]
1638
+ };
1639
+ const message = createInteractiveMessage(text, footer, header, action);
1640
+ return await sendMessage(jid, message);
1641
+ };
1642
+
1643
+ const sendCopyCodeButton = async (jid, text, code, buttonText = 'Copy Code', footer = '', header = null) => {
1644
+ const action = {
1645
+ buttons: [{
1646
+ type: 'copy',
1647
+ copy: {
1648
+ displayText: buttonText,
1649
+ code: code
1650
+ }
1651
+ }]
1652
+ };
1653
+ const message = createInteractiveMessage(text, footer, header, action);
1654
+ return await sendMessage(jid, message);
1655
+ };
1656
+
1657
+ const sendFlowButton = async (jid, text, flowId, flowData = {}, buttonText = 'Continue', footer = '', header = null) => {
1658
+ const action = {
1659
+ buttons: [{
1660
+ type: 'flow',
1661
+ flow: {
1662
+ flowId: flowId,
1663
+ displayText: buttonText,
1664
+ flowData: flowData
1665
+ }
1666
+ }]
1667
+ };
1668
+ const message = createInteractiveMessage(text, footer, header, action);
1669
+ return await sendMessage(jid, message);
1670
+ };
1671
+
1672
+ const sendOptOutButton = async (jid, text, footer = '', header = null) => {
1673
+ const action = {
1674
+ buttons: [{
1675
+ type: 'optout',
1676
+ optout: {
1677
+ displayText: 'Unsubscribe'
1678
+ }
1679
+ }]
1680
+ };
1681
+ const message = createInteractiveMessage(text, footer, header, action);
1682
+ return await sendMessage(jid, message);
1683
+ };
1684
+
1685
+ const sendOneTapButton = async (jid, text, authCode, buttonText = 'Authenticate', footer = '', header = null) => {
1686
+ const action = {
1687
+ buttons: [{
1688
+ type: 'onetime',
1689
+ onetime: {
1690
+ displayText: buttonText,
1691
+ code: authCode
1692
+ }
1693
+ }]
1694
+ };
1695
+ const message = createInteractiveMessage(text, footer, header, action);
1696
+ return await sendMessage(jid, message);
1697
+ };
1698
+
1699
+ const sendZeroTapButton = async (jid, text, authToken, footer = '', header = null) => {
1700
+ const message = {
1701
+ protocolMessage: {
1702
+ type: proto.Message.ProtocolMessage.Type.PEER_DATA_OPERATION_REQUEST_MESSAGE,
1703
+ peerDataOperationRequestMessage: {
1704
+ zeroTap: {
1705
+ displayText: text,
1706
+ token: authToken
1707
+ }
1708
+ }
1709
+ }
1710
+ };
1711
+ return await sendMessage(jid, message);
1712
+ };
1713
+
1714
+ const sendAuthButton = async (jid, text, authType, authData, buttonText = 'Authenticate', footer = '', header = null) => {
1715
+ const action = {
1716
+ buttons: [{
1717
+ type: 'auth',
1718
+ auth: {
1719
+ displayText: buttonText,
1720
+ authType: authType,
1721
+ authData: authData
1722
+ }
1723
+ }]
1724
+ };
1725
+ const message = createInteractiveMessage(text, footer, header, action);
1726
+ return await sendMessage(jid, message);
1727
+ };
1728
+
1729
+ const sendOTPButton = async (jid, text, otp, buttonText = 'Verify OTP', footer = '', header = null) => {
1730
+ const action = {
1731
+ buttons: [{
1732
+ type: 'otp',
1733
+ otp: {
1734
+ displayText: buttonText,
1735
+ otp: otp
1736
+ }
1737
+ }]
1738
+ };
1739
+ const message = createInteractiveMessage(text, footer, header, action);
1740
+ return await sendMessage(jid, message);
1741
+ };
1742
+
1743
+ const sendPaymentButton = async (jid, text, amount, currency = 'PKR', paymentId, buttonText = 'Pay Now', footer = '', header = null) => {
1744
+ const action = {
1745
+ buttons: [{
1746
+ type: 'payment',
1747
+ payment: {
1748
+ displayText: buttonText,
1749
+ amount: amount,
1750
+ currency: currency,
1751
+ paymentId: paymentId || `pay_${Date.now()}`
1752
+ }
1753
+ }]
1754
+ };
1755
+ const message = createInteractiveMessage(text, footer, header, action);
1756
+ return await sendMessage(jid, message);
1757
+ };
1758
+
1759
+ const sendScheduleButton = async (jid, text, scheduleId, startTime, endTime, buttonText = 'Schedule', footer = '', header = null) => {
1760
+ const action = {
1761
+ buttons: [{
1762
+ type: 'schedule',
1763
+ schedule: {
1764
+ displayText: buttonText,
1765
+ scheduleId: scheduleId || `sch_${Date.now()}`,
1766
+ startTime: startTime,
1767
+ endTime: endTime
1768
+ }
1769
+ }]
1770
+ };
1771
+ const message = createInteractiveMessage(text, footer, header, action);
1772
+ return await sendMessage(jid, message);
1773
+ };
1774
+
1775
+ const sendFeedbackButton = async (jid, text, feedbackId, ratingType = 'stars', buttonText = 'Give Feedback', footer = '', header = null) => {
1776
+ const action = {
1777
+ buttons: [{
1778
+ type: 'feedback',
1779
+ feedback: {
1780
+ displayText: buttonText,
1781
+ feedbackId: feedbackId || `fb_${Date.now()}`,
1782
+ ratingType: ratingType
1783
+ }
1784
+ }]
1785
+ };
1786
+ const message = createInteractiveMessage(text, footer, header, action);
1787
+ return await sendMessage(jid, message);
1788
+ };
1789
+
1790
+ const sendShareButton = async (jid, text, shareTarget, shareData, buttonText = 'Share', footer = '', header = null) => {
1791
+ const action = {
1792
+ buttons: [{
1793
+ type: 'share',
1794
+ share: {
1795
+ displayText: buttonText,
1796
+ shareTarget: shareTarget,
1797
+ shareData: shareData
1798
+ }
1799
+ }]
1800
+ };
1801
+ const message = createInteractiveMessage(text, footer, header, action);
1802
+ return await sendMessage(jid, message);
1803
+ };
1804
+
1805
+ const sendDownloadButton = async (jid, text, fileId, fileType, fileSize, buttonText = 'Download', footer = '', header = null) => {
1806
+ const action = {
1807
+ buttons: [{
1808
+ type: 'download',
1809
+ download: {
1810
+ displayText: buttonText,
1811
+ fileId: fileId || `dl_${Date.now()}`,
1812
+ fileType: fileType,
1813
+ fileSize: fileSize
1814
+ }
1815
+ }]
1816
+ };
1817
+ const message = createInteractiveMessage(text, footer, header, action);
1818
+ return await sendMessage(jid, message);
1819
+ };
1820
+
1821
+ const sendUploadButton = async (jid, text, uploadType, maxSize, acceptedFormats, buttonText = 'Upload', footer = '', header = null) => {
1822
+ const action = {
1823
+ buttons: [{
1824
+ type: 'upload',
1825
+ upload: {
1826
+ displayText: buttonText,
1827
+ uploadType: uploadType,
1828
+ maxSize: maxSize,
1829
+ acceptedFormats: acceptedFormats
1830
+ }
1831
+ }]
1832
+ };
1833
+ const message = createInteractiveMessage(text, footer, header, action);
1834
+ return await sendMessage(jid, message);
1835
+ };
1836
+
1837
+ const sendVerifyButton = async (jid, text, verifyType, verificationId, buttonText = 'Verify', footer = '', header = null) => {
1838
+ const action = {
1839
+ buttons: [{
1840
+ type: 'verify',
1841
+ verify: {
1842
+ displayText: buttonText,
1843
+ verifyType: verifyType,
1844
+ verificationId: verificationId || `vf_${Date.now()}`
1845
+ }
1846
+ }]
1847
+ };
1848
+ const message = createInteractiveMessage(text, footer, header, action);
1849
+ return await sendMessage(jid, message);
1850
+ };
1851
+
1852
+ const sendApproveRejectButton = async (jid, text, approvalId, approvalType, approveText = 'Approve', rejectText = 'Reject', footer = '', header = null) => {
1853
+ const action = {
1854
+ buttons: [
1855
+ {
1856
+ type: 'approve',
1857
+ approve: {
1858
+ displayText: approveText,
1859
+ approvalId: approvalId || `app_${Date.now()}`,
1860
+ approvalType: approvalType
1861
+ }
1862
+ },
1863
+ {
1864
+ type: 'reject',
1865
+ reject: {
1866
+ displayText: rejectText,
1867
+ approvalId: approvalId || `app_${Date.now()}`,
1868
+ reason: ''
1869
+ }
1870
+ }
1871
+ ]
1872
+ };
1873
+ const message = createInteractiveMessage(text, footer, header, action);
1874
+ return await sendMessage(jid, message);
1875
+ };
1876
+
1877
+ const sendForwardButton = async (jid, text, forwardId, forwardType, buttonText = 'Forward', footer = '', header = null) => {
1878
+ const action = {
1879
+ buttons: [{
1880
+ type: 'forward',
1881
+ forward: {
1882
+ displayText: buttonText,
1883
+ forwardId: forwardId || `fw_${Date.now()}`,
1884
+ forwardType: forwardType
1885
+ }
1886
+ }]
1887
+ };
1888
+ const message = createInteractiveMessage(text, footer, header, action);
1889
+ return await sendMessage(jid, message);
1890
+ };
1891
+
1892
+ const sendMuteUnmuteButton = async (jid, text, conversationId, muteText = 'Mute', unmuteText = 'Unmute', footer = '', header = null) => {
1893
+ const action = {
1894
+ buttons: [
1895
+ {
1896
+ type: 'mute',
1897
+ mute: {
1898
+ displayText: muteText,
1899
+ conversationId: conversationId || `conv_${Date.now()}`,
1900
+ duration: 3600
1901
+ }
1902
+ },
1903
+ {
1904
+ type: 'unmute',
1905
+ unmute: {
1906
+ displayText: unmuteText,
1907
+ conversationId: conversationId || `conv_${Date.now()}`
1908
+ }
1909
+ }
1910
+ ]
1911
+ };
1912
+ const message = createInteractiveMessage(text, footer, header, action);
1913
+ return await sendMessage(jid, message);
1914
+ };
1915
+
1916
+ const sendArchiveUnarchiveButton = async (jid, text, archiveId, archiveType, archiveText = 'Archive', unarchiveText = 'Unarchive', footer = '', header = null) => {
1917
+ const action = {
1918
+ buttons: [
1919
+ {
1920
+ type: 'archive',
1921
+ archive: {
1922
+ displayText: archiveText,
1923
+ archiveId: archiveId || `arch_${Date.now()}`,
1924
+ archiveType: archiveType
1925
+ }
1926
+ },
1927
+ {
1928
+ type: 'unarchive',
1929
+ unarchive: {
1930
+ displayText: unarchiveText,
1931
+ archiveId: archiveId || `arch_${Date.now()}`
1932
+ }
1933
+ }
1934
+ ]
1935
+ };
1936
+ const message = createInteractiveMessage(text, footer, header, action);
1937
+ return await sendMessage(jid, message);
1938
+ };
1939
+
1940
+ const sendPinUnpinButton = async (jid, text, pinId, pinType, pinText = 'Pin', unpinText = 'Unpin', duration = 86400, footer = '', header = null) => {
1941
+ const action = {
1942
+ buttons: [
1943
+ {
1944
+ type: 'pin',
1945
+ pin: {
1946
+ displayText: pinText,
1947
+ pinId: pinId || `pin_${Date.now()}`,
1948
+ pinType: pinType,
1949
+ duration: duration
1950
+ }
1951
+ },
1952
+ {
1953
+ type: 'unpin',
1954
+ unpin: {
1955
+ displayText: unpinText,
1956
+ pinId: pinId || `pin_${Date.now()}`
1957
+ }
1958
+ }
1959
+ ]
1960
+ };
1961
+ const message = createInteractiveMessage(text, footer, header, action);
1962
+ return await sendMessage(jid, message);
1963
+ };
1964
+
1965
+ const sendBookmarkButton = async (jid, text, bookmarkId, bookmarkCategory, bookmarkText = 'Bookmark', unbookmarkText = 'Remove', footer = '', header = null) => {
1966
+ const action = {
1967
+ buttons: [
1968
+ {
1969
+ type: 'bookmark',
1970
+ bookmark: {
1971
+ displayText: bookmarkText,
1972
+ bookmarkId: bookmarkId || `bm_${Date.now()}`,
1973
+ bookmarkCategory: bookmarkCategory
1974
+ }
1975
+ },
1976
+ {
1977
+ type: 'unbookmark',
1978
+ unbookmark: {
1979
+ displayText: unbookmarkText,
1980
+ bookmarkId: bookmarkId || `bm_${Date.now()}`
1981
+ }
1982
+ }
1983
+ ]
1984
+ };
1985
+ const message = createInteractiveMessage(text, footer, header, action);
1986
+ return await sendMessage(jid, message);
1987
+ };
1988
+
1989
+ const sendFlagUnflagButton = async (jid, text, flagId, flagCategory, flagText = 'Flag', unflagText = 'Unflag', priority = 'normal', footer = '', header = null) => {
1990
+ const action = {
1991
+ buttons: [
1992
+ {
1993
+ type: 'flag',
1994
+ flag: {
1995
+ displayText: flagText,
1996
+ flagId: flagId || `fl_${Date.now()}`,
1997
+ flagCategory: flagCategory,
1998
+ priority: priority
1999
+ }
2000
+ },
2001
+ {
2002
+ type: 'unflag',
2003
+ unflag: {
2004
+ displayText: unflagText,
2005
+ flagId: flagId || `fl_${Date.now()}`
2006
+ }
2007
+ }
2008
+ ]
2009
+ };
2010
+ const message = createInteractiveMessage(text, footer, header, action);
2011
+ return await sendMessage(jid, message);
2012
+ };
2013
+
2014
+ const sendLockUnlockButton = async (jid, text, lockId, lockType, lockText = 'Lock', unlockText = 'Unlock', duration = 3600, footer = '', header = null) => {
2015
+ const action = {
2016
+ buttons: [
2017
+ {
2018
+ type: 'lock',
2019
+ lock: {
2020
+ displayText: lockText,
2021
+ lockId: lockId || `lk_${Date.now()}`,
2022
+ lockType: lockType,
2023
+ duration: duration
2024
+ }
2025
+ },
2026
+ {
2027
+ type: 'unlock',
2028
+ unlock: {
2029
+ displayText: unlockText,
2030
+ lockId: lockId || `lk_${Date.now()}`
2031
+ }
2032
+ }
2033
+ ]
2034
+ };
2035
+ const message = createInteractiveMessage(text, footer, header, action);
2036
+ return await sendMessage(jid, message);
2037
+ };
2038
+
2039
+ const sendAddressRequestButton = async (jid, body, buttonText = 'Share Address', footer = '', header = null) => {
2040
+ const action = {
2041
+ buttons: [{
2042
+ type: 'address',
2043
+ address: {
2044
+ displayText: buttonText
2045
+ }
2046
+ }]
2047
+ };
2048
+ const message = createInteractiveMessage(body, footer, header, action);
2049
+ return await sendMessage(jid, message);
2050
+ };
2051
+
2052
+ const sendLocationRequestButton = async (jid, body, buttonText = 'Share Location', footer = '', header = null) => {
2053
+ const action = {
2054
+ buttons: [{
2055
+ type: 'location',
2056
+ location: {
2057
+ displayText: buttonText
2058
+ }
2059
+ }]
2060
+ };
2061
+ const message = createInteractiveMessage(body, footer, header, action);
2062
+ return await sendMessage(jid, message);
2063
+ };
2064
+
2065
+ const sendVoiceCallButton = async (jid, body, phoneNumber, buttonText = 'Voice Call', footer = '', header = null) => {
2066
+ const action = {
2067
+ buttons: [{
2068
+ type: 'voice',
2069
+ voice: {
2070
+ displayText: buttonText,
2071
+ phoneNumber: phoneNumber
2072
+ }
2073
+ }]
2074
+ };
2075
+ const message = createInteractiveMessage(body, footer, header, action);
2076
+ return await sendMessage(jid, message);
2077
+ };
2078
+
2079
+ const sendVideoCallButton = async (jid, body, phoneNumber, buttonText = 'Video Call', footer = '', header = null) => {
2080
+ const action = {
2081
+ buttons: [{
2082
+ type: 'video',
2083
+ video: {
2084
+ displayText: buttonText,
2085
+ phoneNumber: phoneNumber
2086
+ }
2087
+ }]
2088
+ };
2089
+ const message = createInteractiveMessage(body, footer, header, action);
2090
+ return await sendMessage(jid, message);
2091
+ };
2092
+
2093
+ const sendImageButton = async (jid, body, imageUrl, buttonText = 'View Image', footer = '', header = null) => {
2094
+ const action = {
2095
+ buttons: [{
2096
+ type: 'image',
2097
+ image: {
2098
+ displayText: buttonText,
2099
+ url: imageUrl
2100
+ }
2101
+ }]
2102
+ };
2103
+ const message = createInteractiveMessage(body, footer, header, action);
2104
+ return await sendMessage(jid, message);
2105
+ };
2106
+
2107
+ const sendDocumentButton = async (jid, body, documentUrl, fileName, buttonText = 'View Document', footer = '', header = null) => {
2108
+ const action = {
2109
+ buttons: [{
2110
+ type: 'document',
2111
+ document: {
2112
+ displayText: buttonText,
2113
+ url: documentUrl,
2114
+ fileName: fileName
2115
+ }
2116
+ }]
2117
+ };
2118
+ const message = createInteractiveMessage(body, footer, header, action);
2119
+ return await sendMessage(jid, message);
2120
+ };
2121
+
2122
+ const sendAudioButton = async (jid, body, audioUrl, buttonText = 'Play Audio', footer = '', header = null) => {
2123
+ const action = {
2124
+ buttons: [{
2125
+ type: 'audio',
2126
+ audio: {
2127
+ displayText: buttonText,
2128
+ url: audioUrl
2129
+ }
2130
+ }]
2131
+ };
2132
+ const message = createInteractiveMessage(body, footer, header, action);
2133
+ return await sendMessage(jid, message);
2134
+ };
2135
+
2136
+ const sendStickerButton = async (jid, body, stickerUrl, buttonText = 'View Sticker', footer = '', header = null) => {
2137
+ const action = {
2138
+ buttons: [{
2139
+ type: 'sticker',
2140
+ sticker: {
2141
+ displayText: buttonText,
2142
+ url: stickerUrl
2143
+ }
2144
+ }]
2145
+ };
2146
+ const message = createInteractiveMessage(body, footer, header, action);
2147
+ return await sendMessage(jid, message);
2148
+ };
2149
+
2150
+ const sendInteractiveNativeFlow = async (jid, body, footer, flowId, flowData = {}, buttonText = 'Continue', header = null) => {
2151
+ const action = {
2152
+ buttons: [{
2153
+ type: 'flow',
2154
+ flow: {
2155
+ flowId: flowId,
2156
+ displayText: buttonText,
2157
+ flowData: flowData
2158
+ }
2159
+ }]
2160
+ };
2161
+ const message = createInteractiveMessage(body, footer, header, action);
2162
+ return await sendMessage(jid, message);
2163
+ };
2164
+
2165
+ const sendCollectShoppingInterest = async (jid, productCategory, buttonText = 'I\'m Interested', footer = '', header = null) => {
2166
+ const action = {
2167
+ buttons: [{
2168
+ type: 'interest',
2169
+ interest: {
2170
+ displayText: buttonText,
2171
+ category: productCategory
2172
+ }
2173
+ }]
2174
+ };
2175
+ const message = createInteractiveMessage(`Are you interested in ${productCategory}?`, footer, header, action);
2176
+ return await sendMessage(jid, message);
2177
+ };
2178
+
2179
+ const sendGetFeedback = async (jid, feedbackType, buttonText = 'Give Feedback', footer = '', header = null) => {
2180
+ const action = {
2181
+ buttons: [{
2182
+ type: 'feedback',
2183
+ feedback: {
2184
+ displayText: buttonText,
2185
+ feedbackType: feedbackType
2186
+ }
2187
+ }]
2188
+ };
2189
+ const message = createInteractiveMessage(`Please provide your feedback on ${feedbackType}`, footer, header, action);
2190
+ return await sendMessage(jid, message);
2191
+ };
2192
+
2193
+ const sendSurveyButton = async (jid, surveyId, surveyQuestion, buttonText = 'Take Survey', footer = '', header = null) => {
2194
+ const action = {
2195
+ buttons: [{
2196
+ type: 'survey',
2197
+ survey: {
2198
+ displayText: buttonText,
2199
+ surveyId: surveyId || `sv_${Date.now()}`,
2200
+ question: surveyQuestion
2201
+ }
2202
+ }]
2203
+ };
2204
+ const message = createInteractiveMessage(surveyQuestion, footer, header, action);
2205
+ return await sendMessage(jid, message);
2206
+ };
2207
+
2208
+ const sendCustomerSupportButton = async (jid, supportTopic, buttonText = 'Contact Support', footer = '', header = null) => {
2209
+ const action = {
2210
+ buttons: [{
2211
+ type: 'support',
2212
+ support: {
2213
+ displayText: buttonText,
2214
+ topic: supportTopic
2215
+ }
2216
+ }]
2217
+ };
2218
+ const message = createInteractiveMessage(`Need help with ${supportTopic}?`, footer, header, action);
2219
+ return await sendMessage(jid, message);
2220
+ };
2221
+
2222
+ const sendReminderButton = async (jid, reminderText, buttonText = 'Set Reminder', footer = '', header = null) => {
2223
+ const action = {
2224
+ buttons: [{
2225
+ type: 'reminder',
2226
+ reminder: {
2227
+ displayText: buttonText,
2228
+ text: reminderText
2229
+ }
2230
+ }]
2231
+ };
2232
+ const message = createInteractiveMessage(`Reminder: ${reminderText}`, footer, header, action);
2233
+ return await sendMessage(jid, message);
2234
+ };
2235
+
2236
+ const sendConfirmButton = async (jid, actionText, buttonText = 'Confirm', footer = '', header = null) => {
2237
+ const action = {
2238
+ buttons: [{
2239
+ type: 'confirm',
2240
+ confirm: {
2241
+ displayText: buttonText,
2242
+ action: actionText
2243
+ }
2244
+ }]
2245
+ };
2246
+ const message = createInteractiveMessage(`Confirm: ${actionText}?`, footer, header, action);
2247
+ return await sendMessage(jid, message);
2248
+ };
2249
+
2250
+ const sendCancelButton = async (jid, actionText, buttonText = 'Cancel', footer = '', header = null) => {
2251
+ const action = {
2252
+ buttons: [{
2253
+ type: 'cancel',
2254
+ cancel: {
2255
+ displayText: buttonText,
2256
+ action: actionText
2257
+ }
2258
+ }]
2259
+ };
2260
+ const message = createInteractiveMessage(`Cancel: ${actionText}?`, footer, header, action);
2261
+ return await sendMessage(jid, message);
2262
+ };
2263
+
2264
+ const sendOrderStatusButton = async (jid, orderId, buttonText = 'Check Status', footer = '', header = null) => {
2265
+ const action = {
2266
+ buttons: [{
2267
+ type: 'orderstatus',
2268
+ orderstatus: {
2269
+ displayText: buttonText,
2270
+ orderId: orderId
2271
+ }
2272
+ }]
2273
+ };
2274
+ const message = createInteractiveMessage(`Check status for order #${orderId}`, footer, header, action);
2275
+ return await sendMessage(jid, message);
2276
+ };
2277
+
2278
+ const sendTrackOrderButton = async (jid, orderId, buttonText = 'Track Order', footer = '', header = null) => {
2279
+ const action = {
2280
+ buttons: [{
2281
+ type: 'track',
2282
+ track: {
2283
+ displayText: buttonText,
2284
+ orderId: orderId
2285
+ }
2286
+ }]
2287
+ };
2288
+ const message = createInteractiveMessage(`Track order #${orderId}`, footer, header, action);
2289
+ return await sendMessage(jid, message);
2290
+ };
2291
+
2292
+ const sendReOrderButton = async (jid, orderId, buttonText = 'Reorder', footer = '', header = null) => {
2293
+ const action = {
2294
+ buttons: [{
2295
+ type: 'reorder',
2296
+ reorder: {
2297
+ displayText: buttonText,
2298
+ orderId: orderId
2299
+ }
2300
+ }]
2301
+ };
2302
+ const message = createInteractiveMessage(`Reorder #${orderId}?`, footer, header, action);
2303
+ return await sendMessage(jid, message);
2304
+ };
2305
+
2306
+ const sendCancelOrderButton = async (jid, orderId, buttonText = 'Cancel Order', footer = '', header = null) => {
2307
+ const action = {
2308
+ buttons: [{
2309
+ type: 'cancelorder',
2310
+ cancelorder: {
2311
+ displayText: buttonText,
2312
+ orderId: orderId
2313
+ }
2314
+ }]
2315
+ };
2316
+ const message = createInteractiveMessage(`Cancel order #${orderId}?`, footer, header, action);
2317
+ return await sendMessage(jid, message);
2318
+ };
2319
+
2320
+ registerSocketEndHandler(() => {
2321
+ if (!config.userDevicesCache && userDevicesCache.close) {
2322
+ userDevicesCache.close();
2323
+ }
2324
+ mediaConn = undefined;
2325
+ if (messageRetryManager) {
2326
+ messageRetryManager.clear();
2327
+ }
2328
+ });
2329
+
2330
+ return {
2331
+ ...sock,
2332
+ userDevicesCache,
2333
+ devicesMutex,
2334
+ issuePrivacyTokens,
2335
+ assertSessions,
2336
+ relayMessage,
2337
+ sendReceipt,
2338
+ sendReceipts,
2339
+ readMessages,
2340
+ refreshMediaConn,
2341
+ getMediaHost: () => mediaHost,
2342
+ waUploadToServer,
2343
+ fetchPrivacySettings,
2344
+ sendPeerDataOperationMessage,
2345
+ createParticipantNodes,
2346
+ getUSyncDevices,
2347
+ messageRetryManager,
2348
+ updateMemberLabel,
2349
+ updateMediaMessage: async (message) => {
2350
+ const content = assertMediaContent(message.message);
2351
+ const mediaKey = content.mediaKey;
2352
+ const meId = authState.creds.me.id;
2353
+ const node = encryptMediaRetryRequest(message.key, mediaKey, meId);
2354
+ let error = undefined;
2355
+ await Promise.all([
2356
+ sendNode(node),
2357
+ waitForMsgMediaUpdate(async (update) => {
2358
+ const result = update.find(c => c.key.id === message.key.id);
2359
+ if (result) {
2360
+ if (result.error) {
2361
+ error = result.error;
2362
+ }
2363
+ else {
2364
+ try {
2365
+ const media = decryptMediaRetryData(result.media, mediaKey, result.key.id);
2366
+ if (media.result !== proto.MediaRetryNotification.ResultType.SUCCESS) {
2367
+ const resultStr = proto.MediaRetryNotification.ResultType[media.result];
2368
+ throw new Boom(`Media re-upload failed by device (${resultStr})`, {
2369
+ data: media,
2370
+ statusCode: getStatusCodeForMediaRetry(media.result) || 404
2371
+ });
2372
+ }
2373
+ content.directPath = media.directPath;
2374
+ content.url = getUrlFromDirectPath(content.directPath, mediaHost);
2375
+ logger.debug({ directPath: media.directPath, key: result.key }, 'media update successful');
2376
+ }
2377
+ catch (err) {
2378
+ error = err;
2379
+ }
2380
+ }
2381
+ return true;
2382
+ }
2383
+ })
2384
+ ]);
2385
+ if (error) {
2386
+ throw error;
2387
+ }
2388
+ ev.emit('messages.update', [{ key: message.key, update: { message: message.message } }]);
2389
+ return message;
2390
+ },
2391
+ sendRichResponseMessage,
2392
+ createQuickReplyButton,
2393
+ createUrlButton,
2394
+ createCallButton,
2395
+ createLocationButton,
2396
+ createContactButton,
2397
+ createPollButton,
2398
+ createInteractiveReplyButton,
2399
+ createInteractiveUrlButton,
2400
+ createInteractiveCallButton,
2401
+ createInteractiveCatalogButton,
2402
+ createListMessage,
2403
+ createListSection,
2404
+ createButtonsMessage,
2405
+ createButtonsMessageWithHeader,
2406
+ createButtonsMessageWithImageHeader,
2407
+ createButtonsMessageWithDocumentHeader,
2408
+ createTemplateMessage,
2409
+ createCatalogMessage,
2410
+ createProductMessage,
2411
+ createCollectionMessage,
2412
+ createOrderMessage,
2413
+ createInteractiveMessage,
2414
+ createInteractiveButtonsAction,
2415
+ createInteractiveListAction,
2416
+ createInteractiveCatalogAction,
2417
+ sendQuickReplyButtons,
2418
+ sendUrlButtons,
2419
+ sendMixedButtons,
2420
+ sendButtonsWithHeader,
2421
+ sendButtonsWithImageHeader,
2422
+ sendButtonsWithDocumentHeader,
2423
+ sendListMessage,
2424
+ sendInteractiveButtons,
2425
+ sendInteractiveList,
2426
+ sendInteractiveCatalog,
2427
+ sendPollMessage,
2428
+ sendCallButton,
2429
+ sendLocationButton,
2430
+ sendContactButton,
2431
+ sendTemplateMessage,
2432
+ sendCatalogMessage,
2433
+ sendProductMessage,
2434
+ sendCollectionMessage,
2435
+ sendMessage,
2436
+ createNativeFlowButton,
2437
+ sendNativeFlow,
2438
+ createCarouselCard,
2439
+ sendCarousel,
2440
+ sendMultiProductMessage,
2441
+ sendSingleProductMessage,
2442
+ sendSectionList,
2443
+ sendReplyButton,
2444
+ sendCTAUrl,
2445
+ sendCTAPhone,
2446
+ sendCopyCodeButton,
2447
+ sendFlowButton,
2448
+ sendOptOutButton,
2449
+ sendOneTapButton,
2450
+ sendZeroTapButton,
2451
+ sendAuthButton,
2452
+ sendOTPButton,
2453
+ sendPaymentButton,
2454
+ sendScheduleButton,
2455
+ sendFeedbackButton,
2456
+ sendShareButton,
2457
+ sendDownloadButton,
2458
+ sendUploadButton,
2459
+ sendVerifyButton,
2460
+ sendApproveRejectButton,
2461
+ sendForwardButton,
2462
+ sendMuteUnmuteButton,
2463
+ sendArchiveUnarchiveButton,
2464
+ sendPinUnpinButton,
2465
+ sendBookmarkButton,
2466
+ sendFlagUnflagButton,
2467
+ sendLockUnlockButton,
2468
+ sendAddressRequestButton,
2469
+ sendLocationRequestButton,
2470
+ sendVoiceCallButton,
2471
+ sendVideoCallButton,
2472
+ sendImageButton,
2473
+ sendDocumentButton,
2474
+ sendAudioButton,
2475
+ sendStickerButton,
2476
+ sendInteractiveNativeFlow,
2477
+ sendCollectShoppingInterest,
2478
+ sendGetFeedback,
2479
+ sendSurveyButton,
2480
+ sendCustomerSupportButton,
2481
+ sendReminderButton,
2482
+ sendConfirmButton,
2483
+ sendCancelButton,
2484
+ sendOrderStatusButton,
2485
+ sendTrackOrderButton,
2486
+ sendReOrderButton,
2487
+ sendCancelOrderButton
2488
+ };
2489
+ };