@mrlegendbot/baileys 1.2.3 → 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/README.upstream.md +1312 -0
  2. package/WAProto/WAProto.json +1 -0
  3. package/WAProto/WAProto.proto +6275 -0
  4. package/WAProto/compiler.js +550 -0
  5. package/WAProto/index.js +2 -97691
  6. package/lib/Defaults/index.js +89 -92
  7. package/lib/Signal/Group/ciphertext-message.js +9 -10
  8. package/lib/Signal/Group/group-session-builder.js +21 -22
  9. package/lib/Signal/Group/group_cipher.js +75 -69
  10. package/lib/Signal/Group/index.js +0 -1
  11. package/lib/Signal/Group/keyhelper.js +9 -10
  12. package/lib/Signal/Group/sender-chain-key.js +21 -22
  13. package/lib/Signal/Group/sender-key-distribution-message.js +51 -60
  14. package/lib/Signal/Group/sender-key-message.js +56 -62
  15. package/lib/Signal/Group/sender-key-name.js +38 -40
  16. package/lib/Signal/Group/sender-key-record.js +33 -34
  17. package/lib/Signal/Group/sender-key-state.js +72 -76
  18. package/lib/Signal/Group/sender-message-key.js +22 -23
  19. package/lib/Signal/libsignal.js +506 -405
  20. package/lib/Signal/lid-mapping.js +276 -258
  21. package/lib/Socket/Client/index.js +0 -1
  22. package/lib/Socket/Client/types.js +6 -7
  23. package/lib/Socket/Client/websocket.js +48 -49
  24. package/lib/Socket/business.js +349 -369
  25. package/lib/Socket/chats.js +1172 -1148
  26. package/lib/Socket/communities.js +473 -413
  27. package/lib/Socket/groups.js +387 -331
  28. package/lib/Socket/index.js +6 -8
  29. package/lib/Socket/messages-recv.js +1782 -1719
  30. package/lib/Socket/messages-send.js +1503 -2450
  31. package/lib/Socket/mex.js +40 -35
  32. package/lib/Socket/newsletter.js +239 -191
  33. package/lib/Socket/socket.js +992 -923
  34. package/lib/Store/index.js +8 -0
  35. package/lib/Store/keyed-db.js +108 -0
  36. package/lib/Store/make-cache-manager-store.js +70 -0
  37. package/lib/Store/make-in-memory-store.js +417 -0
  38. package/lib/Store/make-ordered-dictionary.js +69 -0
  39. package/lib/Store/object-repository.js +27 -0
  40. package/lib/Types/Auth.js +0 -1
  41. package/lib/Types/Bussines.js +0 -1
  42. package/lib/Types/Call.js +0 -1
  43. package/lib/Types/Chat.js +1 -8
  44. package/lib/Types/Contact.js +0 -1
  45. package/lib/Types/Events.js +1 -2
  46. package/lib/Types/GroupMetadata.js +0 -1
  47. package/lib/Types/Label.js +20 -22
  48. package/lib/Types/LabelAssociation.js +2 -4
  49. package/lib/Types/Message.js +3 -5
  50. package/lib/Types/Mex.js +26 -31
  51. package/lib/Types/Product.js +0 -1
  52. package/lib/Types/Signal.js +1 -2
  53. package/lib/Types/Socket.js +2 -3
  54. package/lib/Types/State.js +4 -52
  55. package/lib/Types/USync.js +0 -1
  56. package/lib/Types/index.js +10 -11
  57. package/lib/Utils/auth-utils.js +240 -277
  58. package/lib/Utils/browser-utils.js +20 -23
  59. package/lib/Utils/business.js +200 -204
  60. package/lib/Utils/chat-utils.js +836 -810
  61. package/lib/Utils/companion-reg-client-utils.js +23 -25
  62. package/lib/Utils/crypto.js +70 -86
  63. package/lib/Utils/decode-wa-message.js +277 -284
  64. package/lib/Utils/event-buffer.js +592 -595
  65. package/lib/Utils/generics.js +312 -335
  66. package/lib/Utils/history.js +123 -115
  67. package/lib/Utils/index.js +1 -2
  68. package/lib/Utils/link-preview.js +74 -73
  69. package/lib/Utils/logger.js +3 -2
  70. package/lib/Utils/lt-hash.js +0 -6
  71. package/lib/Utils/make-mutex.js +27 -27
  72. package/lib/Utils/message-retry-manager.js +213 -254
  73. package/lib/Utils/messages-media.js +741 -715
  74. package/lib/Utils/messages.js +791 -827
  75. package/lib/Utils/noise-handler.js +215 -187
  76. package/lib/Utils/pre-key-manager.js +69 -98
  77. package/lib/Utils/process-message.js +617 -586
  78. package/lib/Utils/reporting-utils.js +553 -234
  79. package/lib/Utils/signal.js +217 -170
  80. package/lib/Utils/stanza-ack.js +27 -35
  81. package/lib/Utils/sync-action-utils.js +37 -41
  82. package/lib/Utils/tc-token-utils.js +120 -141
  83. package/lib/Utils/use-multi-file-auth-state.js +87 -105
  84. package/lib/Utils/use-sqlite-auth-state.js +162 -0
  85. package/lib/Utils/validate-connection.js +190 -188
  86. package/lib/WABinary/constants.js +28 -1294
  87. package/lib/WABinary/decode.js +245 -249
  88. package/lib/WABinary/encode.js +201 -206
  89. package/lib/WABinary/generic-utils.js +86 -89
  90. package/lib/WABinary/index.js +0 -1
  91. package/lib/WABinary/jid-utils.js +68 -77
  92. package/lib/WABinary/types.js +0 -1
  93. package/lib/WAM/BinaryInfo.js +7 -8
  94. package/lib/WAM/constants.js +19892 -22853
  95. package/lib/WAM/encode.js +121 -132
  96. package/lib/WAM/index.js +0 -1
  97. package/lib/WAUSync/Protocols/USyncContactProtocol.js +46 -43
  98. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +47 -48
  99. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +22 -23
  100. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +30 -33
  101. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +19 -20
  102. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +53 -44
  103. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +24 -24
  104. package/lib/WAUSync/Protocols/index.js +0 -1
  105. package/lib/WAUSync/USyncQuery.js +79 -88
  106. package/lib/WAUSync/USyncUser.js +28 -29
  107. package/lib/WAUSync/index.js +0 -1
  108. package/lib/index.js +2 -2
  109. package/package.json +22 -86
  110. package/WAProto/GenerateStatics.sh +0 -3
  111. package/WAProto/fix-imports.js +0 -85
  112. package/WAProto/index.d.ts +0 -14017
  113. package/lib/Defaults/index.d.ts +0 -75
  114. package/lib/Signal/Group/ciphertext-message.d.ts +0 -10
  115. package/lib/Signal/Group/group-session-builder.d.ts +0 -15
  116. package/lib/Signal/Group/group_cipher.d.ts +0 -17
  117. package/lib/Signal/Group/index.d.ts +0 -12
  118. package/lib/Signal/Group/keyhelper.d.ts +0 -11
  119. package/lib/Signal/Group/sender-chain-key.d.ts +0 -14
  120. package/lib/Signal/Group/sender-key-distribution-message.d.ts +0 -17
  121. package/lib/Signal/Group/sender-key-message.d.ts +0 -19
  122. package/lib/Signal/Group/sender-key-name.d.ts +0 -18
  123. package/lib/Signal/Group/sender-key-record.d.ts +0 -31
  124. package/lib/Signal/Group/sender-key-state.d.ts +0 -39
  125. package/lib/Signal/Group/sender-message-key.d.ts +0 -12
  126. package/lib/Signal/libsignal.d.ts +0 -5
  127. package/lib/Signal/lid-mapping.d.ts +0 -23
  128. package/lib/Socket/Client/index.d.ts +0 -3
  129. package/lib/Socket/Client/types.d.ts +0 -16
  130. package/lib/Socket/Client/websocket.d.ts +0 -13
  131. package/lib/Socket/business.d.ts +0 -217
  132. package/lib/Socket/chats.d.ts +0 -125
  133. package/lib/Socket/communities.d.ts +0 -273
  134. package/lib/Socket/groups.d.ts +0 -161
  135. package/lib/Socket/index.d.ts +0 -260
  136. package/lib/Socket/messages-recv.d.ts +0 -203
  137. package/lib/Socket/messages-send.d.ts +0 -199
  138. package/lib/Socket/mex.d.ts +0 -3
  139. package/lib/Socket/newsletter.d.ts +0 -170
  140. package/lib/Socket/socket.d.ts +0 -59
  141. package/lib/Types/Auth.d.ts +0 -117
  142. package/lib/Types/Bussines.d.ts +0 -25
  143. package/lib/Types/Call.d.ts +0 -15
  144. package/lib/Types/Chat.d.ts +0 -124
  145. package/lib/Types/Contact.d.ts +0 -26
  146. package/lib/Types/Events.d.ts +0 -256
  147. package/lib/Types/GroupMetadata.d.ts +0 -71
  148. package/lib/Types/Label.d.ts +0 -47
  149. package/lib/Types/LabelAssociation.d.ts +0 -30
  150. package/lib/Types/Message.d.ts +0 -320
  151. package/lib/Types/Mex.d.ts +0 -141
  152. package/lib/Types/Product.d.ts +0 -79
  153. package/lib/Types/Signal.d.ts +0 -87
  154. package/lib/Types/Socket.d.ts +0 -136
  155. package/lib/Types/State.d.ts +0 -97
  156. package/lib/Types/USync.d.ts +0 -26
  157. package/lib/Types/index.d.ts +0 -66
  158. package/lib/Utils/auth-utils.d.ts +0 -24
  159. package/lib/Utils/browser-utils.d.ts +0 -4
  160. package/lib/Utils/business.d.ts +0 -23
  161. package/lib/Utils/chat-utils.d.ts +0 -100
  162. package/lib/Utils/companion-reg-client-utils.d.ts +0 -17
  163. package/lib/Utils/crypto.d.ts +0 -37
  164. package/lib/Utils/decode-wa-message.d.ts +0 -66
  165. package/lib/Utils/event-buffer.d.ts +0 -36
  166. package/lib/Utils/generics.d.ts +0 -92
  167. package/lib/Utils/history.d.ts +0 -24
  168. package/lib/Utils/identity-change-handler.d.ts +0 -44
  169. package/lib/Utils/identity-change-handler.js +0 -50
  170. package/lib/Utils/index.d.ts +0 -22
  171. package/lib/Utils/link-preview.d.ts +0 -21
  172. package/lib/Utils/logger.d.ts +0 -12
  173. package/lib/Utils/lt-hash.d.ts +0 -8
  174. package/lib/Utils/make-mutex.d.ts +0 -9
  175. package/lib/Utils/message-retry-manager.d.ts +0 -115
  176. package/lib/Utils/messages-media.d.ts +0 -133
  177. package/lib/Utils/messages.d.ts +0 -91
  178. package/lib/Utils/noise-handler.d.ts +0 -20
  179. package/lib/Utils/offline-node-processor.d.ts +0 -17
  180. package/lib/Utils/offline-node-processor.js +0 -40
  181. package/lib/Utils/pre-key-manager.d.ts +0 -28
  182. package/lib/Utils/process-message.d.ts +0 -60
  183. package/lib/Utils/reporting-utils.d.ts +0 -11
  184. package/lib/Utils/signal.d.ts +0 -47
  185. package/lib/Utils/stanza-ack.d.ts +0 -11
  186. package/lib/Utils/sync-action-utils.d.ts +0 -19
  187. package/lib/Utils/tc-token-utils.d.ts +0 -37
  188. package/lib/Utils/use-multi-file-auth-state.d.ts +0 -13
  189. package/lib/Utils/validate-connection.d.ts +0 -11
  190. package/lib/WABinary/constants.d.ts +0 -28
  191. package/lib/WABinary/decode.d.ts +0 -7
  192. package/lib/WABinary/encode.d.ts +0 -3
  193. package/lib/WABinary/generic-utils.d.ts +0 -15
  194. package/lib/WABinary/index.d.ts +0 -6
  195. package/lib/WABinary/jid-utils.d.ts +0 -48
  196. package/lib/WABinary/types.d.ts +0 -19
  197. package/lib/WAM/BinaryInfo.d.ts +0 -9
  198. package/lib/WAM/constants.d.ts +0 -40
  199. package/lib/WAM/encode.d.ts +0 -3
  200. package/lib/WAM/index.d.ts +0 -4
  201. package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +0 -10
  202. package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +0 -23
  203. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +0 -13
  204. package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +0 -13
  205. package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts +0 -10
  206. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts +0 -26
  207. package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts +0 -10
  208. package/lib/WAUSync/Protocols/index.d.ts +0 -6
  209. package/lib/WAUSync/USyncQuery.d.ts +0 -30
  210. package/lib/WAUSync/USyncUser.d.ts +0 -17
  211. package/lib/WAUSync/index.d.ts +0 -4
  212. package/lib/index.d.ts +0 -12
