@wenzyx1/bails 1.0.0

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 (218) hide show
  1. package/README.md +231 -0
  2. package/WAProto/fix-imports.js +85 -0
  3. package/WAProto/index.d.ts +79257 -0
  4. package/WAProto/index.js +242946 -0
  5. package/engine-requirements.js +10 -0
  6. package/lib/Defaults/baileys-version.json +3 -0
  7. package/lib/Defaults/index.d.ts +53 -0
  8. package/lib/Defaults/index.js +147 -0
  9. package/lib/Defaults/phonenumber-mcc.json +223 -0
  10. package/lib/Signal/Group/ciphertext-message.d.ts +9 -0
  11. package/lib/Signal/Group/ciphertext-message.js +15 -0
  12. package/lib/Signal/Group/group-session-builder.d.ts +14 -0
  13. package/lib/Signal/Group/group-session-builder.js +64 -0
  14. package/lib/Signal/Group/group_cipher.d.ts +17 -0
  15. package/lib/Signal/Group/group_cipher.js +93 -0
  16. package/lib/Signal/Group/index.d.ts +11 -0
  17. package/lib/Signal/Group/index.js +57 -0
  18. package/lib/Signal/Group/keyhelper.d.ts +10 -0
  19. package/lib/Signal/Group/keyhelper.js +55 -0
  20. package/lib/Signal/Group/queue-job.d.ts +1 -0
  21. package/lib/Signal/Group/queue-job.js +53 -0
  22. package/lib/Signal/Group/sender-chain-key.d.ts +13 -0
  23. package/lib/Signal/Group/sender-chain-key.js +34 -0
  24. package/lib/Signal/Group/sender-key-distribution-message.d.ts +16 -0
  25. package/lib/Signal/Group/sender-key-distribution-message.js +66 -0
  26. package/lib/Signal/Group/sender-key-message.d.ts +18 -0
  27. package/lib/Signal/Group/sender-key-message.js +69 -0
  28. package/lib/Signal/Group/sender-key-name.d.ts +17 -0
  29. package/lib/Signal/Group/sender-key-name.js +51 -0
  30. package/lib/Signal/Group/sender-key-record.d.ts +30 -0
  31. package/lib/Signal/Group/sender-key-record.js +53 -0
  32. package/lib/Signal/Group/sender-key-state.d.ts +38 -0
  33. package/lib/Signal/Group/sender-key-state.js +99 -0
  34. package/lib/Signal/Group/sender-message-key.d.ts +11 -0
  35. package/lib/Signal/Group/sender-message-key.js +29 -0
  36. package/lib/Signal/libsignal.d.ts +3 -0
  37. package/lib/Signal/libsignal.js +454 -0
  38. package/lib/Signal/lid-mapping.js +280 -0
  39. package/lib/Socket/Client/abstract-socket-client.d.ts +17 -0
  40. package/lib/Socket/Client/abstract-socket-client.js +13 -0
  41. package/lib/Socket/Client/index.d.ts +3 -0
  42. package/lib/Socket/Client/index.js +19 -0
  43. package/lib/Socket/Client/mobile-socket-client.d.ts +13 -0
  44. package/lib/Socket/Client/mobile-socket-client.js +65 -0
  45. package/lib/Socket/Client/web-socket-client.d.ts +12 -0
  46. package/lib/Socket/Client/web-socket-client.js +62 -0
  47. package/lib/Socket/business.d.ts +171 -0
  48. package/lib/Socket/business.js +342 -0
  49. package/lib/Socket/chats.d.ts +267 -0
  50. package/lib/Socket/chats.js +978 -0
  51. package/lib/Socket/communities.mjs +428 -0
  52. package/lib/Socket/dugong.d.ts +254 -0
  53. package/lib/Socket/dugong.js +558 -0
  54. package/lib/Socket/groups.d.ts +115 -0
  55. package/lib/Socket/groups.js +380 -0
  56. package/lib/Socket/index.d.ts +173 -0
  57. package/lib/Socket/index.js +32 -0
  58. package/lib/Socket/luxu.js +545 -0
  59. package/lib/Socket/messages-recv.d.ts +161 -0
  60. package/lib/Socket/messages-recv.js +1124 -0
  61. package/lib/Socket/messages-send.d.ts +149 -0
  62. package/lib/Socket/messages-send.js +1476 -0
  63. package/lib/Socket/mex.mjs +50 -0
  64. package/lib/Socket/newsletter.d.ts +134 -0
  65. package/lib/Socket/newsletter.js +351 -0
  66. package/lib/Socket/registration.d.ts +267 -0
  67. package/lib/Socket/registration.js +166 -0
  68. package/lib/Socket/socket.d.ts +43 -0
  69. package/lib/Socket/socket.js +904 -0
  70. package/lib/Socket/username.mjs +132 -0
  71. package/lib/Socket/usync.d.ts +36 -0
  72. package/lib/Socket/usync.js +70 -0
  73. package/lib/Store/index.d.ts +3 -0
  74. package/lib/Store/index.js +10 -0
  75. package/lib/Store/keyed-db.d.ts +30 -0
  76. package/lib/Store/keyed-db.js +233 -0
  77. package/lib/Store/make-cache-manager-store.d.ts +13 -0
  78. package/lib/Store/make-cache-manager-store.js +83 -0
  79. package/lib/Store/make-in-memory-store.d.ts +118 -0
  80. package/lib/Store/make-in-memory-store.js +427 -0
  81. package/lib/Store/make-ordered-dictionary.d.ts +13 -0
  82. package/lib/Store/make-ordered-dictionary.js +81 -0
  83. package/lib/Store/object-repository.d.ts +10 -0
  84. package/lib/Store/object-repository.js +27 -0
  85. package/lib/Types/Auth.d.ts +110 -0
  86. package/lib/Types/Auth.js +2 -0
  87. package/lib/Types/Call.d.ts +13 -0
  88. package/lib/Types/Call.js +2 -0
  89. package/lib/Types/Chat.d.ts +102 -0
  90. package/lib/Types/Chat.js +4 -0
  91. package/lib/Types/Contact.d.ts +19 -0
  92. package/lib/Types/Contact.js +2 -0
  93. package/lib/Types/Events.d.ts +157 -0
  94. package/lib/Types/Events.js +2 -0
  95. package/lib/Types/GroupMetadata.d.ts +55 -0
  96. package/lib/Types/GroupMetadata.js +2 -0
  97. package/lib/Types/Label.d.ts +35 -0
  98. package/lib/Types/Label.js +27 -0
  99. package/lib/Types/LabelAssociation.d.ts +29 -0
  100. package/lib/Types/LabelAssociation.js +9 -0
  101. package/lib/Types/Message.d.ts +273 -0
  102. package/lib/Types/Message.js +9 -0
  103. package/lib/Types/Mex.js +39 -0
  104. package/lib/Types/Newsletter.d.ts +103 -0
  105. package/lib/Types/Newsletter.js +38 -0
  106. package/lib/Types/Product.d.ts +78 -0
  107. package/lib/Types/Product.js +2 -0
  108. package/lib/Types/Signal.d.ts +57 -0
  109. package/lib/Types/Signal.js +2 -0
  110. package/lib/Types/Socket.d.ts +111 -0
  111. package/lib/Types/Socket.js +2 -0
  112. package/lib/Types/State.d.ts +27 -0
  113. package/lib/Types/State.js +2 -0
  114. package/lib/Types/USync.d.ts +25 -0
  115. package/lib/Types/USync.js +2 -0
  116. package/lib/Types/index.d.ts +57 -0
  117. package/lib/Types/index.js +43 -0
  118. package/lib/Utils/Mex.js +39 -0
  119. package/lib/Utils/auth-utils.d.ts +18 -0
  120. package/lib/Utils/auth-utils.js +206 -0
  121. package/lib/Utils/baileys-event-stream.d.ts +16 -0
  122. package/lib/Utils/baileys-event-stream.js +57 -0
  123. package/lib/Utils/browser-presets.js +64 -0
  124. package/lib/Utils/business.d.ts +22 -0
  125. package/lib/Utils/business.js +234 -0
  126. package/lib/Utils/chat-utils.d.ts +71 -0
  127. package/lib/Utils/chat-utils.js +726 -0
  128. package/lib/Utils/companion-reg-client-utils.js +40 -0
  129. package/lib/Utils/crypto.d.ts +41 -0
  130. package/lib/Utils/crypto.js +151 -0
  131. package/lib/Utils/decode-wa-message.d.ts +19 -0
  132. package/lib/Utils/decode-wa-message.js +198 -0
  133. package/lib/Utils/event-buffer.d.ts +35 -0
  134. package/lib/Utils/event-buffer.js +514 -0
  135. package/lib/Utils/extended-messages.js +231 -0
  136. package/lib/Utils/generics.d.ts +92 -0
  137. package/lib/Utils/generics.js +425 -0
  138. package/lib/Utils/history.d.ts +15 -0
  139. package/lib/Utils/history.js +96 -0
  140. package/lib/Utils/identity-change-handler.js +52 -0
  141. package/lib/Utils/index.d.ts +17 -0
  142. package/lib/Utils/index.js +40 -0
  143. package/lib/Utils/link-preview.d.ts +21 -0
  144. package/lib/Utils/link-preview.js +93 -0
  145. package/lib/Utils/logger.d.ts +4 -0
  146. package/lib/Utils/logger.js +7 -0
  147. package/lib/Utils/lt-hash.d.ts +12 -0
  148. package/lib/Utils/lt-hash.js +51 -0
  149. package/lib/Utils/make-mutex.d.ts +7 -0
  150. package/lib/Utils/make-mutex.js +43 -0
  151. package/lib/Utils/message-composer.js +293 -0
  152. package/lib/Utils/message-retry-manager.js +226 -0
  153. package/lib/Utils/messages-media.d.ts +116 -0
  154. package/lib/Utils/messages-media.js +815 -0
  155. package/lib/Utils/messages.d.ts +77 -0
  156. package/lib/Utils/messages.js +888 -0
  157. package/lib/Utils/noise-handler.d.ts +21 -0
  158. package/lib/Utils/noise-handler.js +155 -0
  159. package/lib/Utils/offline-node-processor.js +46 -0
  160. package/lib/Utils/pre-key-manager.js +95 -0
  161. package/lib/Utils/process-message.d.ts +41 -0
  162. package/lib/Utils/process-message.js +321 -0
  163. package/lib/Utils/reporting-utils.js +272 -0
  164. package/lib/Utils/signal.d.ts +32 -0
  165. package/lib/Utils/signal.js +160 -0
  166. package/lib/Utils/stanza-ack.js +38 -0
  167. package/lib/Utils/sync-action-utils.js +50 -0
  168. package/lib/Utils/tc-token-utils.js +169 -0
  169. package/lib/Utils/use-multi-file-auth-state.d.ts +13 -0
  170. package/lib/Utils/use-multi-file-auth-state.js +119 -0
  171. package/lib/Utils/validate-connection.d.ts +11 -0
  172. package/lib/Utils/validate-connection.js +229 -0
  173. package/lib/WABinary/constants.d.ts +30 -0
  174. package/lib/WABinary/constants.js +40 -0
  175. package/lib/WABinary/decode.d.ts +7 -0
  176. package/lib/WABinary/decode.js +252 -0
  177. package/lib/WABinary/encode.d.ts +3 -0
  178. package/lib/WABinary/encode.js +265 -0
  179. package/lib/WABinary/generic-utils.d.ts +17 -0
  180. package/lib/WABinary/generic-utils.js +204 -0
  181. package/lib/WABinary/index.d.ts +5 -0
  182. package/lib/WABinary/index.js +21 -0
  183. package/lib/WABinary/jid-utils.d.ts +31 -0
  184. package/lib/WABinary/jid-utils.js +132 -0
  185. package/lib/WABinary/types.d.ts +18 -0
  186. package/lib/WABinary/types.js +2 -0
  187. package/lib/WAM/BinaryInfo.d.ts +17 -0
  188. package/lib/WAM/BinaryInfo.js +13 -0
  189. package/lib/WAM/constants.d.ts +38 -0
  190. package/lib/WAM/constants.js +15350 -0
  191. package/lib/WAM/encode.d.ts +3 -0
  192. package/lib/WAM/encode.js +155 -0
  193. package/lib/WAM/index.d.ts +3 -0
  194. package/lib/WAM/index.js +19 -0
  195. package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +9 -0
  196. package/lib/WAUSync/Protocols/USyncContactProtocol.js +55 -0
  197. package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +22 -0
  198. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +57 -0
  199. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +12 -0
  200. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +30 -0
  201. package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +12 -0
  202. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +42 -0
  203. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +27 -0
  204. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts +25 -0
  205. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +53 -0
  206. package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts +8 -0
  207. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +24 -0
  208. package/lib/WAUSync/Protocols/index.d.ts +4 -0
  209. package/lib/WAUSync/Protocols/index.js +21 -0
  210. package/lib/WAUSync/USyncQuery.d.ts +28 -0
  211. package/lib/WAUSync/USyncQuery.js +93 -0
  212. package/lib/WAUSync/USyncUser.d.ts +12 -0
  213. package/lib/WAUSync/USyncUser.js +34 -0
  214. package/lib/WAUSync/index.d.ts +3 -0
  215. package/lib/WAUSync/index.js +19 -0
  216. package/lib/index.d.ts +12 -0
  217. package/lib/index.js +52 -0
  218. package/package.json +112 -0