@@ -1,8 +1,8 @@
1
1
  import NodeCache from '@cacheable/node-cache';
2
2
  import { Boom } from '@hapi/boom';
3
- import { proto } from '../../WAProto/index.js';
3
+ import { proto } from '../../WAProto/compiler.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, 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, generateWAMessageFromContent, 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,2480 +10,1533 @@ 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
-
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
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
+ }]
25
66
  });
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
- }
67
+ const mediaConnNode = getBinaryNodeChild(result, 'media_conn');
69
68
  const node = {
70
- tag: 'receipt',
71
- attrs: {
72
- id: messageIds[0]
73
- }
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()
74
78
  };
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;
87
- }
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
- ]
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
276
164
  });
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
- ]
318
- });
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
- }
334
- };
335
- const meJid = jidNormalizedUser(authState.creds.me.id);
336
- const msgId = await relayMessage(meJid, protocolMessage, {
337
- additionalAttributes: {
338
- category: 'peer',
339
- push_priority: 'high_force'
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',
442
+ attrs: {
443
+ jid
340
444
  },
341
- additionalNodes: [
342
- {
343
- tag: 'meta',
344
- attrs: { appdata: 'default' }
345
- }
346
- ]
445
+ content: [{
446
+ tag: 'enc',
447
+ attrs: {
448
+ v: '2',
449
+ type,
450
+ ...(extraAttrs || {})
451
+ },
452
+ content: ciphertext
453
+ }]
454
+ };
347
455
  });
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 }) => {
364
- try {
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;
399
- }
400
- catch (err) {
401
- logger.error({ jid, err }, 'Failed to encrypt for recipient');
402
- return null;
403
- }
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
404
557
  });
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
- }
439
- }
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
465
- },
466
- messageContextInfo: message.messageContextInfo
558
+ const stanza = {
559
+ tag: 'message',
560
+ attrs: {
561
+ to: jid,
562
+ id: msgId,
563
+ type: getMessageType(message),
564
+ ...(additionalAttributes || {})
565
+ },
566
+ content: binaryNodeContent
467
567
  };
468
- const extraAttrs = {};
469
- if (participant) {
470
- if (!isGroup && !isStatus) {
471
- additionalAttributes = { ...additionalAttributes, device_fanout: 'false' };
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
472
647
  }
473
- const { user, device } = jidDecode(participant.jid);
648
+ };
649
+ const senderKeySessionTargets = senderKeyRecipients;
650
+ await assertSessions(senderKeySessionTargets);
651
+ const result = await createParticipantNodes(senderKeyRecipients, senderKeyMsg, extraAttrs);
652
+ shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || result.shouldIncludeDeviceIdentity;
653
+ participants.push(...result.nodes);
654
+ }
655
+ binaryNodeContent.push({
656
+ tag: 'enc',
657
+ attrs: {
658
+ v: '2',
659
+ type: 'skmsg',
660
+ ...extraAttrs
661
+ },
662
+ content: ciphertext
663
+ });
664
+ await authState.keys.set({
665
+ 'sender-key-memory': {
666
+ [jid]: senderKeyMap
667
+ }
668
+ });
669
+ } else {
670
+ let ownId = meId;
671
+ if (isLid && meLid) {
672
+ ownId = meLid;
673
+ logger.debug({
674
+ to: jid,
675
+ ownId
676
+ }, 'Using LID identity for @lid conversation');
677
+ } else {
678
+ logger.debug({
679
+ to: jid,
680
+ ownId
681
+ }, 'Using PN identity for @s.whatsapp.net conversation');
682
+ }
683
+ const {
684
+ user: ownUser
685
+ } = jidDecode(ownId);
686
+ if (!isRetryResend) {
687
+ const patchedForReporting = await patchMessageBeforeSending(message, [jid]);
688
+ reportingMessage = Array.isArray(patchedForReporting) ? patchedForReporting.find(item => item.recipientJid === jid) || patchedForReporting[0] : patchedForReporting;
689
+ }
690
+ if (!isRetryResend) {
691
+ const targetUserServer = isLid ? 'lid' : 's.whatsapp.net';
692
+ devices.push({
693
+ user,
694
+ device: 0,
695
+ jid: jidEncode(user, targetUserServer, 0)
696
+ });
697
+ if (user !== ownUser) {
698
+ const ownUserServer = isLid ? 'lid' : 's.whatsapp.net';
699
+ const ownUserForAddressing = isLid && meLid ? jidDecode(meLid).user : jidDecode(meId).user;
474
700
  devices.push({
475
- user,
476
- device,
477
- jid: participant.jid
701
+ user: ownUserForAddressing,
702
+ device: 0,
703
+ jid: jidEncode(ownUserForAddressing, ownUserServer, 0)
478
704
  });
479
- }
480
- await authState.keys.transaction(async () => {
481
- const mediaType = getMediaType(message);
482
- if (mediaType) {
483
- extraAttrs['mediatype'] = mediaType;
705
+ }
706
+ if (additionalAttributes?.['category'] !== 'peer') {
707
+ devices.length = 0;
708
+ const senderIdentity = isLid && meLid ? jidEncode(jidDecode(meLid)?.user, 'lid', undefined) : jidEncode(jidDecode(meId)?.user, 's.whatsapp.net', undefined);
709
+ const sessionDevices = await getUSyncDevices([senderIdentity, jid], true, false);
710
+ devices.push(...sessionDevices);
711
+ logger.debug({
712
+ deviceCount: devices.length,
713
+ devices: devices.map(d => `${d.user}:${d.device}@${jidDecode(d.jid)?.server}`)
714
+ }, 'Device enumeration complete with unified addressing');
715
+ }
716
+ }
717
+ const allRecipients = [];
718
+ const meRecipients = [];
719
+ const otherRecipients = [];
720
+ const {
721
+ user: mePnUser
722
+ } = jidDecode(meId);
723
+ const {
724
+ user: meLidUser
725
+ } = meLid ? jidDecode(meLid) : {
726
+ user: null
727
+ };
728
+ for (const {
729
+ user,
730
+ jid
731
+ } of devices) {
732
+ const isExactSenderDevice = jid === meId || meLid && jid === meLid;
733
+ if (isExactSenderDevice) continue;
734
+ const isMe = user === mePnUser || user === meLidUser;
735
+ const hasDevice = jid.includes(':');
736
+ if (isRecipientOnly && isMe) continue;
737
+ if (isSecret && !(!isMe && !hasDevice)) continue;
738
+ if (protectedSend && !isMe && hasDevice) continue;
739
+ if (meOnly && !isMe) continue;
740
+ if (isMe) meRecipients.push(jid);else otherRecipients.push(jid);
741
+ allRecipients.push(jid);
742
+ }
743
+ await assertSessions(allRecipients);
744
+ const [{
745
+ nodes: meNodes,
746
+ shouldIncludeDeviceIdentity: s1
747
+ }, {
748
+ nodes: otherNodes,
749
+ shouldIncludeDeviceIdentity: s2
750
+ }] = await Promise.all([createParticipantNodes(meRecipients, meMsg || message, extraAttrs), createParticipantNodes(otherRecipients, message, extraAttrs, meMsg)]);
751
+ participants.push(...meNodes);
752
+ participants.push(...otherNodes);
753
+ if (meRecipients.length > 0 || otherRecipients.length > 0) {
754
+ extraAttrs['phash'] = generateParticipantHashV2([...meRecipients, ...otherRecipients]);
755
+ }
756
+ shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || s1 || s2;
757
+ }
758
+ if (isRetryResend) {
759
+ const isParticipantLid = isLidUser(retryParticipants.jid);
760
+ const isMe = areJidsSameUser(retryParticipants.jid, isParticipantLid ? meLid : meId);
761
+ let messageToSend = message;
762
+ if (isGroupOrStatus) {
763
+ let groupSenderIdentity;
764
+ if (meLid && (await signalRepository.hasSenderKey({
765
+ group: destinationJid,
766
+ meId: meLid
767
+ }))) {
768
+ groupSenderIdentity = meLid;
769
+ } else if (await signalRepository.hasSenderKey({
770
+ group: destinationJid,
771
+ meId
772
+ })) {
773
+ groupSenderIdentity = meId;
774
+ }
775
+ if (groupSenderIdentity) {
776
+ try {
777
+ const skdm = await signalRepository.getSenderKeyDistributionMessage({
778
+ group: destinationJid,
779
+ meId: groupSenderIdentity
780
+ });
781
+ messageToSend = {
782
+ ...message,
783
+ senderKeyDistributionMessage: {
784
+ groupId: destinationJid,
785
+ axolotlSenderKeyDistributionMessage: skdm
786
+ }
787
+ };
788
+ } catch (err) {
789
+ logger.warn({
790
+ err,
791
+ jid: destinationJid
792
+ }, 'failed to build SKDM for retry, sending without it');
484
793
  }
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;
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
506
911
  }
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;
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
+ });
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');
546
949
  }
547
- if (normalizeMessageContent(message)?.pinInChatMessage || normalizeMessageContent(message)?.reactionMessage) {
548
- extraAttrs['decrypt-fail'] = 'hide';
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'
990
+ },
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
549
1042
  }
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
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
631
1212
  });