@@ -0,0 +1,1476 @@
1
+ import NodeCache from '@cacheable/node-cache';
2
+ import { Boom } from '@hapi/boom';
3
+ import { proto } from '../../WAProto/index.js';
4
+ import { DEFAULT_CACHE_TTLS, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
5
+ import { aggregateMessageKeysNotFromMe, assertMediaContent, bindWaitForEvent, decryptMediaRetryData, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, generateMessageIDV2, generateWAMessage, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, MessageRetryManager, normalizeMessageContent, parseAndInjectE2ESessions, unixTimestampSeconds } from '../Utils/index.js';
6
+ import { getUrlInfo } from '../Utils/link-preview.js';
7
+ import { makeKeyedMutex, makeMutex } from '../Utils/make-mutex.js';
8
+ import { getMessageReportingToken, shouldIncludeReportingToken } from '../Utils/reporting-utils.js';
9
+ import { buildMergedTcTokenIndexWrite, isTcTokenExpired, resolveIssuanceJid, resolveTcTokenJid, shouldSendNewTcToken, storeTcTokensFromIqResult } from '../Utils/tc-token-utils.js';
10
+ import { areJidsSameUser, getBinaryNodeChild, getBinaryNodeChildren, isHostedLidUser, isHostedPnUser, isJidBot, isJidGroup, isJidMetaAI, isLidUser, isPnUser, jidDecode, jidEncode, jidNormalizedUser, PSA_WID, S_WHATSAPP_NET, getAdditionalNode, getBinaryNodeFilter, getBinaryFilteredBizBot, isInteropUser } from '../WABinary/index.js';
11
+ import { USyncQuery, USyncUser } from '../WAUSync/index.js';
12
+ import { makeNewsletterSocket } from './newsletter.js';
13
+ import imup from './luxu.js';
14
+ import * as Utils_1 from '../Utils/index.js';
15
+ import { createHash, randomBytes } from 'crypto';
16
+ const DEF_MEDIA_HOST = 'mmg.whatsapp.net';
17
+ // --- Missing from this fork's Utils build; local implementations so the module can load. ---
18
+ // Generates an iOS-style message ID (20 uppercase hex chars) for when config.browser signals iOS/Safari.
19
+ const generateIOSMessageID = () => randomBytes(10).toString('hex').toUpperCase();
20
+ // Participant-list hash ("phash") WhatsApp uses server-side to detect stale device lists on group sends.
21
+ const generateParticipantHashV2 = (participants) => {
22
+ const sorted = [...participants].sort();
23
+ const hash = createHash('sha256').update(sorted.join(',')).digest('base64').slice(0, 6);
24
+ return `2:${hash}`;
25
+ };
26
+ // Local cache for message secrets (used by WA's bot/reaction e2e feature). No reader exists
27
+ // elsewhere in this fork yet — this just stores it so callers relying on it later don't break.
28
+ const botMessageSecrets = new Map();
29
+ const setBotMessageSecret = (msgId, secret, jid) => {
30
+ botMessageSecrets.set(msgId, { secret, jid, ts: Date.now() });
31
+ };
32
+ export const makeMessagesSocket = (config) => {
33
+ const { logger, linkPreviewImageThumbnailWidth, generateHighQualityLinkPreview, options: httpRequestOptions, patchMessageBeforeSending, cachedGroupMetadata, enableRecentMessageCache, maxMsgRetryCount, aiLabel } = config;
34
+ const sock = makeNewsletterSocket(config);
35
+ const { ev, authState, messageMutex, signalRepository, upsertMessage, query, fetchPrivacySettings, sendNode, groupMetadata, groupToggleEphemeral, registerSocketEndHandler } = sock;
36
+ const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping);
37
+ /**
38
+ * Set of tctoken storage JIDs with a fire-and-forget `issuePrivacyTokens` IQ in flight.
39
+ * Prevents duplicate IQs from rapid back-to-back sends before `senderTimestamp` persists.
40
+ * Entries are always removed in `.finally()`, so the set is bounded by concurrency.
41
+ */
42
+ const inFlightTcTokenIssuance = new Set();
43
+ const userDevicesCache = config.userDevicesCache ||
44
+ new NodeCache({
45
+ stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES, // 5 minutes
46
+ useClones: false
47
+ });
48
+ /** Serializes writes to userDevicesCache across USync refresh and device-notification handling. */
49
+ const devicesMutex = makeMutex();
50
+ // Initialize message retry manager if enabled
51
+ const messageRetryManager = enableRecentMessageCache ? new MessageRetryManager(logger, maxMsgRetryCount) : null;
52
+ // Prevent race conditions in Signal session encryption by user
53
+ const encryptionMutex = makeKeyedMutex();
54
+ let mediaConn;
55
+ /** Per-socket media host; updated whenever media_conn is fetched. Defaults to the public WhatsApp host. */
56
+ let mediaHost = DEF_MEDIA_HOST;
57
+ const refreshMediaConn = async (forceGet = false) => {
58
+ const media = await mediaConn;
59
+ if (!media || forceGet || new Date().getTime() - media.fetchDate.getTime() > media.ttl * 1000) {
60
+ mediaConn = (async () => {
61
+ const result = await query({
62
+ tag: 'iq',
63
+ attrs: {
64
+ type: 'set',
65
+ xmlns: 'w:m',
66
+ to: S_WHATSAPP_NET
67
+ },
68
+ content: [{ tag: 'media_conn', attrs: {} }]
69
+ });
70
+ const mediaConnNode = getBinaryNodeChild(result, 'media_conn');
71
+ // TODO: explore full length of data that whatsapp provides
72
+ const node = {
73
+ hosts: getBinaryNodeChildren(mediaConnNode, 'host').map(({ attrs }) => ({
74
+ hostname: attrs.hostname,
75
+ maxContentLengthBytes: +attrs.maxContentLengthBytes
76
+ })),
77
+ auth: mediaConnNode.attrs.auth,
78
+ ttl: +mediaConnNode.attrs.ttl,
79
+ fetchDate: new Date()
80
+ };
81
+ logger.debug('fetched media conn');
82
+ if (node.hosts[0]) {
83
+ mediaHost = node.hosts[0].hostname;
84
+ }
85
+ return node;
86
+ })();
87
+ }
88
+ return mediaConn;
89
+ };
90
+ /**
91
+ * generic send receipt function
92
+ * used for receipts of phone call, read, delivery etc.
93
+ * */
94
+ const sendReceipt = async (jid, participant, messageIds, type) => {
95
+ if (!messageIds || messageIds.length === 0) {
96
+ throw new Boom('missing ids in receipt');
97
+ }
98
+ const node = {
99
+ tag: 'receipt',
100
+ attrs: {
101
+ id: messageIds[0]
102
+ }
103
+ };
104
+ const isReadReceipt = type === 'read' || type === 'read-self';
105
+ if (isReadReceipt) {
106
+ node.attrs.t = unixTimestampSeconds().toString();
107
+ }
108
+ if (type === 'sender' && (isPnUser(jid) || isLidUser(jid))) {
109
+ node.attrs.recipient = jid;
110
+ node.attrs.to = participant;
111
+ }
112
+ else {
113
+ node.attrs.to = jid;
114
+ if (participant) {
115
+ node.attrs.participant = participant;
116
+ }
117
+ }
118
+ if (type) {
119
+ node.attrs.type = type;
120
+ }
121
+ const remainingMessageIds = messageIds.slice(1);
122
+ if (remainingMessageIds.length) {
123
+ node.content = [
124
+ {
125
+ tag: 'list',
126
+ attrs: {},
127
+ content: remainingMessageIds.map(id => ({
128
+ tag: 'item',
129
+ attrs: { id }
130
+ }))
131
+ }
132
+ ];
133
+ }
134
+ logger.debug({ attrs: node.attrs, messageIds }, 'sending receipt for messages');
135
+ await sendNode(node);
136
+ };
137
+ /** Correctly bulk send receipts to multiple chats, participants */
138
+ const sendReceipts = async (keys, type) => {
139
+ const recps = aggregateMessageKeysNotFromMe(keys);
140
+ for (const { jid, participant, messageIds } of recps) {
141
+ await sendReceipt(jid, participant, messageIds, type);
142
+ }
143
+ };
144
+ /** Bulk read messages. Keys can be from different chats & participants */
145
+ const readMessages = async (keys) => {
146
+ const privacySettings = await fetchPrivacySettings();
147
+ // based on privacy settings, we have to change the read type
148
+ const readType = privacySettings.readreceipts === 'all' ? 'read' : 'read-self';
149
+ await sendReceipts(keys, readType);
150
+ };
151
+ /** Fetch all the devices we've to send a message to */
152
+ const getUSyncDevices = async (jids, useCache, ignoreZeroDevices) => {
153
+ const deviceResults = [];
154
+ if (!useCache) {
155
+ logger.debug('not using cache for devices');
156
+ }
157
+ const toFetch = [];
158
+ const jidsWithUser = jids
159
+ .map(jid => {
160
+ const decoded = jidDecode(jid);
161
+ const user = decoded?.user;
162
+ const device = decoded?.device;
163
+ const isExplicitDevice = typeof device === 'number' && device >= 0;
164
+ if (isExplicitDevice && user) {
165
+ deviceResults.push({
166
+ user,
167
+ device,
168
+ jid
169
+ });
170
+ return null;
171
+ }
172
+ jid = jidNormalizedUser(jid);
173
+ return { jid, user };
174
+ })
175
+ .filter(jid => jid !== null);
176
+ let mgetDevices;
177
+ if (useCache && userDevicesCache.mget) {
178
+ const usersToFetch = jidsWithUser.map(j => j?.user).filter(Boolean);
179
+ mgetDevices = await userDevicesCache.mget(usersToFetch);
180
+ }
181
+ for (const { jid, user } of jidsWithUser) {
182
+ if (useCache) {
183
+ const devices = mgetDevices?.[user] ||
184
+ (userDevicesCache.mget ? undefined : (await userDevicesCache.get(user)));
185
+ if (devices) {
186
+ const devicesWithJid = devices.map(d => ({
187
+ ...d,
188
+ jid: jidEncode(d.user, d.server, d.device)
189
+ }));
190
+ deviceResults.push(...devicesWithJid);
191
+ logger.trace({ user }, 'using cache for devices');
192
+ }
193
+ else {
194
+ toFetch.push(jid);
195
+ }
196
+ }
197
+ else {
198
+ toFetch.push(jid);
199
+ }
200
+ }
201
+ if (!toFetch.length) {
202
+ return deviceResults;
203
+ }
204
+ const requestedLidUsers = new Set();
205
+ for (const jid of toFetch) {
206
+ if (isLidUser(jid) || isHostedLidUser(jid)) {
207
+ const user = jidDecode(jid)?.user;
208
+ if (user)
209
+ requestedLidUsers.add(user);
210
+ }
211
+ }
212
+ const query = new USyncQuery().withContext('message').withDeviceProtocol().withLIDProtocol();
213
+ for (const jid of toFetch) {
214
+ query.withUser(new USyncUser().withId(jid)); // todo: investigate - the idea here is that <user> should have an inline lid field with the lid being the pn equivalent
215
+ }
216
+ const result = await sock.executeUSyncQuery(query);
217
+ if (result) {
218
+ // TODO: LID MAP this stuff (lid protocol will now return lid with devices)
219
+ const lidResults = result.list.filter(a => !!a.lid);
220
+ if (lidResults.length > 0) {
221
+ logger.trace('Storing LID maps from device call');
222
+ await signalRepository.lidMapping.storeLIDPNMappings(lidResults.map(a => ({ lid: a.lid, pn: a.id })));
223
+ // Force-refresh sessions for newly mapped LIDs to align identity addressing
224
+ try {
225
+ const lids = lidResults.map(a => a.lid);
226
+ if (lids.length) {
227
+ await assertSessions(lids, true);
228
+ }
229
+ }
230
+ catch (e) {
231
+ logger.warn({ e, count: lidResults.length }, 'failed to assert sessions for newly mapped LIDs');
232
+ }
233
+ }
234
+ const extracted = extractDeviceJids(result?.list, authState.creds.me.id, authState.creds.me.lid, ignoreZeroDevices);
235
+ const deviceMap = {};
236
+ for (const item of extracted) {
237
+ deviceMap[item.user] = deviceMap[item.user] || [];
238
+ deviceMap[item.user]?.push(item);
239
+ }
240
+ // Process each user's devices as a group for bulk LID migration
241
+ for (const [user, userDevices] of Object.entries(deviceMap)) {
242
+ const isLidUser = requestedLidUsers.has(user);
243
+ // Process all devices for this user
244
+ for (const item of userDevices) {
245
+ const finalJid = isLidUser
246
+ ? jidEncode(user, item.server, item.device)
247
+ : jidEncode(item.user, item.server, item.device);
248
+ deviceResults.push({
249
+ ...item,
250
+ jid: finalJid
251
+ });
252
+ logger.debug({
253
+ user: item.user,
254
+ device: item.device,
255
+ finalJid,
256
+ usedLid: isLidUser
257
+ }, 'Processed device with LID priority');
258
+ }
259
+ }
260
+ await devicesMutex.mutex(async () => {
261
+ if (userDevicesCache.mset) {
262
+ // if the cache supports mset, we can set all devices in one go
263
+ await userDevicesCache.mset(Object.entries(deviceMap).map(([key, value]) => ({ key, value })));
264
+ }
265
+ else {
266
+ for (const key in deviceMap) {
267
+ if (deviceMap[key])
268
+ await userDevicesCache.set(key, deviceMap[key]);
269
+ }
270
+ }
271
+ });
272
+ const userDeviceUpdates = {};
273
+ for (const [userId, devices] of Object.entries(deviceMap)) {
274
+ if (devices && devices.length > 0) {
275
+ userDeviceUpdates[userId] = devices.map(d => d.device?.toString() || '0');
276
+ }
277
+ }
278
+ if (Object.keys(userDeviceUpdates).length > 0) {
279
+ try {
280
+ await authState.keys.set({ 'device-list': userDeviceUpdates });
281
+ logger.debug({ userCount: Object.keys(userDeviceUpdates).length }, 'stored user device lists for bulk migration');
282
+ }
283
+ catch (error) {
284
+ logger.warn({ error }, 'failed to store user device lists');
285
+ }
286
+ }
287
+ }
288
+ return deviceResults;
289
+ };
290
+ /**
291
+ * Update Member Label
292
+ */
293
+ const updateMemberLabel = (jid, memberLabel) => {
294
+ return relayMessage(jid, {
295
+ protocolMessage: {
296
+ type: proto.Message.ProtocolMessage.Type.GROUP_MEMBER_LABEL_CHANGE,
297
+ memberLabel: {
298
+ label: memberLabel?.slice(0, 30),
299
+ labelTimestamp: unixTimestampSeconds()
300
+ }
301
+ }
302
+ }, {
303
+ additionalNodes: [
304
+ {
305
+ tag: 'meta',
306
+ attrs: {
307
+ tag_reason: 'user_update',
308
+ appdata: 'member_tag'
309
+ },
310
+ content: undefined
311
+ }
312
+ ]
313
+ });
314
+ };
315
+ const assertSessions = async (jids, force) => {
316
+ let didFetchNewSession = false;
317
+ const uniqueJids = [...new Set(jids)];
318
+ const jidsRequiringFetch = [];
319
+ logger.debug({ jids }, 'assertSessions call with jids');
320
+ for (const jid of uniqueJids) {
321
+ if (!force) {
322
+ const sessionValidation = await signalRepository.validateSession(jid);
323
+ if (sessionValidation.exists) {
324
+ continue;
325
+ }
326
+ }
327
+ jidsRequiringFetch.push(jid);
328
+ }
329
+ if (jidsRequiringFetch.length) {
330
+ // LID if mapped, otherwise original
331
+ const wireJids = [
332
+ ...jidsRequiringFetch.filter(jid => !!isLidUser(jid) || !!isHostedLidUser(jid)),
333
+ ...((await signalRepository.lidMapping.getLIDsForPNs(jidsRequiringFetch.filter(jid => !!isPnUser(jid) || !!isHostedPnUser(jid)))) || []).map(a => a.lid)
334
+ ];
335
+ logger.debug({ jidsRequiringFetch, wireJids }, 'fetching sessions');
336
+ const result = await query({
337
+ tag: 'iq',
338
+ attrs: {
339
+ xmlns: 'encrypt',
340
+ type: 'get',
341
+ to: S_WHATSAPP_NET
342
+ },
343
+ content: [
344
+ {
345
+ tag: 'key',
346
+ attrs: {},
347
+ content: wireJids.map(jid => {
348
+ const attrs = { jid };
349
+ if (force)
350
+ attrs.reason = 'identity';
351
+ return { tag: 'user', attrs };
352
+ })
353
+ }
354
+ ]
355
+ });
356
+ await parseAndInjectE2ESessions(result, signalRepository);
357
+ didFetchNewSession = true;
358
+ }
359
+ return didFetchNewSession;
360
+ };
361
+ const sendPeerDataOperationMessage = async (pdoMessage) => {
362
+ //TODO: for later, abstract the logic to send a Peer Message instead of just PDO - useful for App State Key Resync with phone
363
+ if (!authState.creds.me?.id) {
364
+ throw new Boom('Not authenticated');
365
+ }
366
+ const protocolMessage = {
367
+ protocolMessage: {
368
+ peerDataOperationRequestMessage: pdoMessage,
369
+ type: proto.Message.ProtocolMessage.Type.PEER_DATA_OPERATION_REQUEST_MESSAGE
370
+ }
371
+ };
372
+ const meJid = jidNormalizedUser(authState.creds.me.id);
373
+ const msgId = await relayMessage(meJid, protocolMessage, {
374
+ additionalAttributes: {
375
+ category: 'peer',
376
+ push_priority: 'high_force'
377
+ },
378
+ additionalNodes: [
379
+ {
380
+ tag: 'meta',
381
+ attrs: { appdata: 'default' }
382
+ }
383
+ ]
384
+ });
385
+ return msgId;
386
+ };
387
+ const createParticipantNodes = async (recipientJids, message, extraAttrs, dsmMessage) => {
388
+ if (!recipientJids.length) {
389
+ return { nodes: [], shouldIncludeDeviceIdentity: false };
390
+ }
391
+ const patched = await patchMessageBeforeSending(message, recipientJids);
392
+ const patchedMessages = Array.isArray(patched)
393
+ ? patched
394
+ : recipientJids.map(jid => ({ recipientJid: jid, message: patched }));
395
+ let shouldIncludeDeviceIdentity = false;
396
+ const meId = authState.creds.me.id;
397
+ const meLid = authState.creds.me?.lid;
398
+ const meLidUser = meLid ? jidDecode(meLid)?.user : null;
399
+ const encryptionPromises = patchedMessages.map(async ({ recipientJid: jid, message: patchedMessage }) => {
400
+ try {
401
+ if (!jid)
402
+ return null;
403
+ let msgToEncrypt = patchedMessage;
404
+ if (dsmMessage) {
405
+ const { user: targetUser } = jidDecode(jid);
406
+ const { user: ownPnUser } = jidDecode(meId);
407
+ const ownLidUser = meLidUser;
408
+ const isOwnUser = targetUser === ownPnUser || (ownLidUser && targetUser === ownLidUser);
409
+ const isExactSenderDevice = jid === meId || (meLid && jid === meLid);
410
+ if (isOwnUser && !isExactSenderDevice) {
411
+ msgToEncrypt = dsmMessage;
412
+ logger.debug({ jid, targetUser }, 'Using DSM for own device');
413
+ }
414
+ }
415
+ const bytes = encodeWAMessage(msgToEncrypt);
416
+ const mutexKey = jid;
417
+ const node = await encryptionMutex.mutex(mutexKey, async () => {
418
+ const { type, ciphertext } = await signalRepository.encryptMessage({ jid, data: bytes });
419
+ if (type === 'pkmsg') {
420
+ shouldIncludeDeviceIdentity = true;
421
+ }
422
+ return {
423
+ tag: 'to',
424
+ attrs: { jid },
425
+ content: [
426
+ {
427
+ tag: 'enc',
428
+ attrs: { v: '2', type, ...(extraAttrs || {}) },
429
+ content: ciphertext
430
+ }
431
+ ]
432
+ };
433
+ });
434
+ return node;
435
+ }
436
+ catch (err) {
437
+ logger.error({ jid, err }, 'Failed to encrypt for recipient');
438
+ return null;
439
+ }
440
+ });
441
+ const nodes = (await Promise.all(encryptionPromises)).filter(node => node !== null);
442
+ if (recipientJids.length > 0 && nodes.length === 0) {
443
+ throw new Boom('All encryptions failed', { statusCode: 500 });
444
+ }
445
+ return { nodes, shouldIncludeDeviceIdentity };
446
+ };
447
+ const relayMessage = async (
448
+ jid,
449
+ message,
450
+ {
451
+ messageId: msgId,
452
+ participant = false,
453
+ noSelfSync = false,
454
+ additionalAttributes,
455
+ additionalNodes,
456
+ useUserDevicesCache,
457
+ useCachedGroupMetadata,
458
+ statusJidList
459
+ }
460
+ ) => {
461
+ const meId = authState.creds.me.id
462
+ const meLid = authState.creds.me?.lid
463
+ const isRetryResend = Boolean(participant?.jid)
464
+ let shouldIncludeDeviceIdentity = isRetryResend
465
+ const statusJid = 'status@broadcast'
466
+ const { user, server } = jidDecode(jid)
467
+ const isGroup = server === 'g.us'
468
+ const isStatus = jid === statusJid
469
+ const isLid = server === 'lid'
470
+ const isNewsletter = server === 'newsletter'
471
+ const isInterop = isInteropUser(jid)
472
+ const isGroupOrStatus = isGroup || isStatus
473
+ const finalJid = jid
474
+ const iosBros = config.browser[0] === "iOS" || config.browser[1] === "Safari";
475
+ msgId = iosBros ? generateIOSMessageID() : msgId ?? generateMessageIDV2(meId)
476
+ useUserDevicesCache = useUserDevicesCache!== false
477
+ useCachedGroupMetadata = useCachedGroupMetadata!== false &&!isStatus
478
+ const participants = []
479
+ const destinationJid =!isStatus? finalJid : statusJid
480
+ const binaryNodeContent = []
481
+ const devices = []
482
+ let reportingMessage
483
+ const meMsg = {
484
+ deviceSentMessage: { destinationJid, message },
485
+ messageContextInfo: message.messageContextInfo
486
+ }
487
+ const extraAttrs = {}
488
+ const regexGroupOld = /^(\d{1,15})-(\d+)@g\.us$/
489
+ const messages = normalizeMessageContent(message)
490
+ const buttonType = getButtonType(messages)
491
+ const pollMessage =
492
+ messages.pollCreationMessage || messages.pollCreationMessageV2 || messages.pollCreationMessageV3
493
+ await authState.keys.transaction(async () => {
494
+ const mediaType = getMediaType(message)
495
+ if (mediaType) extraAttrs.mediatype = mediaType
496
+ if (isNewsletter) {
497
+ const patched = patchMessageBeforeSending? await patchMessageBeforeSending(message, []) : message
498
+ const bytes = encodeNewsletterMessage(patched)
499
+ binaryNodeContent.push({ tag: 'plaintext', attrs: {}, content: bytes })
500
+ const stanza = {
501
+ tag: 'message',
502
+ attrs: {
503
+ to: jid,
504
+ id: msgId,
505
+ type: getMessageType(message),
506
+ ...(additionalAttributes || {})
507
+ },
508
+ content: binaryNodeContent
509
+ }
510
+ logger.debug({ msgId }, `sending newsletter message to ${jid}`)
511
+ await sendNode(stanza)
512
+ return
513
+ }
514
+ if (normalizeMessageContent(message)?.pinInChatMessage || normalizeMessageContent(message)?.reactionMessage) {
515
+ extraAttrs['decrypt-fail'] = 'hide'
516
+ }
517
+ if (isGroupOrStatus &&!isRetryResend) {
518
+ const [groupData, senderKeyMap] = await Promise.all([
519
+ (async () => {
520
+ let groupData = useCachedGroupMetadata && cachedGroupMetadata? await cachedGroupMetadata(jid) : undefined
521
+ if (groupData && Array.isArray(groupData?.participants)) {
522
+ logger.trace({ jid, participants: groupData.participants.length }, 'using cached group metadata')
523
+ } else if (!isStatus) {
524
+ groupData = await groupMetadata(jid)
525
+ }
526
+ return groupData
527
+ })(),
528
+ (async () => {
529
+ if (!participant &&!isStatus) {
530
+ const result = await authState.keys.get('sender-key-memory', [jid])
531
+ return result[jid] || {}
532
+ }
533
+ return {}
534
+ })()
535
+ ])
536
+ const participantsList = groupData? groupData.participants.map(p => p.id) : []
537
+ if (groupData?.ephemeralDuration && groupData.ephemeralDuration > 0) {
538
+ additionalAttributes = {...additionalAttributes, expiration: groupData.ephemeralDuration.toString() }
539
+ }
540
+ if (isStatus && statusJidList) participantsList.push(...statusJidList)
541
+ const additionalDevices = await getUSyncDevices(participantsList,!!useUserDevicesCache, false)
542
+ devices.push(...additionalDevices)
543
+ if (isGroup) {
544
+ additionalAttributes = {
545
+ ...additionalAttributes,
546
+ addressing_mode: groupData?.addressingMode || 'lid'
547
+ }
548
+ }
549
+ if (message?.groupStatusMessageV2 &&!message?.messageContextInfo?.messageSecret) {
550
+ message = {
551
+ ...message,
552
+ messageContextInfo: {
553
+ ...(message.messageContextInfo || {}),
554
+ messageSecret: randomBytes(32)
555
+ },
556
+ groupStatusMessageV2: {
557
+ ...message.groupStatusMessageV2,
558
+ message: {
559
+ ...(message.groupStatusMessageV2.message || {}),
560
+ messageContextInfo: {
561
+ ...(message.groupStatusMessageV2.message?.messageContextInfo || {}),
562
+ messageSecret: message.messageContextInfo?.messageSecret || randomBytes(32)
563
+ }
564
+ }
565
+ }
566
+ }
567
+ }
568
+ // list/buttons/template -> interactiveMessage
569
+ if (message.listMessage) {
570
+ const list = message.listMessage
571
+ message = {
572
+ interactiveMessage: {
573
+ nativeFlowMessage: {
574
+ buttons: [
575
+ {
576
+ name: 'single_select',
577
+ buttonParamsJson: JSON.stringify({
578
+ title: list.buttonText || 'Select',
579
+ sections: (list.sections || []).map(section => ({
580
+ title: section.title || '',
581
+ highlight_label: '',
582
+ rows: (section.rows || []).map(row => ({
583
+ header: '',
584
+ title: row.title || '',
585
+ description: row.description || '',
586
+ id: row.rowId || row.id || ''
587
+ }))
588
+ }))
589
+ })
590
+ }
591
+ ],
592
+ messageParamsJson: '',
593
+ messageVersion: 1
594
+ },
595
+ body: { text: list.description || '' },
596
+ footer: list.footerText? { text: list.footerText } : undefined,
597
+ header: list.title? { title: list.title, hasMediaAttachment: false, subtitle: '' } : undefined,
598
+ contextInfo: list.contextInfo
599
+ }
600
+ }
601
+ } else if (message.buttonsMessage) {
602
+ const bMsg = message.buttonsMessage
603
+ const buttons = (bMsg.buttons || []).map(btn => ({
604
+ name: 'quick_reply',
605
+ buttonParamsJson: JSON.stringify({
606
+ display_text: btn.buttonText?.displayText || btn.buttonText || '',
607
+ id: btn.buttonId || btn.buttonText?.displayText || ''
608
+ })
609
+ }))
610
+ message = {
611
+ interactiveMessage: {
612
+ nativeFlowMessage: { buttons, messageParamsJson: '', messageVersion: 1 },
613
+ body: { text: bMsg.contentText || bMsg.text || '' },
614
+ footer: bMsg.footerText? { text: bMsg.footerText } : undefined,
615
+ header: bMsg.text
616
+ ? { title: bMsg.text, hasMediaAttachment: false, subtitle: '' }
617
+ : bMsg.imageMessage || bMsg.videoMessage || bMsg.documentMessage
618
+ ? { hasMediaAttachment: true,...(bMsg.imageMessage? { imageMessage: bMsg.imageMessage } : {}),...(bMsg.videoMessage? { videoMessage: bMsg.videoMessage } : {}) }
619
+ : undefined,
620
+ contextInfo: bMsg.contextInfo
621
+ }
622
+ }
623
+ } else if (message.templateMessage) {
624
+ const tmpl = message.templateMessage.hydratedTemplate || message.templateMessage.fourRowTemplate
625
+ if (tmpl) {
626
+ const buttons = (tmpl.hydratedButtons || [])
627
+ .map(hBtn => {
628
+ if (hBtn.quickReplyButton) {
629
+ return { name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: hBtn.quickReplyButton.displayText || '', id: hBtn.quickReplyButton.id || hBtn.quickReplyButton.displayText || '' }) }
630
+ } else if (hBtn.urlButton) {
631
+ return { name: 'cta_url', buttonParamsJson: JSON.stringify({ display_text: hBtn.urlButton.displayText || '', url: hBtn.urlButton.url || '', merchant_url: hBtn.urlButton.url || '' }) }
632
+ } else if (hBtn.callButton) {
633
+ return { name: 'cta_call', buttonParamsJson: JSON.stringify({ display_text: hBtn.callButton.displayText || '', phone_number: hBtn.callButton.phoneNumber || '' }) }
634
+ }
635
+ return null
636
+ })
637
+ .filter(Boolean)
638
+ message = {
639
+ interactiveMessage: {
640
+ nativeFlowMessage: { buttons, messageParamsJson: '', messageVersion: 1 },
641
+ body: { text: tmpl.hydratedContentText || tmpl.contentText || '' },
642
+ footer: tmpl.hydratedFooterText? { text: tmpl.hydratedFooterText } : undefined,
643
+ header: tmpl.hydratedTitleText
644
+ ? { title: tmpl.hydratedTitleText, hasMediaAttachment: false, subtitle: '' }
645
+ : tmpl.imageMessage || tmpl.videoMessage || tmpl.documentMessage
646
+ ? { hasMediaAttachment: true,...(tmpl.imageMessage? { imageMessage: tmpl.imageMessage } : {}),...(tmpl.videoMessage? { videoMessage: tmpl.videoMessage } : {}) }
647
+ : undefined,
648
+ contextInfo: tmpl.contextInfo
649
+ }
650
+ }
651
+ }
652
+ }
653
+
654
+ const patched = await patchMessageBeforeSending(message)
655
+ if (Array.isArray(patched)) throw new Boom('Per-jid patching is not supported in groups')
656
+ const bytes = encodeWAMessage(patched)
657
+ reportingMessage = patched
658
+ const groupAddressingMode = additionalAttributes?.['addressing_mode'] || groupData?.addressingMode || 'lid'
659
+ const groupSenderIdentity = groupAddressingMode === 'lid' && meLid? meLid : meId
660
+ const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage({
661
+ group: destinationJid,
662
+ data: bytes,
663
+ meId: groupSenderIdentity
664
+ })
665
+ const senderKeyRecipients = []
666
+ for (const device of devices) {
667
+ const deviceJid = device.jid
668
+ const hasKey =!!senderKeyMap[deviceJid]
669
+ if (!hasKey ||!!participant &&!isHostedLidUser(deviceJid) &&!isHostedPnUser(deviceJid) && device.device!== 99) {
670
+ senderKeyRecipients.push(deviceJid)
671
+ senderKeyMap[deviceJid] = true
672
+ }
673
+ }
674
+ if (senderKeyRecipients.length) {
675
+ logger.debug({ senderKeyJids: senderKeyRecipients }, 'sending new sender key')
676
+ const senderKeyMsg = {
677
+ senderKeyDistributionMessage: {
678
+ axolotlSenderKeyDistributionMessage: senderKeyDistributionMessage,
679
+ groupId: destinationJid
680
+ }
681
+ }
682
+ await assertSessions(senderKeyRecipients)
683
+ const result = await createParticipantNodes(senderKeyRecipients, senderKeyMsg, extraAttrs)
684
+ shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || result.shouldIncludeDeviceIdentity
685
+ participants.push(...result.nodes)
686
+ }
687
+ binaryNodeContent.push({ tag: 'enc', attrs: { v: '2', type: 'skmsg',...extraAttrs }, content: ciphertext })
688
+ await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } })
689
+ } else {
690
+ let ownId = meId
691
+ if (isLid && meLid) {
692
+ ownId = meLid
693
+ logger.debug({ to: jid, ownId }, 'Using LID identity for @lid conversation')
694
+ } else {
695
+ logger.debug({ to: jid, ownId }, 'Using PN identity for @s.whatsapp.net conversation')
696
+ }
697
+ const { user: ownUser } = jidDecode(ownId)
698
+ if (!participant) {
699
+ const patchedForReporting = await patchMessageBeforeSending(message, [jid])
700
+ reportingMessage = Array.isArray(patchedForReporting)
701
+ ? patchedForReporting.find(item => item.recipientJid === jid) || patchedForReporting[0]
702
+ : patchedForReporting
703
+ }
704
+ if (!isRetryResend) {
705
+ const targetUserServer = isLid? 'lid' : isInterop? 'interop' : 's.whatsapp.net'
706
+ devices.push({ user, device: 0, jid: jidEncode(user, targetUserServer, 0) })
707
+ if (user!== ownUser &&!isInterop) {
708
+ const ownUserServer = isLid? 'lid' : 's.whatsapp.net'
709
+ const ownUserForAddressing = isLid && meLid? jidDecode(meLid).user : jidDecode(meId).user
710
+ devices.push({ user: ownUserForAddressing, device: 0, jid: jidEncode(ownUserForAddressing, ownUserServer, 0) })
711
+ }
712
+ if (additionalAttributes?.['category']!== 'peer' &&!isInterop) {
713
+ devices.length = 0
714
+ const senderIdentity = isLid && meLid
715
+ ? jidEncode(jidDecode(meLid)?.user, 'lid', undefined)
716
+ : jidEncode(jidDecode(meId)?.user, 's.whatsapp.net', undefined)
717
+ const sessionDevices = await getUSyncDevices([senderIdentity, jid], true, false)
718
+ devices.push(...sessionDevices)
719
+ logger.debug({ deviceCount: devices.length, devices: devices.map(d => `${d.user}:${d.device}@${jidDecode(d.jid)?.server}`) }, 'Device enumeration complete with unified addressing')
720
+ }
721
+ }
722
+ const allRecipients = []
723
+ const meRecipients = []
724
+ const otherRecipients = []
725
+ const { user: mePnUser } = jidDecode(meId)
726
+ const { user: meLidUser } = meLid? jidDecode(meLid) : { user: null }
727
+ for (const { user, jid } of devices) {
728
+ /** noSelfSync: opsi untuk skip sync pesan ke device lain milik akun sendiri (private chat) */
729
+ const isExactSenderDevice = jid === meId || (meLid && jid === meLid)
730
+ if (isExactSenderDevice) {
731
+ logger.debug({ jid, meId, meLid }, 'Skipping exact sender device (whatsmeow pattern)')
732
+ continue
733
+ }
734
+ const isMe = user === mePnUser || user === meLidUser
735
+ let ptcp = false
736
+ if (noSelfSync) {
737
+ if (!isJidGroup(jid) && !isStatus) {
738
+ if (!(!isMe)) ptcp = true
739
+ } else {
740
+ ptcp = false
741
+ }
742
+ }
743
+ if (!ptcp) {
744
+ if (isMe) {
745
+ meRecipients.push(jid)
746
+ } else {
747
+ otherRecipients.push(jid)
748
+ }
749
+ allRecipients.push(jid)
750
+ }
751
+ }
752
+ // Fix: detect silent delivery failure — if the destination is genuinely
753
+ // someone else (not our own number) and device resolution produced zero
754
+ // recipient devices for them, don't proceed as if the send succeeded.
755
+ const isSelfChatDestination = user === mePnUser || user === meLidUser
756
+ if (!isRetryResend && !isSelfChatDestination && otherRecipients.length === 0) {
757
+ logger.warn({ jid, deviceCount: devices.length, noSelfSync }, 'relayMessage: no recipient devices resolved for the other party; aborting to avoid a silent no-op send')
758
+ throw new Boom('No devices resolved for recipient — message was not delivered to the other party', { statusCode: 421, data: { jid } })
759
+ }
760
+ await assertSessions(allRecipients)
761
+ const [
762
+ { nodes: meNodes, shouldIncludeDeviceIdentity: s1 },
763
+ { nodes: otherNodes, shouldIncludeDeviceIdentity: s2 }
764
+ ] = await Promise.all([
765
+ createParticipantNodes(meRecipients, meMsg || message, extraAttrs),
766
+ createParticipantNodes(otherRecipients, message, extraAttrs, meMsg)
767
+ ])
768
+ participants.push(...meNodes,...otherNodes)
769
+ if (meRecipients.length > 0 || otherRecipients.length > 0) {
770
+ extraAttrs.phash = generateParticipantHashV2([...meRecipients,...otherRecipients])
771
+ }
772
+ shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || s1 || s2
773
+ }
774
+ if (isRetryResend) {
775
+ const isParticipantLid = jidDecode(participant.jid).server === 'lid'
776
+ const isMe = areJidsSameUser(participant.jid, isParticipantLid? meLid : meId)
777
+ const encodedMessageToSend = isMe
778
+ ? encodeWAMessage({ deviceSentMessage: { destinationJid, message } })
779
+ : encodeWAMessage(message)
780
+ const { type, ciphertext: encryptedContent } = await signalRepository.encryptMessage({
781
+ data: encodedMessageToSend,
782
+ jid: participant.jid
783
+ })
784
+ binaryNodeContent.push({
785
+ tag: 'enc',
786
+ attrs: { v: '2', type, count: (participant.count?? 0).toString() },
787
+ content: encryptedContent
788
+ })
789
+ }
790
+ if (participants.length) {
791
+ if (additionalAttributes?.['category'] === 'peer') {
792
+ const peerNode = participants[0]?.content?.[0]
793
+ if (peerNode) binaryNodeContent.push(peerNode)
794
+ } else if (isInterop) {
795
+ const recipientNode = participants.find(p => isInteropUser(p?.attrs?.jid))
796
+ const encNode = (recipientNode?? participants[0])?.content?.[0]
797
+ if (encNode) binaryNodeContent.push(encNode)
798
+ } else {
799
+ binaryNodeContent.push({ tag: 'participants', attrs: {}, content: participants })
800
+ }
801
+ }
802
+ const stanza = {
803
+ tag: 'message',
804
+ attrs: { id: msgId, to: destinationJid, type: getMessageType(message),...(additionalAttributes || {}) },
805
+ content: binaryNodeContent
806
+ }
807
+ if (shouldIncludeDeviceIdentity) {
808
+ stanza.content.push({ tag: 'device-identity', attrs: {}, content: encodeSignedDeviceIdentity(authState.creds.account, true) })
809
+ logger.debug({ jid }, 'adding device identity')
810
+ }
811
+
812
+ if (isGroup && regexGroupOld.test(jid) &&!message.reactionMessage) {
813
+ stanza.content.push({ tag: 'multicast', attrs: {} })
814
+ }
815
+ if (pollMessage || messages.eventMessage) {
816
+ stanza.content.push({
817
+ tag: 'meta',
818
+ attrs: messages.eventMessage
819
+ ? { event_type: 'creation' }
820
+ : isNewsletter
821
+ ? { polltype: 'creation', contenttype: pollMessage?.pollContentType === 2? 'image' : 'text' }
822
+ : { polltype: 'creation' }
823
+ })
824
+ }
825
+ if (!isNewsletter &&!isRetryResend && reportingMessage?.messageContextInfo?.messageSecret && shouldIncludeReportingToken(reportingMessage)) {
826
+ try {
827
+ const encoded = encodeWAMessage(reportingMessage)
828
+ const reportingKey = { id: msgId, fromMe: true, remoteJid: destinationJid, participant: participant?.jid }
829
+ const reportingNode = await getMessageReportingToken(encoded, reportingMessage, reportingKey)
830
+ if (reportingNode) {
831
+ stanza.content.push(reportingNode)
832
+ logger.trace({ jid }, 'added reporting token to message')
833
+ }
834
+ } catch (error) {
835
+ logger.warn({ jid, trace: error?.stack }, 'failed to attach reporting token')
836
+ }
837
+ }
838
+ let didPushAdditional = false
839
+ if (!isNewsletter && buttonType) {
840
+ const buttonsNode = getButtonArgs(messages)
841
+ const filteredButtons = getBinaryNodeFilter(additionalNodes? additionalNodes : [])
842
+ if (filteredButtons) {
843
+ stanza.content.push(...additionalNodes)
844
+ didPushAdditional = true
845
+ } else {
846
+ stanza.content.push(buttonsNode)
847
+ }
848
+ }
849
+ if (!aiLabel && isPnUser(destinationJid)) {
850
+ const alreadyHasBizBot = getBinaryFilteredBizBot(additionalNodes || []) || getBinaryFilteredBizBot(stanza.content)
851
+ if (!alreadyHasBizBot) stanza.content.push({ tag: 'bot', attrs: { biz_bot: '1' } })
852
+ } else if (aiLabel &&!isGroup &&!isStatus &&!isNewsletter) {
853
+ const existingBizBot = getBinaryFilteredBizBot(additionalNodes || [])
854
+ if (!existingBizBot) stanza.content.push({ tag: 'bot', attrs: { biz_bot: '1' } })
855
+ }
856
+ const isPeerMessage = additionalAttributes?.['category'] === 'peer'
857
+ const is1on1Send =!isGroup &&!isRetryResend &&!isStatus &&!isNewsletter &&!isPeerMessage
858
+ const tcTokenJid = is1on1Send? await resolveTcTokenJid(destinationJid, getLIDForPN) : destinationJid
859
+ const contactTcTokenData = is1on1Send? await authState.keys.get('tctoken', [tcTokenJid]) : {}
860
+ const existingTokenEntry = contactTcTokenData[tcTokenJid]
861
+ let tcTokenBuffer = existingTokenEntry?.token
862
+ if (tcTokenBuffer?.length && isTcTokenExpired(existingTokenEntry?.timestamp)) {
863
+ logger.debug({ jid: destinationJid, timestamp: existingTokenEntry?.timestamp }, 'tctoken expired, clearing')
864
+ tcTokenBuffer = undefined
865
+ const cleared = existingTokenEntry?.senderTimestamp!== undefined? { token: Buffer.alloc(0), senderTimestamp: existingTokenEntry.senderTimestamp } : null
866
+ try {
867
+ await authState.keys.set({ tctoken: { [tcTokenJid]: cleared } })
868
+ } catch (err) {
869
+ logger.debug({ jid: destinationJid, err: err?.message }, 'failed to persist tctoken expiry cleanup')
870
+ }
871
+ }
872
+ if (tcTokenBuffer?.length && sock.serverProps.privacyTokenOn1to1) {
873
+ stanza.content.push({ tag: 'tctoken', attrs: {}, content: tcTokenBuffer })
874
+ }
875
+ if (additionalNodes && additionalNodes.length > 0 &&!didPushAdditional) {
876
+ stanza.content.push(...additionalNodes)
877
+ }
878
+ logger.debug({ msgId }, `sending message to ${participants.length} devices`)
879
+ await sendNode(stanza)
880
+ if (message.messageContextInfo?.messageSecret) {
881
+ setBotMessageSecret(msgId, message.messageContextInfo.messageSecret, destinationJid)
882
+ }
883
+ const isProtocolMsg =!!normalizeMessageContent(message)?.protocolMessage
884
+ const isBotOrPSA = destinationJid === PSA_WID || isJidBot(destinationJid) || isJidMetaAI(destinationJid)
885
+ if (is1on1Send &&!isProtocolMsg &&!isBotOrPSA && shouldSendNewTcToken(existingTokenEntry?.senderTimestamp) &&!inFlightTcTokenIssuance.has(tcTokenJid)) {
886
+ inFlightTcTokenIssuance.add(tcTokenJid)
887
+ const issueTimestamp = unixTimestampSeconds()
888
+ const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping)
889
+ resolveIssuanceJid(destinationJid, sock.serverProps.lidTrustedTokenIssueToLid, getLIDForPN, getPNForLID)
890
+ .then(issueJid => issuePrivacyTokens([issueJid], issueTimestamp))
891
+ .then(async result => {
892
+ await storeTcTokensFromIqResult({ result, fallbackJid: tcTokenJid, keys: authState.keys, getLIDForPN })
893
+ const currentData = await authState.keys.get('tctoken', [tcTokenJid])
894
+ const currentEntry = currentData[tcTokenJid]
895
+ const indexWrite = await buildMergedTcTokenIndexWrite(authState.keys, [tcTokenJid])
896
+ await authState.keys.set({
897
+ tctoken: {
898
+ [tcTokenJid]: { token: Buffer.alloc(0),...currentEntry, senderTimestamp: issueTimestamp },
899
+ ...indexWrite
900
+ }
901
+ })
902
+ })
903
+ .catch(err => logger.debug({ jid: destinationJid, err: err?.message }, 'fire-and-forget tctoken issuance failed'))
904
+ .finally(() => inFlightTcTokenIssuance.delete(tcTokenJid))
905
+ }
906
+ if (messageRetryManager &&!participant) {
907
+ messageRetryManager.addRecentMessage(destinationJid, msgId, message)
908
+ }
909
+ if (isInterop &&!isRetryResend) {
910
+ await trustInteropContact(destinationJid).catch(err => logger.debug({ err, jid: destinationJid }, 'failed to trust interop contact'))
911
+ }
912
+ }, meId)
913
+ return msgId
914
+ }
915
+ const getMessageType = (message) => {
916
+ const normalizedMessage = normalizeMessageContent(message);
917
+ if (!normalizedMessage)
918
+ return 'text';
919
+ if (normalizedMessage.reactionMessage || normalizedMessage.encReactionMessage) {
920
+ return 'reaction';
921
+ }
922
+ if (normalizedMessage.pollCreationMessage ||
923
+ normalizedMessage.pollCreationMessageV2 ||
924
+ normalizedMessage.pollCreationMessageV3 ||
925
+ normalizedMessage.pollCreationMessageV4 ||
926
+ normalizedMessage.pollCreationMessageV5 ||
927
+ normalizedMessage.pollUpdateMessage) {
928
+ return 'poll';
929
+ }
930
+ if (normalizedMessage.eventMessage) {
931
+ return 'event';
932
+ }
933
+ if (getMediaType(normalizedMessage) !== '') {
934
+ return 'media';
935
+ }
936
+ return 'text';
937
+ };
938
+ const getMediaType = (message) => {
939
+ if (message.imageMessage) {
940
+ return 'image';
941
+ }
942
+ else if (message.videoMessage) {
943
+ return message.videoMessage.gifPlayback ? 'gif' : 'video';
944
+ }
945
+ else if (message.audioMessage) {
946
+ return message.audioMessage.ptt ? 'ptt' : 'audio';
947
+ }
948
+ else if (message.contactMessage) {
949
+ return 'vcard';
950
+ }
951
+ else if (message.documentMessage) {
952
+ return 'document';
953
+ }
954
+ else if (message.contactsArrayMessage) {
955
+ return 'contact_array';
956
+ }
957
+ else if (message.liveLocationMessage) {
958
+ return 'livelocation';
959
+ }
960
+ else if (message.stickerMessage) {
961
+ return 'sticker';
962
+ }
963
+ else if (message.listMessage) {
964
+ return 'list';
965
+ }
966
+ else if (message.listResponseMessage) {
967
+ return 'list_response';
968
+ }
969
+ else if (message.buttonsResponseMessage) {
970
+ return 'buttons_response';
971
+ }
972
+ else if (message.orderMessage) {
973
+ return 'order';
974
+ }
975
+ else if (message.productMessage) {
976
+ return 'product';
977
+ }
978
+ else if (message.interactiveResponseMessage) {
979
+ return 'native_flow_response';
980
+ }
981
+ else if (message.groupInviteMessage) {
982
+ return 'url';
983
+ }
984
+ return '';
985
+ };
986
+ const getButtonType = (message) => {
987
+ if (message.listMessage) {
988
+ return 'list'
989
+ }
990
+ else if (message.buttonsMessage) {
991
+ return 'buttons'
992
+ }
993
+ else if (message.interactiveMessage?.nativeFlowMessage?.buttons?.[0]?.name === 'review_and_pay') {
994
+ return 'review_and_pay'
995
+ }
996
+ else if (message.interactiveMessage?.nativeFlowMessage?.buttons?.[0]?.name === 'review_order') {
997
+ return 'review_order'
998
+ }
999
+ else if (message.interactiveMessage?.nativeFlowMessage?.buttons?.[0]?.name === 'payment_info') {
1000
+ return 'payment_info'
1001
+ } else if (message.interactiveMessage?.nativeFlowMessage?.buttons?.[0]?.name === 'payment_key_info') {
1002
+ return 'payment_key_info'
1003
+ } else if (message.interactiveMessage?.nativeFlowMessage?.buttons?.[0]?.name === 'payment_status') {
1004
+ return 'payment_status'
1005
+ }
1006
+ else if (message.interactiveMessage?.nativeFlowMessage?.buttons?.[0]?.name === 'payment_method') {
1007
+ return 'payment_method'
1008
+ }
1009
+ else if (message.interactiveMessage?.nativeFlowMessage?.buttons?.[0]?.name === 'catalog_message') {
1010
+ return 'catalog_message'
1011
+ }
1012
+ else if (message.interactiveMessage && message.interactiveMessage?.nativeFlowMessage) {
1013
+ return 'interactive'
1014
+ }
1015
+ else if (message.interactiveMessage?.nativeFlowMessage) {
1016
+ return 'native_flow'
1017
+ }
1018
+ };
1019
+ const getButtonArgs = (message) => {
1020
+ const nativeFlow = message.interactiveMessage?.nativeFlowMessage
1021
+ const firstButtonName = nativeFlow?.buttons?.[0]?.name
1022
+ const nativeFlowSpecials = [
1023
+ 'mpm',
1024
+ 'cta_catalog',
1025
+ 'send_location',
1026
+ 'call_permission_request',
1027
+ 'wa_payment_transaction_details',
1028
+ 'automated_greeting_message_view_catalog'
1029
+ ]
1030
+
1031
+ if (nativeFlow && (firstButtonName === 'review_and_pay' || firstButtonName === 'payment_info')) {
1032
+ return {
1033
+ tag: 'biz',
1034
+ attrs: {
1035
+ native_flow_name: firstButtonName === 'review_and_pay' ? 'order_details' : firstButtonName
1036
+ }
1037
+ }
1038
+ } else if (nativeFlow && nativeFlowSpecials.includes(firstButtonName)) {
1039
+ // Only works for WhatsApp Original, not WhatsApp Business
1040
+ return {
1041
+ tag: 'biz',
1042
+ attrs: {
1043
+ actual_actors: '2',
1044
+ host_storage: '2',
1045
+ privacy_mode_ts: Utils_1.unixTimestampSeconds().toString()
1046
+ },
1047
+ content: [
1048
+ {
1049
+ tag: 'interactive',
1050
+ attrs: {
1051
+ type: 'native_flow',
1052
+ v: '1'
1053
+ },
1054
+ content: [
1055
+ {
1056
+ tag: 'native_flow',
1057
+ attrs: {
1058
+ v: '2',
1059
+ name: firstButtonName
1060
+ }
1061
+ }
1062
+ ]
1063
+ },
1064
+ {
1065
+ tag: 'quality_control',
1066
+ attrs: {
1067
+ source_type: 'third_party'
1068
+ }
1069
+ }
1070
+ ]
1071
+ }
1072
+ } else if (nativeFlow || message.buttonsMessage) {
1073
+ // It works for whatsapp original and whatsapp business
1074
+ return {
1075
+ tag: 'biz',
1076
+ attrs: {
1077
+ actual_actors: '2',
1078
+ host_storage: '2',
1079
+ privacy_mode_ts: Utils_1.unixTimestampSeconds().toString()
1080
+ },
1081
+ content: [
1082
+ {
1083
+ tag: 'interactive',
1084
+ attrs: {
1085
+ type: 'native_flow',
1086
+ v: '1'
1087
+ },
1088
+ content: [
1089
+ {
1090
+ tag: 'native_flow',
1091
+ attrs: {
1092
+ v: '9',
1093
+ name: 'mixed'
1094
+ }
1095
+ }
1096
+ ]
1097
+ },
1098
+ {
1099
+ tag: 'quality_control',
1100
+ attrs: {
1101
+ source_type: 'third_party'
1102
+ }
1103
+ }
1104
+ ]
1105
+ }
1106
+ } else if (message.listMessage) {
1107
+ return {
1108
+ tag: 'biz',
1109
+ attrs: {
1110
+ actual_actors: '2',
1111
+ host_storage: '2',
1112
+ privacy_mode_ts: Utils_1.unixTimestampSeconds().toString()
1113
+ },
1114
+ content: [
1115
+ {
1116
+ tag: 'list',
1117
+ attrs: {
1118
+ v: '2',
1119
+ type: 'product_list'
1120
+ }
1121
+ },
1122
+ {
1123
+ tag: 'quality_control',
1124
+ attrs: {
1125
+ source_type: 'third_party'
1126
+ }
1127
+ }
1128
+ ]
1129
+ }
1130
+ } else {
1131
+ return {
1132
+ tag: 'biz',
1133
+ attrs: {
1134
+ actual_actors: '2',
1135
+ host_storage: '2',
1136
+ privacy_mode_ts: Utils_1.unixTimestampSeconds().toString()
1137
+ }
1138
+ }
1139
+ }
1140
+ }
1141
+ const issuePrivacyTokens = async (jids, timestamp) => {
1142
+ const t = (timestamp ?? unixTimestampSeconds()).toString();
1143
+ const result = await query({
1144
+ tag: 'iq',
1145
+ attrs: {
1146
+ to: S_WHATSAPP_NET,
1147
+ type: 'set',
1148
+ xmlns: 'privacy'
1149
+ },
1150
+ content: [
1151
+ {
1152
+ tag: 'tokens',
1153
+ attrs: {},
1154
+ content: jids.map(jid => ({
1155
+ tag: 'token',
1156
+ attrs: {
1157
+ jid: jidNormalizedUser(jid),
1158
+ t,
1159
+ type: 'trusted_contact'
1160
+ }
1161
+ }))
1162
+ }
1163
+ ]
1164
+ });
1165
+ return result;
1166
+ };
1167
+ const waUploadToServer = getWAUploadToServer(config, refreshMediaConn);
1168
+ const waitForMsgMediaUpdate = bindWaitForEvent(ev, 'messages.media-update');
1169
+ registerSocketEndHandler(() => {
1170
+ if (!config.userDevicesCache && userDevicesCache.close) {
1171
+ userDevicesCache.close();
1172
+ }
1173
+ mediaConn = undefined;
1174
+ if (messageRetryManager) {
1175
+ messageRetryManager.clear();
1176
+ }
1177
+ });
1178
+ return {
1179
+ ...sock,
1180
+ userDevicesCache,
1181
+ devicesMutex,
1182
+ issuePrivacyTokens,
1183
+ assertSessions,
1184
+ relayMessage,
1185
+ sendReceipt,
1186
+ sendReceipts,
1187
+ readMessages,
1188
+ refreshMediaConn,
1189
+ // Function (not getter) so the spread in chats.ts preserves the live closure binding.
1190
+ getMediaHost: () => mediaHost,
1191
+ waUploadToServer,
1192
+ fetchPrivacySettings,
1193
+ sendPeerDataOperationMessage,
1194
+ createParticipantNodes,
1195
+ getUSyncDevices,
1196
+ messageRetryManager,
1197
+ updateMemberLabel,
1198
+ updateMediaMessage: async (message) => {
1199
+ const content = assertMediaContent(message.message);
1200
+ const mediaKey = content.mediaKey;
1201
+ const meId = authState.creds.me.id;
1202
+ const node = encryptMediaRetryRequest(message.key, mediaKey, meId);
1203
+ let error = undefined;
1204
+ await Promise.all([
1205
+ sendNode(node),
1206
+ waitForMsgMediaUpdate(async (update) => {
1207
+ const result = update.find(c => c.key.id === message.key.id);
1208
+ if (result) {
1209
+ if (result.error) {
1210
+ error = result.error;
1211
+ }
1212
+ else {
1213
+ try {
1214
+ const media = decryptMediaRetryData(result.media, mediaKey, result.key.id);
1215
+ if (media.result !== proto.MediaRetryNotification.ResultType.SUCCESS) {
1216
+ const resultStr = proto.MediaRetryNotification.ResultType[media.result];
1217
+ throw new Boom(`Media re-upload failed by device (${resultStr})`, {
1218
+ data: media,
1219
+ statusCode: getStatusCodeForMediaRetry(media.result) || 404
1220
+ });
1221
+ }
1222
+ content.directPath = media.directPath;
1223
+ content.url = getUrlFromDirectPath(content.directPath, mediaHost);
1224
+ logger.debug({ directPath: media.directPath, key: result.key }, 'media update successful');
1225
+ }
1226
+ catch (err) {
1227
+ error = err;
1228
+ }
1229
+ }
1230
+ return true;
1231
+ }
1232
+ })
1233
+ ]);
1234
+ if (error) {
1235
+ throw error;
1236
+ }
1237
+ ev.emit('messages.update', [{ key: message.key, update: { message: message.message } }]);
1238
+ return message;
1239
+ },
1240
+ sendTable: async (jid, title, headers, rows, quoted, options = {}) => {
1241
+ const { message, messageId } = Utils_1.generateTableContent(title, headers, rows, quoted, options)
1242
+ await relayMessage(jid, message, { messageId, noSelfSync: options.noSelfSync })
1243
+ return { message, messageId }
1244
+ },
1245
+ sendList: async (jid, title, items, quoted, options = {}) => {
1246
+ const { message, messageId } = Utils_1.generateListContent(title, items, quoted, options)
1247
+ await relayMessage(jid, message, { messageId, noSelfSync: options.noSelfSync })
1248
+ return { message, messageId }
1249
+ },
1250
+ sendCodeBlock: async (jid, code, quoted, options = {}) => {
1251
+ const { message, messageId } = Utils_1.generateCodeBlockContent(code, quoted, options)
1252
+ await relayMessage(jid, message, { messageId, noSelfSync: options.noSelfSync })
1253
+ return { message, messageId }
1254
+ },
1255
+ sendLatex: async (jid, quoted, options) => {
1256
+ const { message, messageId } = Utils_1.generateLatexContent(quoted, options)
1257
+ await relayMessage(jid, message, { messageId, noSelfSync: options.noSelfSync })
1258
+ return { message, messageId }
1259
+ },
1260
+ sendLatexImage: async (jid, quoted, options, renderLatexToPng, uploadFn) => {
1261
+ const { message, messageId } = await Utils_1.generateLatexImageContent(
1262
+ quoted,
1263
+ options,
1264
+ uploadFn,
1265
+ renderLatexToPng
1266
+ )
1267
+ await relayMessage(jid, message, { messageId, noSelfSync: options.noSelfSync })
1268
+ return { message, messageId }
1269
+ },
1270
+ sendLatexInlineImage: async (jid, quoted, options, renderLatexToPng, uploadFn) => {
1271
+ const { message, messageId } = await Utils_1.generateLatexInlineImageContent(
1272
+ quoted,
1273
+ options,
1274
+ uploadFn,
1275
+ renderLatexToPng
1276
+ )
1277
+ await relayMessage(jid, message, { messageId, noSelfSync: options.noSelfSync })
1278
+ return { message, messageId }
1279
+ },
1280
+ captureUnifiedResponse: Utils_1.captureUnifiedResponse,
1281
+ sendUnifiedResponse: async (jid, quoted, captured) => {
1282
+ const { message, messageId } = Utils_1.generateUnifiedResponseContent(quoted, captured)
1283
+ await relayMessage(jid, message, { messageId, noSelfSync: options.noSelfSync })
1284
+ return { message, messageId }
1285
+ },
1286
+ sendRichMessage: async (jid, submessages, quoted, options = {}) => {
1287
+ const { message, messageId } = Utils_1.generateRichMessageContent(submessages, quoted, options)
1288
+ await relayMessage(jid, message, { messageId, noSelfSync: options.noSelfSync })
1289
+ return { message, messageId }
1290
+ },
1291
+ sendMessage: async (jid, content, options = {}) => {
1292
+ const userJid = authState.creds.me.id;
1293
+ const luki = new imup(Utils_1, waUploadToServer, relayMessage)
1294
+ const { quoted, participant = false } = options;
1295
+ const messageType = luki.detectType(content);
1296
+ if (typeof content === 'object' &&
1297
+ 'disappearingMessagesInChat' in content &&
1298
+ typeof content['disappearingMessagesInChat'] !== 'undefined' &&
1299
+ isJidGroup(jid)) {
1300
+ const { disappearingMessagesInChat } = content;
1301
+ const value = typeof disappearingMessagesInChat === 'boolean'
1302
+ ? disappearingMessagesInChat
1303
+ ? WA_DEFAULT_EPHEMERAL
1304
+ : 0
1305
+ : disappearingMessagesInChat;
1306
+ await groupToggleEphemeral(jid, value);
1307
+ }
1308
+ else {
1309
+ if (messageType) {
1310
+ switch(messageType) {
1311
+ case 'PAYMENT':
1312
+ const paymentContent = await luki.handlePayment(content, quoted);
1313
+ return await relayMessage(jid, paymentContent, {
1314
+ messageId: Utils_1.generateMessageID(),
1315
+ noSelfSync: options.noSelfSync
1316
+ });
1317
+ case 'PRODUCT':
1318
+ const productContent = await luki.handleProduct(content, jid, quoted);
1319
+ const productMsg = await Utils_1.generateWAMessageFromContent(jid, productContent, { quoted });
1320
+ return await relayMessage(jid, productMsg.message, {
1321
+ messageId: productMsg.key.id,
1322
+ noSelfSync: options.noSelfSync
1323
+ });
1324
+
1325
+ case 'INTERACTIVE':
1326
+ const interactiveContent = await luki.handleInteractive(content, jid, quoted);
1327
+ const interactiveMsg = await Utils_1.generateWAMessageFromContent(jid, interactiveContent, { quoted });
1328
+ return await relayMessage(jid, interactiveMsg.message, {
1329
+ messageId: interactiveMsg.key.id,
1330
+ noSelfSync: options.noSelfSync
1331
+ });
1332
+ case 'ALBUM':
1333
+ return await luki.handleAlbum(content, jid, quoted)
1334
+ case 'EVENT':
1335
+ return await luki.handleEvent(content, jid, quoted)
1336
+ case 'POLL_RESULT':
1337
+ return await luki.handlePollResult(content, jid, quoted)
1338
+ case 'ORDER':
1339
+ return await luki.handleOrderMessage(content, jid, quoted)
1340
+ case 'GROUP_STATUS':
1341
+ return await luki.handleGroupStory(content, jid, quoted)
1342
+ case 'GROUP_LABEL':
1343
+ return await luki.handleGbLabel(content, jid)
1344
+ }
1345
+ }
1346
+ const fullMsg = await generateWAMessage(jid, content, {
1347
+ logger,
1348
+ userJid,
1349
+ getUrlInfo: text => getUrlInfo(text, {
1350
+ thumbnailWidth: linkPreviewImageThumbnailWidth,
1351
+ fetchOpts: {
1352
+ timeout: 3000,
1353
+ ...(httpRequestOptions || {})
1354
+ },
1355
+ logger,
1356
+ uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined
1357
+ }),
1358
+ //TODO: CACHE
1359
+ getProfilePicUrl: sock.profilePictureUrl,
1360
+ getCallLink: sock.createCallLink,
1361
+ upload: waUploadToServer,
1362
+ mediaCache: config.mediaCache,
1363
+ options: config.options,
1364
+ messageId: generateMessageIDV2(sock.user?.id),
1365
+ ...options
1366
+ });
1367
+ const isEventMsg = 'event' in content && !!content.event;
1368
+ const isDeleteMsg = 'delete' in content && !!content.delete;
1369
+ const isEditMsg = 'edit' in content && !!content.edit;
1370
+ const isPinMsg = 'pin' in content && !!content.pin;
1371
+ const isPollMessage = 'poll' in content && !!content.poll;
1372
+ const additionalAttributes = {};
1373
+ const additionalNodes = [];
1374
+ // required for delete
1375
+ if (isDeleteMsg) {
1376
+ // if the chat is a group, and I am not the author, then delete the message as an admin
1377
+ if (isJidGroup(content.delete?.remoteJid) && !content.delete?.fromMe) {
1378
+ additionalAttributes.edit = '8';
1379
+ }
1380
+ else {
1381
+ additionalAttributes.edit = '7';
1382
+ }
1383
+ }
1384
+ else if (isEditMsg) {
1385
+ additionalAttributes.edit = '1';
1386
+ }
1387
+ else if (isPinMsg) {
1388
+ additionalAttributes.edit = '2';
1389
+ }
1390
+ else if (isPollMessage) {
1391
+ additionalNodes.push({
1392
+ tag: 'meta',
1393
+ attrs: {
1394
+ polltype: 'creation'
1395
+ }
1396
+ });
1397
+ }
1398
+ else if (isEventMsg) {
1399
+ additionalNodes.push({
1400
+ tag: 'meta',
1401
+ attrs: {
1402
+ event_type: 'creation'
1403
+ }
1404
+ });
1405
+ }
1406
+ await relayMessage(jid, fullMsg.message, {
1407
+ messageId: fullMsg.key.id,
1408
+ useCachedGroupMetadata: options.useCachedGroupMetadata,
1409
+ additionalAttributes,
1410
+ statusJidList: options.statusJidList,
1411
+ additionalNodes: aiLabel ? additionalNodes : options.additionalNodes,
1412
+ participant,
1413
+ noSelfSync: options.noSelfSync
1414
+ });
1415
+ if (config.emitOwnEvents) {
1416
+ process.nextTick(async () => {
1417
+ await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
1418
+ });
1419
+ }
1420
+ return fullMsg;
1421
+ }
1422
+ },
1423
+ sendMessageMembers: async (jid, message, options = {}) => {
1424
+ const {
1425
+ messageId: idm,
1426
+ quoted,
1427
+ delayMs = 1500,
1428
+ useUserDevicesCache = true,
1429
+ cachedGroupMetadata,
1430
+ onlyMember = true
1431
+ } = options;
1432
+ const { server } = jidDecode(jid);
1433
+ if (server !== "g.us") throw new Error("@g.us server required");
1434
+ const meId = authState.creds.me.id;
1435
+ const messages = Utils_1.normalizeMessageContent(message);
1436
+ const groupData = cachedGroupMetadata? await cachedGroupMetadata(jid) : await groupMetadata(jid);
1437
+ const isLid = groupData.addressingMode === "lid";
1438
+ const adminJids = groupData.participants.filter((x) => x.admin !== null).map((y) => y.id)
1439
+ let participantJids = groupData.participants.map(z => z.id);
1440
+ if (onlyMember) {
1441
+ // Fix: array selalu truthy di JS meski kosong ([] ? true).
1442
+ // Cek .length supaya fallback ke semua member beneran jalan
1443
+ // kalau data admin kosong, bukan diam-diam kirim ke 0 orang.
1444
+ participantJids = adminJids.length ? adminJids : participantJids;
1445
+ }
1446
+ logger.info(`Sending message to ${participantJids.length} members from ${jid}`);
1447
+ for (let i = 0; i < participantJids.length; i++) {
1448
+ const jid = participantJids[i];
1449
+ if (areJidsSameUser(jid, meId)) continue;
1450
+ try {
1451
+ const msgId = `${idm || Utils_1.generateMessageID()}_${i}`;
1452
+ const fullMsg = await Utils_1.generateWAMessageFromContent(jid, message, {
1453
+ messageId: msgId,
1454
+ quoted
1455
+ })
1456
+ await relayMessage(jid, fullMsg.message, {
1457
+ messageId: fullMsg.key.id,
1458
+ useUserDevicesCache,
1459
+ noSelfSync: options.noSelfSync
1460
+ });
1461
+ logger.debug(`Message successfully sent to ${jid}`);
1462
+ if (delayMs && i < participantJids.length - 1) {
1463
+ await new Promise(z => setTimeout(z, delayMs));
1464
+ }
1465
+ } catch (e) {
1466
+ logger.error({ jid, e }, "Error sending message to");
1467
+ }
1468
+ }
1469
+ return JSON.stringify({
1470
+ members_total: participantJids.length,
1471
+ message
1472
+ }, null, 4);
1473
+ }
1474
+ };
1475
+ };
1476
+ //# sourceMappingURL=messages-send.js.map