632
- await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } });
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;
633
1222
  }
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;
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
+ }
710
1257
  }
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
- });
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
+ }
763
1269
  }
764
- if (participants.length) {
765
- if (additionalAttributes?.['category'] === 'peer') {
766
- const peerNode = participants[0]?.content?.[0];
767
- if (peerNode) {
768
- binaryNodeContent.push(peerNode);
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
+ }
769
1298
  }
1299
+ }]
770
1300
  }
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 || {})
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 || ''
786
1375
  },
787
- content: binaryNodeContent
788
- };
789
- if (participant) {
790
- if (isJidGroup(destinationJid)) {
791
- stanza.attrs.to = destinationJid;
792
- stanza.attrs.participant = participant.jid;
793
- }
794
- else if (areJidsSameUser(participant.jid, meId)) {
795
- stanza.attrs.to = participant.jid;
796
- stanza.attrs.recipient = destinationJid;
797
- }
798
- else {
799
- stanza.attrs.to = participant.jid;
800
- }
801
- }
802
- else {
803
- stanza.attrs.to = destinationJid;
804
- }
805
- if (shouldIncludeDeviceIdentity) {
806
- stanza.content.push({
807
- tag: 'device-identity',
808
- attrs: {},
809
- content: encodeSignedDeviceIdentity(authState.creds.account, true)
810
- });
811
- logger.debug({ jid }, 'adding device identity');
812
- }
813
- if (!isNewsletter &&
814
- !isRetryResend &&
815
- reportingMessage?.messageContextInfo?.messageSecret &&
816
- shouldIncludeReportingToken(reportingMessage)) {
817
- try {
818
- const encoded = encodeWAMessage(reportingMessage);
819
- const reportingKey = {
820
- id: msgId,
821
- fromMe: true,
822
- remoteJid: destinationJid,
823
- participant: participant?.jid
824
- };
825
- const reportingNode = await getMessageReportingToken(encoded, reportingMessage, reportingKey);
826
- if (reportingNode) {
827
- stanza.content.push(reportingNode);
828
- logger.trace({ jid }, 'added reporting token to message');
829
- }
830
- }
831
- catch (error) {
832
- logger.warn({ jid, trace: error?.stack }, 'failed to attach reporting token');
833
- }
834
- }
835
- const isPeerMessage = additionalAttributes?.['category'] === 'peer';
836
- const is1on1Send = !isGroup && !isRetryResend && !isStatus && !isNewsletter && !isPeerMessage;
837
- const tcTokenJid = is1on1Send ? await resolveTcTokenJid(destinationJid, getLIDForPN) : destinationJid;
838
- const contactTcTokenData = is1on1Send ? await authState.keys.get('tctoken', [tcTokenJid]) : {};
839
- const existingTokenEntry = contactTcTokenData[tcTokenJid];
840
- let tcTokenBuffer = existingTokenEntry?.token;
841
- if (tcTokenBuffer?.length && isTcTokenExpired(existingTokenEntry?.timestamp)) {
842
- logger.debug({ jid: destinationJid, timestamp: existingTokenEntry?.timestamp }, 'tctoken expired, clearing');
843
- tcTokenBuffer = undefined;
844
- const cleared = existingTokenEntry?.senderTimestamp !== undefined
845
- ? { token: Buffer.alloc(0), senderTimestamp: existingTokenEntry.senderTimestamp }
846
- : null;
847
- try {
848
- await authState.keys.set({ tctoken: { [tcTokenJid]: cleared } });
849
- }
850
- catch (err) {
851
- logger.debug({ jid: destinationJid, err: err?.message }, 'failed to persist tctoken expiry cleanup');
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: []
852
1390
  }
1391
+ },
1392
+ __typename: 'GenAISingleLayoutViewModel'
853
1393
  }
854
- if (tcTokenBuffer?.length && sock.serverProps.privacyTokenOn1to1) {
855
- stanza.content.push({
856
- tag: 'tctoken',
857
- attrs: {},
858
- content: tcTokenBuffer
859
- });
860
- }
861
- if (additionalNodes && additionalNodes.length > 0) {
862
- stanza.content.push(...additionalNodes);
863
- }
864
- logger.debug({ msgId }, `sending message to ${participants.length} devices`);
865
- await sendNode(stanza);
866
- const isProtocolMsg = !!normalizeMessageContent(message)?.protocolMessage;
867
- const isBotOrPSA = destinationJid === PSA_WID || isJidBot(destinationJid) || isJidMetaAI(destinationJid);
868
- if (is1on1Send &&
869
- !isProtocolMsg &&
870
- !isBotOrPSA &&
871
- shouldSendNewTcToken(existingTokenEntry?.senderTimestamp) &&
872
- !inFlightTcTokenIssuance.has(tcTokenJid)) {
873
- inFlightTcTokenIssuance.add(tcTokenJid);
874
- const issueTimestamp = unixTimestampSeconds();
875
- const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping);
876
- resolveIssuanceJid(destinationJid, sock.serverProps.lidTrustedTokenIssueToLid, getLIDForPN, getPNForLID)
877
- .then(issueJid => issuePrivacyTokens([issueJid], issueTimestamp))
878
- .then(async (result) => {
879
- await storeTcTokensFromIqResult({
880
- result,
881
- fallbackJid: tcTokenJid,
882
- keys: authState.keys,
883
- getLIDForPN
884
- });
885
- const currentData = await authState.keys.get('tctoken', [tcTokenJid]);
886
- const currentEntry = currentData[tcTokenJid];
887
- const indexWrite = await buildMergedTcTokenIndexWrite(authState.keys, [tcTokenJid]);
888
- await authState.keys.set({
889
- tctoken: {
890
- [tcTokenJid]: {
891
- token: Buffer.alloc(0),
892
- ...currentEntry,
893
- senderTimestamp: issueTimestamp
894
- },
895
- ...indexWrite
896
- }
897
- });
898
- })
899
- .catch(err => {
900
- logger.debug({ jid: destinationJid, err: err?.message }, 'fire-and-forget tctoken issuance failed');
901
- })
902
- .finally(() => {
903
- inFlightTcTokenIssuance.delete(tcTokenJid);
904
- });
905
- }
906
- if (messageRetryManager && !participant) {
907
- messageRetryManager.addRecentMessage(destinationJid, msgId, message);
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
+ }
908
1456
  }
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'
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 || {})
1008
1482
  },
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
- ]
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
1023
1493
  });
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 || []
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'
1160
1516
  }
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;
1517
+ });
1518
+ } else if (isEventMsg) {
1519
+ additionalNodes.push({
1520
+ tag: 'meta',
1521
+ attrs: {
1522
+ event_type: 'creation'
2387
1523
  }
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
- };
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
1532
+ });
1533
+ if (config.emitOwnEvents) {
1534
+ process.nextTick(async () => {
1535
+ await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
1536
+ });
1537
+ }
1538
+ return fullMsg;
1539
+ }
1540
+ }
1541
+ };
1542
+ };