violetics 7.0.1-alpha → 7.0.2-alpha

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 (114) hide show
  1. package/LICENSE +3 -2
  2. package/README.md +1001 -232
  3. package/WAProto/index.js +75379 -142631
  4. package/engine-requirements.js +11 -8
  5. package/lib/Defaults/index.js +132 -146
  6. package/lib/Signal/Group/ciphertext-message.js +2 -6
  7. package/lib/Signal/Group/group-session-builder.js +7 -42
  8. package/lib/Signal/Group/group_cipher.js +37 -52
  9. package/lib/Signal/Group/index.js +11 -57
  10. package/lib/Signal/Group/keyhelper.js +7 -45
  11. package/lib/Signal/Group/sender-chain-key.js +7 -16
  12. package/lib/Signal/Group/sender-key-distribution-message.js +8 -12
  13. package/lib/Signal/Group/sender-key-message.js +9 -13
  14. package/lib/Signal/Group/sender-key-name.js +2 -6
  15. package/lib/Signal/Group/sender-key-record.js +9 -22
  16. package/lib/Signal/Group/sender-key-state.js +27 -43
  17. package/lib/Signal/Group/sender-message-key.js +4 -8
  18. package/lib/Signal/libsignal.js +319 -94
  19. package/lib/Signal/lid-mapping.js +224 -139
  20. package/lib/Socket/Client/index.js +2 -19
  21. package/lib/Socket/Client/types.js +10 -0
  22. package/lib/Socket/Client/websocket.js +53 -0
  23. package/lib/Socket/business.js +162 -44
  24. package/lib/Socket/chats.js +477 -418
  25. package/lib/Socket/communities.js +430 -0
  26. package/lib/Socket/groups.js +110 -99
  27. package/lib/Socket/index.js +10 -10
  28. package/lib/Socket/messages-recv.js +884 -561
  29. package/lib/Socket/messages-send.js +859 -428
  30. package/lib/Socket/mex.js +41 -0
  31. package/lib/Socket/newsletter.js +195 -390
  32. package/lib/Socket/socket.js +465 -315
  33. package/lib/Store/index.js +3 -10
  34. package/lib/Store/make-in-memory-store.js +73 -79
  35. package/lib/Store/make-ordered-dictionary.js +4 -7
  36. package/lib/Store/object-repository.js +2 -6
  37. package/lib/Types/Auth.js +1 -2
  38. package/lib/Types/Bussines.js +1 -0
  39. package/lib/Types/Call.js +1 -2
  40. package/lib/Types/Chat.js +7 -4
  41. package/lib/Types/Contact.js +1 -2
  42. package/lib/Types/Events.js +1 -2
  43. package/lib/Types/GroupMetadata.js +1 -2
  44. package/lib/Types/Label.js +2 -5
  45. package/lib/Types/LabelAssociation.js +2 -5
  46. package/lib/Types/Message.js +17 -9
  47. package/lib/Types/Newsletter.js +33 -38
  48. package/lib/Types/Product.js +1 -2
  49. package/lib/Types/Signal.js +1 -2
  50. package/lib/Types/Socket.js +2 -2
  51. package/lib/Types/State.js +12 -2
  52. package/lib/Types/USync.js +1 -2
  53. package/lib/Types/index.js +14 -31
  54. package/lib/Utils/auth-utils.js +228 -152
  55. package/lib/Utils/browser-utils.js +28 -0
  56. package/lib/Utils/business.js +66 -70
  57. package/lib/Utils/chat-utils.js +331 -249
  58. package/lib/Utils/crypto.js +57 -91
  59. package/lib/Utils/decode-wa-message.js +168 -84
  60. package/lib/Utils/event-buffer.js +138 -80
  61. package/lib/Utils/generics.js +180 -297
  62. package/lib/Utils/history.js +83 -49
  63. package/lib/Utils/identity-change-handler.js +48 -0
  64. package/lib/Utils/index.js +19 -33
  65. package/lib/Utils/link-preview.js +14 -23
  66. package/lib/Utils/logger.js +2 -7
  67. package/lib/Utils/lt-hash.js +2 -46
  68. package/lib/Utils/make-mutex.js +24 -47
  69. package/lib/Utils/message-retry-manager.js +224 -0
  70. package/lib/Utils/messages-media.js +501 -496
  71. package/lib/Utils/messages.js +1428 -362
  72. package/lib/Utils/noise-handler.js +145 -100
  73. package/lib/Utils/pre-key-manager.js +105 -0
  74. package/lib/Utils/process-message.js +356 -150
  75. package/lib/Utils/reporting-utils.js +257 -0
  76. package/lib/Utils/signal.js +78 -73
  77. package/lib/Utils/sync-action-utils.js +47 -0
  78. package/lib/Utils/tc-token-utils.js +17 -0
  79. package/lib/Utils/use-multi-file-auth-state.js +35 -45
  80. package/lib/Utils/validate-connection.js +91 -107
  81. package/lib/WABinary/constants.js +1300 -1304
  82. package/lib/WABinary/decode.js +26 -48
  83. package/lib/WABinary/encode.js +109 -155
  84. package/lib/WABinary/generic-utils.js +161 -149
  85. package/lib/WABinary/index.js +5 -21
  86. package/lib/WABinary/jid-utils.js +73 -40
  87. package/lib/WABinary/types.js +1 -2
  88. package/lib/WAM/BinaryInfo.js +2 -6
  89. package/lib/WAM/constants.js +19070 -11568
  90. package/lib/WAM/encode.js +17 -23
  91. package/lib/WAM/index.js +3 -19
  92. package/lib/WAUSync/Protocols/USyncContactProtocol.js +8 -12
  93. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +11 -15
  94. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +9 -13
  95. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +9 -14
  96. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +20 -23
  97. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +13 -9
  98. package/lib/WAUSync/Protocols/index.js +4 -20
  99. package/lib/WAUSync/USyncQuery.js +40 -36
  100. package/lib/WAUSync/USyncUser.js +2 -6
  101. package/lib/WAUSync/index.js +3 -19
  102. package/lib/index.js +11 -44
  103. package/package.json +74 -107
  104. package/lib/Defaults/baileys-version.json +0 -3
  105. package/lib/Defaults/phonenumber-mcc.json +0 -223
  106. package/lib/Signal/Group/queue-job.js +0 -57
  107. package/lib/Socket/Client/abstract-socket-client.js +0 -13
  108. package/lib/Socket/Client/mobile-socket-client.js +0 -65
  109. package/lib/Socket/Client/web-socket-client.js +0 -118
  110. package/lib/Socket/groupStatus.js +0 -637
  111. package/lib/Socket/registration.js +0 -166
  112. package/lib/Socket/usync.js +0 -70
  113. package/lib/Store/make-cache-manager-store.js +0 -83
  114. package/lib/Utils/baileys-event-stream.js +0 -63
@@ -1,65 +1,53 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.makeMessagesSocket = void 0;
7
- const boom_1 = require("@hapi/boom");
8
- const node_cache_1 = __importDefault(require("@cacheable/node-cache"));
9
- const WAProto_1 = require("../../WAProto");
10
- const Defaults_1 = require("../Defaults");
11
- const Types_1 = require("../Types");
12
- const Utils_1 = require("../Utils");
13
- const link_preview_1 = require("../Utils/link-preview");
14
- const WABinary_1 = require("../WABinary");
15
- const newsletter_1 = require("./newsletter");
16
- const WAUSync_1 = require("../WAUSync");
17
- const violetics = require('./groupStatus');
18
- const makeMessagesSocket = (config) => {
19
- const {
20
- logger,
21
- linkPreviewImageThumbnailWidth,
22
- generateHighQualityLinkPreview,
23
- options: axiosOptions,
24
- patchMessageBeforeSending
25
- } = config;
26
- const sock = (0, newsletter_1.makeNewsletterSocket)(config);
27
- const {
28
- ev,
29
- authState,
30
- processingMutex,
31
- signalRepository,
32
- upsertMessage,
33
- query,
34
- fetchPrivacySettings,
35
- sendNode,
36
- groupMetadata,
37
- groupToggleEphemeral,
38
- executeUSyncQuery
39
- } = sock;
40
- const userDevicesCache = config.userDevicesCache || new node_cache_1.default({
41
- stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.USER_DEVICES,
1
+ import NodeCache from '@cacheable/node-cache';
2
+ import { Boom } from '@hapi/boom';
3
+ import { randomBytes } from 'crypto';
4
+ import { proto } from '../../WAProto/index.js';
5
+ import { BIZ_BOT_SUPPORT_PAYLOAD, DEFAULT_CACHE_TTLS, OLD_GROUP_ID_REGEX, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
6
+ import { aggregateMessageKeysNotFromMe, assertMediaContent, bindWaitForEvent, decryptMediaRetryData, delay, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, generateMessageIDV2, generateParticipantHashV2, generateWAMessageFromContent, generateWAMessage, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, hasValidAlbumMedia, MessageRetryManager, normalizeMessageContent, parseAndInjectE2ESessions, shouldIncludeBizBinaryNode, unixTimestampSeconds } from '../Utils/index.js';
7
+ import { AssociationType } from '../Types/index.js';
8
+ import { getUrlInfo } from '../Utils/link-preview.js';
9
+ import { makeKeyedMutex } from '../Utils/make-mutex.js';
10
+ import { getMessageReportingToken, shouldIncludeReportingToken } from '../Utils/reporting-utils.js';
11
+ import { areJidsSameUser, getBinaryNodeChild, getBinaryNodeChildren, getBizBinaryNode, isHostedLidUser, isHostedPnUser, isJidGroup, isJidNewsletter, isLidUser, isPnUser, jidDecode, jidEncode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
12
+ import { USyncQuery, USyncUser } from '../WAUSync/index.js';
13
+ import { makeNewsletterSocket } from './newsletter.js';
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 } = sock;
18
+ const userDevicesCache = config.userDevicesCache ||
19
+ new NodeCache({
20
+ stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES, // 5 minutes
21
+ useClones: false
22
+ });
23
+ const peerSessionsCache = new NodeCache({
24
+ stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES,
42
25
  useClones: false
43
26
  });
27
+ // Initialize message retry manager if enabled
28
+ const messageRetryManager = enableRecentMessageCache ? new MessageRetryManager(logger, maxMsgRetryCount) : null;
29
+ // Prevent race conditions in Signal session encryption by user
30
+ const encryptionMutex = makeKeyedMutex();
44
31
  let mediaConn;
45
32
  const refreshMediaConn = async (forceGet = false) => {
46
33
  const media = await mediaConn;
47
- if (!media || forceGet || (new Date().getTime() - media.fetchDate.getTime()) > media.ttl * 1000) {
34
+ if (!media || forceGet || new Date().getTime() - media.fetchDate.getTime() > media.ttl * 1000) {
48
35
  mediaConn = (async () => {
49
36
  const result = await query({
50
37
  tag: 'iq',
51
38
  attrs: {
52
39
  type: 'set',
53
40
  xmlns: 'w:m',
54
- to: WABinary_1.S_WHATSAPP_NET,
41
+ to: S_WHATSAPP_NET
55
42
  },
56
43
  content: [{ tag: 'media_conn', attrs: {} }]
57
44
  });
58
- const mediaConnNode = WABinary_1.getBinaryNodeChild(result, 'media_conn');
45
+ const mediaConnNode = getBinaryNodeChild(result, 'media_conn');
46
+ // TODO: explore full length of data that whatsapp provides
59
47
  const node = {
60
- hosts: WABinary_1.getBinaryNodeChildren(mediaConnNode, 'host').map(({ attrs }) => ({
48
+ hosts: getBinaryNodeChildren(mediaConnNode, 'host').map(({ attrs }) => ({
61
49
  hostname: attrs.hostname,
62
- maxContentLengthBytes: +attrs.maxContentLengthBytes,
50
+ maxContentLengthBytes: +attrs.maxContentLengthBytes
63
51
  })),
64
52
  auth: mediaConnNode.attrs.auth,
65
53
  ttl: +mediaConnNode.attrs.ttl,
@@ -71,18 +59,25 @@ const makeMessagesSocket = (config) => {
71
59
  }
72
60
  return mediaConn;
73
61
  };
62
+ /**
63
+ * generic send receipt function
64
+ * used for receipts of phone call, read, delivery etc.
65
+ * */
74
66
  const sendReceipt = async (jid, participant, messageIds, type) => {
67
+ if (!messageIds || messageIds.length === 0) {
68
+ throw new Boom('missing ids in receipt');
69
+ }
75
70
  const node = {
76
71
  tag: 'receipt',
77
72
  attrs: {
78
- id: messageIds[0],
79
- },
73
+ id: messageIds[0]
74
+ }
80
75
  };
81
76
  const isReadReceipt = type === 'read' || type === 'read-self';
82
77
  if (isReadReceipt) {
83
- node.attrs.t = (0, Utils_1.unixTimestampSeconds)().toString();
78
+ node.attrs.t = unixTimestampSeconds().toString();
84
79
  }
85
- if (type === 'sender' && WABinary_1.isJidUser(jid)) {
80
+ if (type === 'sender' && (isPnUser(jid) || isLidUser(jid))) {
86
81
  node.attrs.recipient = jid;
87
82
  node.attrs.to = participant;
88
83
  }
@@ -93,7 +88,7 @@ const makeMessagesSocket = (config) => {
93
88
  }
94
89
  }
95
90
  if (type) {
96
- node.attrs.type = WABinary_1.isJidNewsLetter(jid) ? 'read-self' : type;
91
+ node.attrs.type = type;
97
92
  }
98
93
  const remainingMessageIds = messageIds.slice(1);
99
94
  if (remainingMessageIds.length) {
@@ -111,252 +106,492 @@ const makeMessagesSocket = (config) => {
111
106
  logger.debug({ attrs: node.attrs, messageIds }, 'sending receipt for messages');
112
107
  await sendNode(node);
113
108
  };
109
+ /** Correctly bulk send receipts to multiple chats, participants */
114
110
  const sendReceipts = async (keys, type) => {
115
- const recps = (0, Utils_1.aggregateMessageKeysNotFromMe)(keys);
111
+ const recps = aggregateMessageKeysNotFromMe(keys);
116
112
  for (const { jid, participant, messageIds } of recps) {
117
113
  await sendReceipt(jid, participant, messageIds, type);
118
114
  }
119
115
  };
116
+ /** Bulk read messages. Keys can be from different chats & participants */
120
117
  const readMessages = async (keys) => {
121
118
  const privacySettings = await fetchPrivacySettings();
119
+ // based on privacy settings, we have to change the read type
122
120
  const readType = privacySettings.readreceipts === 'all' ? 'read' : 'read-self';
123
121
  await sendReceipts(keys, readType);
124
122
  };
123
+ /** Fetch all the devices we've to send a message to */
125
124
  const getUSyncDevices = async (jids, useCache, ignoreZeroDevices) => {
126
- const deviceResults = []
125
+ const deviceResults = [];
127
126
  if (!useCache) {
128
- logger.debug('not using cache for devices')
127
+ logger.debug('not using cache for devices');
129
128
  }
130
- const toFetch = []
131
- jids = Array.from(new Set(jids))
132
- for (let jid of jids) {
133
- const user = WABinary_1.jidDecode(jid)?.user
134
- jid = WABinary_1.jidNormalizedUser(jid)
129
+ const toFetch = [];
130
+ const jidsWithUser = jids
131
+ .map(jid => {
132
+ const decoded = jidDecode(jid);
133
+ const user = decoded?.user;
134
+ const device = decoded?.device;
135
+ const isExplicitDevice = typeof device === 'number' && device >= 0;
136
+ if (isExplicitDevice && user) {
137
+ deviceResults.push({
138
+ user,
139
+ device,
140
+ jid
141
+ });
142
+ return null;
143
+ }
144
+ jid = jidNormalizedUser(jid);
145
+ return { jid, user };
146
+ })
147
+ .filter(jid => jid !== null);
148
+ let mgetDevices;
149
+ if (useCache && userDevicesCache.mget) {
150
+ const usersToFetch = jidsWithUser.map(j => j?.user).filter(Boolean);
151
+ mgetDevices = await userDevicesCache.mget(usersToFetch);
152
+ }
153
+ for (const { jid, user } of jidsWithUser) {
135
154
  if (useCache) {
136
- const devices = userDevicesCache.get(user)
155
+ const devices = mgetDevices?.[user] ||
156
+ (userDevicesCache.mget ? undefined : (await userDevicesCache.get(user)));
137
157
  if (devices) {
138
- deviceResults.push(...devices)
139
- logger.trace({ user }, 'using cache for devices')
158
+ const devicesWithJid = devices.map(d => ({
159
+ ...d,
160
+ jid: jidEncode(d.user, d.server, d.device)
161
+ }));
162
+ deviceResults.push(...devicesWithJid);
163
+ logger.trace({ user }, 'using cache for devices');
140
164
  }
141
165
  else {
142
- toFetch.push(jid)
166
+ toFetch.push(jid);
143
167
  }
144
168
  }
145
169
  else {
146
- toFetch.push(jid)
170
+ toFetch.push(jid);
147
171
  }
148
172
  }
149
173
  if (!toFetch.length) {
150
- return deviceResults
174
+ return deviceResults;
175
+ }
176
+ const requestedLidUsers = new Set();
177
+ for (const jid of toFetch) {
178
+ if (isLidUser(jid) || isHostedLidUser(jid)) {
179
+ const user = jidDecode(jid)?.user;
180
+ if (user)
181
+ requestedLidUsers.add(user);
182
+ }
151
183
  }
152
- const query = new WAUSync_1.USyncQuery()
153
- .withContext('message')
154
- .withDeviceProtocol()
184
+ const query = new USyncQuery().withContext('message').withDeviceProtocol().withLIDProtocol();
155
185
  for (const jid of toFetch) {
156
- query.withUser(new WAUSync_1.USyncUser().withId(jid))
186
+ 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
157
187
  }
158
- const result = await executeUSyncQuery(query)
188
+ const result = await sock.executeUSyncQuery(query);
159
189
  if (result) {
160
- const extracted = Utils_1.extractDeviceJids(result?.list, authState.creds.me.id, ignoreZeroDevices)
161
- const deviceMap = {}
190
+ // TODO: LID MAP this stuff (lid protocol will now return lid with devices)
191
+ const lidResults = result.list.filter(a => !!a.lid);
192
+ if (lidResults.length > 0) {
193
+ logger.trace('Storing LID maps from device call');
194
+ await signalRepository.lidMapping.storeLIDPNMappings(lidResults.map(a => ({ lid: a.lid, pn: a.id })));
195
+ // Force-refresh sessions for newly mapped LIDs to align identity addressing
196
+ try {
197
+ const lids = lidResults.map(a => a.lid);
198
+ if (lids.length) {
199
+ await assertSessions(lids, true);
200
+ }
201
+ }
202
+ catch (e) {
203
+ logger.warn({ e, count: lidResults.length }, 'failed to assert sessions for newly mapped LIDs');
204
+ }
205
+ }
206
+ const extracted = extractDeviceJids(result?.list, authState.creds.me.id, authState.creds.me.lid, ignoreZeroDevices);
207
+ const deviceMap = {};
162
208
  for (const item of extracted) {
163
- deviceMap[item.user] = deviceMap[item.user] || []
164
- deviceMap[item.user].push(item)
165
- deviceResults.push(item)
209
+ deviceMap[item.user] = deviceMap[item.user] || [];
210
+ deviceMap[item.user]?.push(item);
211
+ }
212
+ // Process each user's devices as a group for bulk LID migration
213
+ for (const [user, userDevices] of Object.entries(deviceMap)) {
214
+ const isLidUser = requestedLidUsers.has(user);
215
+ // Process all devices for this user
216
+ for (const item of userDevices) {
217
+ const finalJid = isLidUser
218
+ ? jidEncode(user, item.server, item.device)
219
+ : jidEncode(item.user, item.server, item.device);
220
+ deviceResults.push({
221
+ ...item,
222
+ jid: finalJid
223
+ });
224
+ logger.debug({
225
+ user: item.user,
226
+ device: item.device,
227
+ finalJid,
228
+ usedLid: isLidUser
229
+ }, 'Processed device with LID priority');
230
+ }
166
231
  }
167
- for (const key in deviceMap) {
168
- userDevicesCache.set(key, deviceMap[key])
232
+ if (userDevicesCache.mset) {
233
+ // if the cache supports mset, we can set all devices in one go
234
+ await userDevicesCache.mset(Object.entries(deviceMap).map(([key, value]) => ({ key, value })));
235
+ }
236
+ else {
237
+ for (const key in deviceMap) {
238
+ if (deviceMap[key])
239
+ await userDevicesCache.set(key, deviceMap[key]);
240
+ }
241
+ }
242
+ const userDeviceUpdates = {};
243
+ for (const [userId, devices] of Object.entries(deviceMap)) {
244
+ if (devices && devices.length > 0) {
245
+ userDeviceUpdates[userId] = devices.map(d => d.device?.toString());
246
+ }
247
+ }
248
+ if (Object.keys(userDeviceUpdates).length > 0) {
249
+ try {
250
+ await authState.keys.set({ 'device-list': userDeviceUpdates });
251
+ logger.debug({ userCount: Object.keys(userDeviceUpdates).length }, 'stored user device lists for bulk migration');
252
+ }
253
+ catch (error) {
254
+ logger.warn({ error }, 'failed to store user device lists');
255
+ }
169
256
  }
170
257
  }
171
- return deviceResults
172
- }
258
+ return deviceResults;
259
+ };
260
+ /**
261
+ * Update Member Label
262
+ */
263
+ const updateMemberLabel = (jid, memberLabel) => {
264
+ return relayMessage(jid, {
265
+ protocolMessage: {
266
+ type: proto.Message.ProtocolMessage.Type.GROUP_MEMBER_LABEL_CHANGE,
267
+ memberLabel: {
268
+ label: memberLabel?.slice(0, 30),
269
+ labelTimestamp: unixTimestampSeconds()
270
+ }
271
+ }
272
+ }, {
273
+ additionalNodes: [
274
+ {
275
+ tag: 'meta',
276
+ attrs: {
277
+ tag_reason: 'user_update',
278
+ appdata: 'member_tag'
279
+ },
280
+ content: undefined
281
+ }
282
+ ]
283
+ });
284
+ };
173
285
  const assertSessions = async (jids, force) => {
174
286
  let didFetchNewSession = false;
175
- let jidsRequiringFetch = [];
176
- if (force) {
177
- jidsRequiringFetch = jids;
178
- }
179
- else {
180
- const addrs = jids.map(jid => (signalRepository
181
- .jidToSignalProtocolAddress(jid)));
182
- const sessions = await authState.keys.get('session', addrs);
183
- for (const jid of jids) {
184
- const signalId = signalRepository
185
- .jidToSignalProtocolAddress(jid);
186
- if (!sessions[signalId]) {
187
- jidsRequiringFetch.push(jid);
287
+ const uniqueJids = [...new Set(jids)]; // Deduplicate JIDs
288
+ const jidsRequiringFetch = [];
289
+ logger.debug({ jids }, 'assertSessions call with jids');
290
+ // Check peerSessionsCache and validate sessions using libsignal loadSession
291
+ for (const jid of uniqueJids) {
292
+ const signalId = signalRepository.jidToSignalProtocolAddress(jid);
293
+ const cachedSession = peerSessionsCache.get(signalId);
294
+ if (cachedSession !== undefined) {
295
+ if (cachedSession && !force) {
296
+ continue; // Session exists in cache
188
297
  }
189
298
  }
299
+ else {
300
+ const sessionValidation = await signalRepository.validateSession(jid);
301
+ const hasSession = sessionValidation.exists;
302
+ peerSessionsCache.set(signalId, hasSession);
303
+ if (hasSession && !force) {
304
+ continue;
305
+ }
306
+ }
307
+ jidsRequiringFetch.push(jid);
190
308
  }
191
309
  if (jidsRequiringFetch.length) {
192
- logger.debug({ jidsRequiringFetch }, 'fetching sessions');
310
+ // LID if mapped, otherwise original
311
+ const wireJids = [
312
+ ...jidsRequiringFetch.filter(jid => !!isLidUser(jid) || !!isHostedLidUser(jid)),
313
+ ...((await signalRepository.lidMapping.getLIDsForPNs(jidsRequiringFetch.filter(jid => !!isPnUser(jid) || !!isHostedPnUser(jid)))) || []).map(a => a.lid)
314
+ ];
315
+ logger.debug({ jidsRequiringFetch, wireJids }, 'fetching sessions');
193
316
  const result = await query({
194
317
  tag: 'iq',
195
318
  attrs: {
196
319
  xmlns: 'encrypt',
197
320
  type: 'get',
198
- to: WABinary_1.S_WHATSAPP_NET,
321
+ to: S_WHATSAPP_NET
199
322
  },
200
323
  content: [
201
324
  {
202
325
  tag: 'key',
203
326
  attrs: {},
204
- content: jidsRequiringFetch.map(jid => ({
205
- tag: 'user',
206
- attrs: { jid },
207
- }))
327
+ content: wireJids.map(jid => {
328
+ const attrs = { jid };
329
+ if (force)
330
+ attrs.reason = 'identity';
331
+ return { tag: 'user', attrs };
332
+ })
208
333
  }
209
334
  ]
210
335
  });
211
- await (0, Utils_1.parseAndInjectE2ESessions)(result, signalRepository);
336
+ await parseAndInjectE2ESessions(result, signalRepository);
212
337
  didFetchNewSession = true;
338
+ // Cache fetched sessions using wire JIDs
339
+ for (const wireJid of wireJids) {
340
+ const signalId = signalRepository.jidToSignalProtocolAddress(wireJid);
341
+ peerSessionsCache.set(signalId, true);
342
+ }
213
343
  }
214
344
  return didFetchNewSession;
215
345
  };
216
346
  const sendPeerDataOperationMessage = async (pdoMessage) => {
347
+ //TODO: for later, abstract the logic to send a Peer Message instead of just PDO - useful for App State Key Resync with phone
217
348
  if (!authState.creds.me?.id) {
218
- throw new boom_1.Boom('Not authenticated')
349
+ throw new Boom('Not authenticated');
219
350
  }
220
351
  const protocolMessage = {
221
352
  protocolMessage: {
222
353
  peerDataOperationRequestMessage: pdoMessage,
223
- type: WAProto_1.proto.Message.ProtocolMessage.Type.PEER_DATA_OPERATION_REQUEST_MESSAGE
354
+ type: proto.Message.ProtocolMessage.Type.PEER_DATA_OPERATION_REQUEST_MESSAGE
224
355
  }
225
356
  };
226
- const meJid = WABinary_1.jidNormalizedUser(authState.creds.me.id);
357
+ const meJid = jidNormalizedUser(authState.creds.me.id);
227
358
  const msgId = await relayMessage(meJid, protocolMessage, {
228
359
  additionalAttributes: {
229
360
  category: 'peer',
230
- push_priority: 'high_force',
361
+ push_priority: 'high_force'
231
362
  },
363
+ additionalNodes: [
364
+ {
365
+ tag: 'meta',
366
+ attrs: { appdata: 'default' }
367
+ }
368
+ ]
232
369
  });
233
370
  return msgId;
234
371
  };
235
- const createParticipantNodes = async (jids, message, extraAttrs) => {
236
- const patched = await patchMessageBeforeSending(message, jids);
237
- const bytes = (0, Utils_1.encodeWAMessage)(patched);
372
+ const createParticipantNodes = async (recipientJids, message, extraAttrs, dsmMessage) => {
373
+ if (!recipientJids.length) {
374
+ return { nodes: [], shouldIncludeDeviceIdentity: false };
375
+ }
376
+ const patched = await patchMessageBeforeSending(message, recipientJids);
377
+ const patchedMessages = Array.isArray(patched)
378
+ ? patched
379
+ : recipientJids.map(jid => ({ recipientJid: jid, message: patched }));
238
380
  let shouldIncludeDeviceIdentity = false;
239
- const nodes = await Promise.all(jids.map(async (jid) => {
240
- const { type, ciphertext } = await signalRepository
241
- .encryptMessage({ jid, data: bytes });
242
- if (type === 'pkmsg') {
243
- shouldIncludeDeviceIdentity = true;
244
- }
245
- const node = {
246
- tag: 'to',
247
- attrs: { jid },
248
- content: [{
249
- tag: 'enc',
250
- attrs: {
251
- v: '2',
252
- type,
253
- ...extraAttrs || {}
254
- },
255
- content: ciphertext
256
- }]
257
- };
258
- return node;
259
- }));
381
+ const meId = authState.creds.me.id;
382
+ const meLid = authState.creds.me?.lid;
383
+ const meLidUser = meLid ? jidDecode(meLid)?.user : null;
384
+ const encryptionPromises = patchedMessages.map(async ({ recipientJid: jid, message: patchedMessage }) => {
385
+ try {
386
+ if (!jid)
387
+ return null;
388
+ let msgToEncrypt = patchedMessage;
389
+ if (dsmMessage) {
390
+ const { user: targetUser } = jidDecode(jid);
391
+ const { user: ownPnUser } = jidDecode(meId);
392
+ const ownLidUser = meLidUser;
393
+ const isOwnUser = targetUser === ownPnUser || (ownLidUser && targetUser === ownLidUser);
394
+ const isExactSenderDevice = jid === meId || (meLid && jid === meLid);
395
+ if (isOwnUser && !isExactSenderDevice) {
396
+ msgToEncrypt = dsmMessage;
397
+ logger.debug({ jid, targetUser }, 'Using DSM for own device');
398
+ }
399
+ }
400
+ const bytes = encodeWAMessage(msgToEncrypt);
401
+ const mutexKey = jid;
402
+ const node = await encryptionMutex.mutex(mutexKey, async () => {
403
+ const { type, ciphertext } = await signalRepository.encryptMessage({ jid, data: bytes });
404
+ if (type === 'pkmsg') {
405
+ shouldIncludeDeviceIdentity = true;
406
+ }
407
+ return {
408
+ tag: 'to',
409
+ attrs: { jid },
410
+ content: [
411
+ {
412
+ tag: 'enc',
413
+ attrs: { v: '2', type, ...(extraAttrs || {}) },
414
+ content: ciphertext
415
+ }
416
+ ]
417
+ };
418
+ });
419
+ return node;
420
+ }
421
+ catch (err) {
422
+ logger.error({ jid, err }, 'Failed to encrypt for recipient');
423
+ return null;
424
+ }
425
+ });
426
+ const nodes = (await Promise.all(encryptionPromises)).filter(node => node !== null);
427
+ if (recipientJids.length > 0 && nodes.length === 0) {
428
+ throw new Boom('All encryptions failed', { statusCode: 500 });
429
+ }
260
430
  return { nodes, shouldIncludeDeviceIdentity };
261
431
  };
262
- const relayMessage = async (jid, message, { messageId: msgId, participant, additionalAttributes, additionalNodes, useUserDevicesCache, cachedGroupMetadata, useCachedGroupMetadata, statusJidList }) => {
432
+ const relayMessage = async (jid, message, { messageId: msgId, participant, additionalAttributes, additionalNodes, useUserDevicesCache, useCachedGroupMetadata, statusJidList }) => {
263
433
  const meId = authState.creds.me.id;
264
- let shouldIncludeDeviceIdentity = false;
265
- let didPushAdditional = false
266
- const { user, server } = WABinary_1.jidDecode(jid);
434
+ const meLid = authState.creds.me?.lid;
435
+ const isRetryResend = Boolean(participant?.jid);
436
+ let shouldIncludeDeviceIdentity = isRetryResend;
267
437
  const statusJid = 'status@broadcast';
438
+ const { user, server } = jidDecode(jid);
268
439
  const isGroup = server === 'g.us';
269
440
  const isStatus = jid === statusJid;
270
441
  const isLid = server === 'lid';
271
- const isPrivate = server === 's.whatsapp.net'
272
442
  const isNewsletter = server === 'newsletter';
273
- msgId = msgId || (0, Utils_1.generateMessageID)();
443
+ const isGroupOrStatus = isGroup || isStatus;
444
+ const finalJid = jid;
445
+ msgId = msgId || generateMessageIDV2(meId);
274
446
  useUserDevicesCache = useUserDevicesCache !== false;
275
- useCachedGroupMetadata = useCachedGroupMetadata !== false && !isStatus
447
+ useCachedGroupMetadata = useCachedGroupMetadata !== false && !isStatus;
276
448
  const participants = [];
277
- const destinationJid = (!isStatus) ? WABinary_1.jidEncode(user, isLid ? 'lid' : isGroup ? 'g.us' : isNewsletter ? 'newsletter' : 's.whatsapp.net') : statusJid;
449
+ const destinationJid = !isStatus ? finalJid : statusJid;
278
450
  const binaryNodeContent = [];
279
451
  const devices = [];
452
+ let reportingMessage;
280
453
  const meMsg = {
281
454
  deviceSentMessage: {
282
455
  destinationJid,
283
456
  message
284
- }
457
+ },
458
+ messageContextInfo: message.messageContextInfo
285
459
  };
286
- const extraAttrs = {}
287
- const messages = Utils_1.normalizeMessageContent(message);
288
- const buttonType = getButtonType(messages);
460
+ const extraAttrs = {};
289
461
  if (participant) {
290
462
  if (!isGroup && !isStatus) {
291
- additionalAttributes = { ...additionalAttributes, 'device_fanout': 'false' };
463
+ additionalAttributes = { ...additionalAttributes, device_fanout: 'false' };
292
464
  }
293
- const { user, device } = WABinary_1.jidDecode(participant.jid);
294
- devices.push({ user, device });
465
+ const { user, device } = jidDecode(participant.jid);
466
+ devices.push({
467
+ user,
468
+ device,
469
+ jid: participant.jid
470
+ });
295
471
  }
296
472
  await authState.keys.transaction(async () => {
297
- const mediaType = getMediaType(messages);
473
+ // vltcs@changes 02-02-26 --- Normalize message first to extract the original message and valid media type
474
+ const innerMessage = normalizeMessageContent(message);
475
+ const mediaType = getMediaType(innerMessage);
298
476
  if (mediaType) {
299
- extraAttrs['mediatype'] = mediaType
477
+ extraAttrs['mediatype'] = mediaType;
478
+ }
479
+ if (isNewsletter) {
480
+ if (innerMessage.productMessage) {
481
+ extraAttrs['mediatype'] = 'image'; // Lia@Note 02-02-26 --- Treat product message as image message to avoid the "479" error (⁠✷⁠‿⁠✷⁠)
482
+ }
483
+ const patched = patchMessageBeforeSending ? await patchMessageBeforeSending(message, []) : message;
484
+ const bytes = encodeNewsletterMessage(patched);
485
+ // vltcs@changes 08-02-26 --- Add "additionalNodes" for newsletter too (⁠っ⁠˘̩⁠╭⁠╮⁠˘̩⁠)⁠っ
486
+ if (additionalNodes && additionalNodes.length > 0) {
487
+ ;
488
+ binaryNodeContent.push(...additionalNodes);
489
+ }
490
+ binaryNodeContent.push({
491
+ tag: 'plaintext',
492
+ attrs: extraAttrs, // vltcs@changes 02-02-26 --- Add extraAttrs to fix media being rejected when sending to newsletter (⁠◠⁠‿⁠◕⁠)
493
+ content: bytes
494
+ });
495
+ const stanza = {
496
+ tag: 'message',
497
+ attrs: {
498
+ to: jid,
499
+ id: msgId,
500
+ type: getMessageType(message),
501
+ ...(additionalAttributes || {})
502
+ },
503
+ content: binaryNodeContent
504
+ };
505
+ logger.debug({ msgId }, `sending newsletter message to ${jid}`);
506
+ await sendNode(stanza);
507
+ return;
300
508
  }
301
- if (messages.pinInChatMessage || messages.keepInChatMessage || message.reactionMessage || message.protocolMessage?.editedMessage) {
302
- extraAttrs['decrypt-fail'] = 'hide'
509
+ // vltcs@changes 02-02-26 --- Add keepInChat, editedMessage, mediaNotifyMessage and pollUpdateMessage
510
+ if (innerMessage?.pinInChatMessage || innerMessage?.pollUpdateMessage || innerMessage?.keepInChatMessage || innerMessage?.protocolMessage?.editedMessage || innerMessage?.protocolMessage?.mediaNotifyMessage || innerMessage?.reactionMessage) {
511
+ extraAttrs['decrypt-fail'] = 'hide'; // todo: expand for reactions and other types
303
512
  }
304
- if (messages.interactiveResponseMessage?.nativeFlowResponseMessage) {
305
- extraAttrs['native_flow_name'] = messages.interactiveResponseMessage?.nativeFlowResponseMessage.name
513
+ // vltcs@changes 02-02-26 --- Add native_flow_name to extraAttrs when sending interactiveResponseMessage
514
+ if (innerMessage?.interactiveResponseMessage?.nativeFlowResponseMessage) {
515
+ extraAttrs['native_flow_name'] = innerMessage.interactiveResponseMessage.nativeFlowResponseMessage.name;
306
516
  }
307
- if (isGroup || isStatus) {
517
+ if (isGroupOrStatus && !isRetryResend) {
308
518
  const [groupData, senderKeyMap] = await Promise.all([
309
519
  (async () => {
310
- let groupData = useCachedGroupMetadata && cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined
311
- if (groupData) {
520
+ let groupData = useCachedGroupMetadata && cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined; // todo: should we rely on the cache specially if the cache is outdated and the metadata has new fields?
521
+ if (groupData && Array.isArray(groupData?.participants)) {
312
522
  logger.trace({ jid, participants: groupData.participants.length }, 'using cached group metadata');
313
523
  }
314
524
  else if (!isStatus) {
315
- groupData = await groupMetadata(jid)
525
+ groupData = await groupMetadata(jid); // TODO: start storing group participant list + addr mode in Signal & stop relying on this
316
526
  }
317
527
  return groupData;
318
528
  })(),
319
529
  (async () => {
320
530
  if (!participant && !isStatus) {
321
- const result = await authState.keys.get('sender-key-memory', [jid])
322
- return result[jid] || {}
531
+ // what if sender memory is less accurate than the cached metadata
532
+ // on participant change in group, we should do sender memory manipulation
533
+ const result = await authState.keys.get('sender-key-memory', [jid]); // TODO: check out what if the sender key memory doesn't include the LID stuff now?
534
+ return result[jid] || {};
323
535
  }
324
- return {}
536
+ return {};
325
537
  })()
326
538
  ]);
327
- if (!participant) {
328
- const participantsList = (groupData && !isStatus) ? groupData.participants.map(p => p.id) : []
329
- if (isStatus && statusJidList) {
330
- participantsList.push(...statusJidList)
331
- }
332
- const additionalDevices = await getUSyncDevices(participantsList, !!useUserDevicesCache, false)
333
- devices.push(...additionalDevices)
539
+ const participantsList = groupData ? groupData.participants.map(p => p.id) : [];
540
+ if (groupData?.ephemeralDuration && groupData.ephemeralDuration > 0) {
541
+ additionalAttributes = {
542
+ ...additionalAttributes,
543
+ expiration: groupData.ephemeralDuration.toString()
544
+ };
545
+ }
546
+ if (isStatus && statusJidList) {
547
+ participantsList.push(...statusJidList);
548
+ }
549
+ const additionalDevices = await getUSyncDevices(participantsList, !!useUserDevicesCache, false);
550
+ devices.push(...additionalDevices);
551
+ if (isGroup) {
552
+ additionalAttributes = {
553
+ ...additionalAttributes,
554
+ addressing_mode: groupData?.addressingMode || 'lid'
555
+ };
556
+ }
557
+ const patched = await patchMessageBeforeSending(message);
558
+ if (Array.isArray(patched)) {
559
+ throw new Boom('Per-jid patching is not supported in groups');
334
560
  }
335
- const patched = await patchMessageBeforeSending(message, devices.map(d => WABinary_1.jidEncode(d.user, isLid ? 'lid' : 's.whatsapp.net', d.device)));
336
- const bytes = Utils_1.encodeWAMessage(patched);
561
+ const bytes = encodeWAMessage(patched);
562
+ reportingMessage = patched;
563
+ const groupAddressingMode = additionalAttributes?.['addressing_mode'] || groupData?.addressingMode || 'lid';
564
+ const groupSenderIdentity = groupAddressingMode === 'lid' && meLid ? meLid : meId;
337
565
  const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage({
338
566
  group: destinationJid,
339
567
  data: bytes,
340
- meId,
568
+ meId: groupSenderIdentity
341
569
  });
342
- const senderKeyJids = [];
343
- for (const { user, device } of devices) {
344
- const jid = WABinary_1.jidEncode(user, (groupData === null || groupData === void 0 ? void 0 : groupData.addressingMode) === 'lid' ? 'lid' : 's.whatsapp.net', device);
345
- if (!senderKeyMap[jid] || !!participant) {
346
- senderKeyJids.push(jid);
347
- senderKeyMap[jid] = true;
570
+ const senderKeyRecipients = [];
571
+ for (const device of devices) {
572
+ const deviceJid = device.jid;
573
+ const hasKey = !!senderKeyMap[deviceJid];
574
+ if ((!hasKey || !!participant) &&
575
+ !isHostedLidUser(deviceJid) &&
576
+ !isHostedPnUser(deviceJid) &&
577
+ device.device !== 99) {
578
+ //todo: revamp all this logic
579
+ // the goal is to follow with what I said above for each group, and instead of a true false map of ids, we can set an array full of those the app has already sent pkmsgs
580
+ senderKeyRecipients.push(deviceJid);
581
+ senderKeyMap[deviceJid] = true;
348
582
  }
349
583
  }
350
- if (senderKeyJids.length) {
351
- logger.debug({ senderKeyJids }, 'sending new sender key');
584
+ if (senderKeyRecipients.length) {
585
+ logger.debug({ senderKeyJids: senderKeyRecipients }, 'sending new sender key');
352
586
  const senderKeyMsg = {
353
587
  senderKeyDistributionMessage: {
354
588
  axolotlSenderKeyDistributionMessage: senderKeyDistributionMessage,
355
589
  groupId: destinationJid
356
590
  }
357
591
  };
358
- await assertSessions(senderKeyJids, false);
359
- const result = await createParticipantNodes(senderKeyJids, senderKeyMsg, extraAttrs)
592
+ const senderKeySessionTargets = senderKeyRecipients;
593
+ await assertSessions(senderKeySessionTargets);
594
+ const result = await createParticipantNodes(senderKeyRecipients, senderKeyMsg, extraAttrs);
360
595
  shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || result.shouldIncludeDeviceIdentity;
361
596
  participants.push(...result.nodes);
362
597
  }
@@ -367,63 +602,120 @@ const makeMessagesSocket = (config) => {
367
602
  });
368
603
  await authState.keys.set({ 'sender-key-memory': { [jid]: senderKeyMap } });
369
604
  }
370
- else if (isNewsletter) {
371
- if (message.protocolMessage?.editedMessage) {
372
- msgId = message.protocolMessage.key?.id
373
- message = message.protocolMessage.editedMessage
605
+ else {
606
+ // ADDRESSING CONSISTENCY: Match own identity to conversation context
607
+ // TODO: investigate if this is true
608
+ let ownId = meId;
609
+ if (isLid && meLid) {
610
+ ownId = meLid;
611
+ logger.debug({ to: jid, ownId }, 'Using LID identity for @lid conversation');
374
612
  }
375
- if (message.protocolMessage?.type === WAProto_1.proto.Message.ProtocolMessage.Type.REVOKE) {
376
- msgId = message.protocolMessage.key?.id
377
- message = {}
613
+ else {
614
+ logger.debug({ to: jid, ownId }, 'Using PN identity for @s.whatsapp.net conversation');
378
615
  }
379
- const patched = await patchMessageBeforeSending(message, [])
380
- const bytes = Utils_1.encodeNewsletterMessage(patched)
381
- binaryNodeContent.push({
382
- tag: 'plaintext',
383
- attrs: extraAttrs ? extraAttrs : {},
384
- content: bytes
385
- })
386
- }
387
- else {
388
- const { user: meUser } = WABinary_1.jidDecode(meId);
616
+ const { user: ownUser } = jidDecode(ownId);
389
617
  if (!participant) {
390
- devices.push({ user })
391
- if (user !== meUser) {
392
- devices.push({ user: meUser })
618
+ const patchedForReporting = await patchMessageBeforeSending(message, [jid]);
619
+ reportingMessage = Array.isArray(patchedForReporting)
620
+ ? patchedForReporting.find(item => item.recipientJid === jid) || patchedForReporting[0]
621
+ : patchedForReporting;
622
+ }
623
+ if (!isRetryResend) {
624
+ const targetUserServer = isLid ? 'lid' : 's.whatsapp.net';
625
+ devices.push({
626
+ user,
627
+ device: 0,
628
+ jid: jidEncode(user, targetUserServer, 0) // rajeh, todo: this entire logic is convoluted and weird.
629
+ });
630
+ if (user !== ownUser) {
631
+ const ownUserServer = isLid ? 'lid' : 's.whatsapp.net';
632
+ const ownUserForAddressing = isLid && meLid ? jidDecode(meLid).user : jidDecode(meId).user;
633
+ devices.push({
634
+ user: ownUserForAddressing,
635
+ device: 0,
636
+ jid: jidEncode(ownUserForAddressing, ownUserServer, 0)
637
+ });
393
638
  }
394
639
  if (additionalAttributes?.['category'] !== 'peer') {
395
- const additionalDevices = await getUSyncDevices([meId, jid], !!useUserDevicesCache, true)
396
- devices.push(...additionalDevices)
640
+ // Clear placeholders and enumerate actual devices
641
+ devices.length = 0;
642
+ // Use conversation-appropriate sender identity
643
+ const senderIdentity = isLid && meLid
644
+ ? jidEncode(jidDecode(meLid)?.user, 'lid', undefined)
645
+ : jidEncode(jidDecode(meId)?.user, 's.whatsapp.net', undefined);
646
+ // Enumerate devices for sender and target with consistent addressing
647
+ const sessionDevices = await getUSyncDevices([senderIdentity, jid], true, false);
648
+ devices.push(...sessionDevices);
649
+ logger.debug({
650
+ deviceCount: devices.length,
651
+ devices: devices.map(d => `${d.user}:${d.device}@${jidDecode(d.jid)?.server}`)
652
+ }, 'Device enumeration complete with unified addressing');
397
653
  }
398
654
  }
399
- const allJids = [];
400
- const meJids = [];
401
- const otherJids = [];
402
- for (const { user, device } of devices) {
403
- const isMe = user === meUser
404
- const jid = WABinary_1.jidEncode(isMe && isLid ? authState.creds?.me?.lid?.split(':')[0] || user : user, isLid ? 'lid' : 's.whatsapp.net', device)
655
+ const allRecipients = [];
656
+ const meRecipients = [];
657
+ const otherRecipients = [];
658
+ const { user: mePnUser } = jidDecode(meId);
659
+ const { user: meLidUser } = meLid ? jidDecode(meLid) : { user: null };
660
+ for (const { user, jid } of devices) {
661
+ const isExactSenderDevice = jid === meId || (meLid && jid === meLid);
662
+ if (isExactSenderDevice) {
663
+ logger.debug({ jid, meId, meLid }, 'Skipping exact sender device (whatsmeow pattern)');
664
+ continue;
665
+ }
666
+ // Check if this is our device (could match either PN or LID user)
667
+ const isMe = user === mePnUser || user === meLidUser;
405
668
  if (isMe) {
406
- meJids.push(jid)
669
+ meRecipients.push(jid);
407
670
  }
408
671
  else {
409
- otherJids.push(jid)
672
+ otherRecipients.push(jid);
410
673
  }
411
- allJids.push(jid)
674
+ allRecipients.push(jid);
412
675
  }
413
- await assertSessions(allJids, false);
676
+ await assertSessions(allRecipients);
414
677
  const [{ nodes: meNodes, shouldIncludeDeviceIdentity: s1 }, { nodes: otherNodes, shouldIncludeDeviceIdentity: s2 }] = await Promise.all([
415
- createParticipantNodes(meJids, meMsg, extraAttrs),
416
- createParticipantNodes(otherJids, message, extraAttrs)
417
- ])
678
+ // For own devices: use DSM if available (1:1 chats only)
679
+ createParticipantNodes(meRecipients, meMsg || message, extraAttrs),
680
+ createParticipantNodes(otherRecipients, message, extraAttrs, meMsg)
681
+ ]);
418
682
  participants.push(...meNodes);
419
683
  participants.push(...otherNodes);
684
+ if (meRecipients.length > 0 || otherRecipients.length > 0) {
685
+ extraAttrs['phash'] = generateParticipantHashV2([...meRecipients, ...otherRecipients]);
686
+ }
420
687
  shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || s1 || s2;
421
688
  }
689
+ if (isRetryResend) {
690
+ const isParticipantLid = isLidUser(participant.jid);
691
+ const isMe = areJidsSameUser(participant.jid, isParticipantLid ? meLid : meId);
692
+ const encodedMessageToSend = isMe
693
+ ? encodeWAMessage({
694
+ deviceSentMessage: {
695
+ destinationJid,
696
+ message
697
+ }
698
+ })
699
+ : encodeWAMessage(message);
700
+ const { type, ciphertext: encryptedContent } = await signalRepository.encryptMessage({
701
+ data: encodedMessageToSend,
702
+ jid: participant.jid
703
+ });
704
+ binaryNodeContent.push({
705
+ tag: 'enc',
706
+ attrs: {
707
+ v: '2',
708
+ type,
709
+ count: participant.count?.toString() || '0'
710
+ },
711
+ content: encryptedContent
712
+ });
713
+ }
422
714
  if (participants.length) {
423
715
  if (additionalAttributes?.['category'] === 'peer') {
424
- const peerNode = participants[0]?.content?.[0]
716
+ const peerNode = participants[0]?.content?.[0];
425
717
  if (peerNode) {
426
- binaryNodeContent.push(peerNode)
718
+ binaryNodeContent.push(peerNode); // push only enc
427
719
  }
428
720
  }
429
721
  else {
@@ -431,24 +723,28 @@ const makeMessagesSocket = (config) => {
431
723
  tag: 'participants',
432
724
  attrs: {},
433
725
  content: participants
434
- })
726
+ });
435
727
  }
436
728
  }
437
729
  const stanza = {
438
730
  tag: 'message',
439
731
  attrs: {
440
732
  id: msgId,
441
- type: getTypeMessage(messages),
733
+ to: destinationJid,
734
+ type: getMessageType(message),
442
735
  ...(additionalAttributes || {})
443
736
  },
444
737
  content: binaryNodeContent
445
- }
738
+ };
739
+ // if the participant to send to is explicitly specified (generally retry recp)
740
+ // ensure the message is only sent to that person
741
+ // if a retry receipt is sent to everyone -- it'll fail decryption for everyone else who received the msg
446
742
  if (participant) {
447
- if (WABinary_1.isJidGroup(destinationJid)) {
743
+ if (isJidGroup(destinationJid)) {
448
744
  stanza.attrs.to = destinationJid;
449
745
  stanza.attrs.participant = participant.jid;
450
746
  }
451
- else if (WABinary_1.areJidsSameUser(participant.jid, meId)) {
747
+ else if (areJidsSameUser(participant.jid, meId)) {
452
748
  stanza.attrs.to = participant.jid;
453
749
  stanza.attrs.recipient = destinationJid;
454
750
  }
@@ -460,143 +756,162 @@ const makeMessagesSocket = (config) => {
460
756
  stanza.attrs.to = destinationJid;
461
757
  }
462
758
  if (shouldIncludeDeviceIdentity) {
759
+ ;
463
760
  stanza.content.push({
464
761
  tag: 'device-identity',
465
762
  attrs: {},
466
- content: (0, Utils_1.encodeSignedDeviceIdentity)(authState.creds.account, true)
763
+ content: encodeSignedDeviceIdentity(authState.creds.account, true)
467
764
  });
468
765
  logger.debug({ jid }, 'adding device identity');
469
766
  }
470
- if (!isNewsletter && buttonType && !isStatus) {
471
- const content = WABinary_1.getAdditionalNode(buttonType)
472
- const filteredNode = WABinary_1.getBinaryNodeFilter(additionalNodes)
473
- if (filteredNode) {
474
- didPushAdditional = true
475
- stanza.content.push(...additionalNodes)
767
+ if (!isNewsletter &&
768
+ !isRetryResend &&
769
+ reportingMessage?.messageContextInfo?.messageSecret &&
770
+ shouldIncludeReportingToken(reportingMessage)) {
771
+ try {
772
+ const encoded = encodeWAMessage(reportingMessage);
773
+ const reportingKey = {
774
+ id: msgId,
775
+ fromMe: true,
776
+ remoteJid: destinationJid,
777
+ participant: participant?.jid
778
+ };
779
+ const reportingNode = await getMessageReportingToken(encoded, reportingMessage, reportingKey);
780
+ if (reportingNode) {
781
+ ;
782
+ stanza.content.push(reportingNode);
783
+ logger.trace({ jid }, 'added reporting token to message');
784
+ }
476
785
  }
477
- else {
478
- stanza.content.push(...content)
786
+ catch (error) {
787
+ logger.warn({ jid, trace: error?.stack }, 'failed to attach reporting token');
479
788
  }
480
- logger.debug({ jid }, 'adding business node')
481
789
  }
482
- if (!didPushAdditional && additionalNodes && additionalNodes.length > 0) {
790
+ const contactTcTokenData = !isGroup && !isRetryResend && !isStatus ? await authState.keys.get('tctoken', [destinationJid]) : {};
791
+ const tcTokenBuffer = contactTcTokenData[destinationJid]?.token;
792
+ if (tcTokenBuffer) {
793
+ ;
794
+ stanza.content.push({
795
+ tag: 'tctoken',
796
+ attrs: {},
797
+ content: tcTokenBuffer
798
+ });
799
+ }
800
+ if (additionalNodes && additionalNodes.length > 0) {
801
+ ;
483
802
  stanza.content.push(...additionalNodes);
484
803
  }
804
+ // vltcs@changes 30-01-26 --- Add Biz Binary Node to support button messages
805
+ else if (shouldIncludeBizBinaryNode(innerMessage)) {
806
+ const bizNode = getBizBinaryNode(innerMessage);
807
+ stanza.content.push(bizNode);
808
+ }
809
+ if (isGroup && OLD_GROUP_ID_REGEX.test(jid) && !innerMessage.reactionMessage) {
810
+ stanza.content.push({
811
+ tag: 'multicast',
812
+ attrs: {},
813
+ content: undefined
814
+ })
815
+ }
485
816
  logger.debug({ msgId }, `sending message to ${participants.length} devices`);
486
817
  await sendNode(stanza);
487
- });
488
- message = Types_1.WAProto.Message.fromObject(message)
489
- const messageJSON = {
490
- key: {
491
- remoteJid: jid,
492
- fromMe: true,
493
- id: msgId
494
- },
495
- message: message,
496
- messageTimestamp: Utils_1.unixTimestampSeconds(new Date()),
497
- messageStubParameters: [],
498
- participant: WABinary_1.isJidGroup(jid) || WABinary_1.isJidStatusBroadcast(jid) ? meId : undefined,
499
- status: Types_1.WAMessageStatus.PENDING
500
- }
501
- return Types_1.WAProto.WebMessageInfo.fromObject(messageJSON);
818
+ // Add message to retry cache if enabled
819
+ if (messageRetryManager && !participant) {
820
+ messageRetryManager.addRecentMessage(destinationJid, msgId, message);
821
+ }
822
+ }, meId);
823
+ return msgId;
502
824
  };
503
- const getTypeMessage = (msg) => {
504
- const message = Utils_1.normalizeMessageContent(msg);
505
- if (message.reactionMessage) {
506
- return 'reaction'
825
+ const getMessageType = (message) => {
826
+ const normalizedMessage = normalizeMessageContent(message);
827
+ if (!normalizedMessage)
828
+ return 'text';
829
+ if (normalizedMessage.reactionMessage || normalizedMessage.encReactionMessage) {
830
+ return 'reaction';
507
831
  }
508
- else if (getMediaType(message)) {
509
- return 'media'
832
+ if (normalizedMessage.pollCreationMessage ||
833
+ normalizedMessage.pollCreationMessageV2 ||
834
+ normalizedMessage.pollCreationMessageV3 ||
835
+ normalizedMessage.pollCreationMessageV5 ||
836
+ normalizedMessage.pollCreationMessageV6 ||
837
+ normalizedMessage.pollUpdateMessage) {
838
+ return 'poll';
510
839
  }
511
- else {
512
- return 'text'
840
+ if (normalizedMessage.eventMessage) {
841
+ return 'event';
842
+ }
843
+ if (getMediaType(normalizedMessage) !== '') {
844
+ return 'media';
513
845
  }
514
- }
846
+ return 'text';
847
+ };
515
848
  const getMediaType = (message) => {
516
849
  if (message.imageMessage) {
517
- return 'image'
850
+ return 'image';
518
851
  }
519
852
  else if (message.videoMessage) {
520
- return message.videoMessage.gifPlayback ? 'gif' : 'video'
853
+ return message.videoMessage.gifPlayback ? 'gif' : 'video';
854
+ }
855
+ else if (message.stickerMessage) {
856
+ return message.stickerMessage.isLottie ? '1p_sticker' : message.stickerMessage.isAvatar ? 'avatar_sticker' : 'sticker';
521
857
  }
522
858
  else if (message.audioMessage) {
523
- return message.audioMessage.ptt ? 'ptt' : 'audio'
859
+ return message.audioMessage.ptt ? 'ptt' : 'audio';
860
+ }
861
+ else if (message.albumMessage) {
862
+ return 'collection';
524
863
  }
525
864
  else if (message.contactMessage) {
526
- return 'vcard'
865
+ return 'vcard';
527
866
  }
528
867
  else if (message.documentMessage) {
529
- return 'document'
868
+ return 'document';
530
869
  }
531
870
  else if (message.contactsArrayMessage) {
532
- return 'contact_array'
871
+ return 'contact_array';
533
872
  }
534
873
  else if (message.liveLocationMessage) {
535
- return 'livelocation'
874
+ return 'livelocation';
536
875
  }
537
- else if (message.stickerMessage) {
538
- return 'sticker'
876
+ else if (message.stickerPackMessage) {
877
+ return 'sticker_pack';
539
878
  }
540
879
  else if (message.listMessage) {
541
- return 'list'
880
+ return 'list';
542
881
  }
543
882
  else if (message.listResponseMessage) {
544
- return 'list_response'
883
+ return 'list_response';
545
884
  }
546
885
  else if (message.buttonsResponseMessage) {
547
- return 'buttons_response'
886
+ return 'buttons_response';
548
887
  }
549
888
  else if (message.orderMessage) {
550
- return 'order'
889
+ return 'order';
551
890
  }
552
891
  else if (message.productMessage) {
553
- return 'product'
892
+ return 'product';
554
893
  }
555
894
  else if (message.interactiveResponseMessage) {
556
- return 'native_flow_response'
557
- }
558
- else if (message.groupInviteMessage) {
559
- return 'url'
560
- }
561
- else if (/https:\/\/wa\.me\/p\/\d+\/\d+/.test(message.extendedTextMessage?.text)) {
562
- return 'productlink'
563
- }
564
- }
565
- const getButtonType = (message) => {
566
- if (message.listMessage) {
567
- return 'list'
568
- }
569
- else if (message.buttonsMessage) {
570
- return 'buttons'
571
- }
572
- else if (message.interactiveMessage?.nativeFlowMessage?.buttons?.[0]?.name === 'review_and_pay') {
573
- return 'review_and_pay'
574
- }
575
- else if (message.interactiveMessage?.nativeFlowMessage?.buttons?.[0]?.name === 'review_order') {
576
- return 'review_order'
895
+ return 'native_flow_response';
577
896
  }
578
- else if (message.interactiveMessage?.nativeFlowMessage?.buttons?.[0]?.name === 'payment_info') {
579
- return 'payment_info'
897
+ else if (message.extendedTextMessage?.matchedText || message.groupInviteMessage) {
898
+ return 'url';
580
899
  }
581
- else if (message.interactiveMessage?.nativeFlowMessage?.buttons?.[0]?.name === 'payment_status') {
582
- return 'payment_status'
900
+ // Lia@Note 02-02-26 --- Add more message type here
901
+ else if ((message.extendedTextMessage?.text || message.conversation || '').includes('://wa.me/c/')) {
902
+ return 'cataloglink';
583
903
  }
584
- else if (message.interactiveMessage?.nativeFlowMessage?.buttons?.[0]?.name === 'payment_method') {
585
- return 'payment_method'
904
+ else if ((message.extendedTextMessage?.text || message.conversation || '').includes('://wa.me/p/')) {
905
+ return 'productlink';
586
906
  }
587
- else if (message.interactiveMessage && message.interactiveMessage?.nativeFlowMessage) {
588
- return 'interactive'
589
- }
590
- else if (message.interactiveMessage?.nativeFlowMessage) {
591
- return 'native_flow'
592
- }
593
- }
907
+ return ''
908
+ };
594
909
  const getPrivacyTokens = async (jids) => {
595
- const t = Utils_1.unixTimestampSeconds().toString();
910
+ const t = unixTimestampSeconds().toString();
596
911
  const result = await query({
597
912
  tag: 'iq',
598
913
  attrs: {
599
- to: WABinary_1.S_WHATSAPP_NET,
914
+ to: S_WHATSAPP_NET,
600
915
  type: 'set',
601
916
  xmlns: 'privacy'
602
917
  },
@@ -607,7 +922,7 @@ const makeMessagesSocket = (config) => {
607
922
  content: jids.map(jid => ({
608
923
  tag: 'token',
609
924
  attrs: {
610
- jid: WABinary_1.jidNormalizedUser(jid),
925
+ jid: jidNormalizedUser(jid),
611
926
  t,
612
927
  type: 'trusted_contact'
613
928
  }
@@ -616,10 +931,9 @@ const makeMessagesSocket = (config) => {
616
931
  ]
617
932
  });
618
933
  return result;
619
- }
620
- const waUploadToServer = (0, Utils_1.getWAUploadToServer)(config, refreshMediaConn);
621
- const violeticsHandler = new violetics(Utils_1, waUploadToServer, relayMessage, config, sock);
622
- const waitForMsgMediaUpdate = (0, Utils_1.bindWaitForEvent)(ev, 'messages.media-update');
934
+ };
935
+ const waUploadToServer = getWAUploadToServer(config, refreshMediaConn);
936
+ const waitForMsgMediaUpdate = bindWaitForEvent(ev, 'messages.media-update');
623
937
  return {
624
938
  ...sock,
625
939
  getPrivacyTokens,
@@ -627,23 +941,24 @@ const makeMessagesSocket = (config) => {
627
941
  relayMessage,
628
942
  sendReceipt,
629
943
  sendReceipts,
630
- violeticsHandler,
631
944
  readMessages,
632
945
  refreshMediaConn,
633
- getUSyncDevices,
634
- createParticipantNodes,
635
946
  waUploadToServer,
636
- sendPeerDataOperationMessage,
637
947
  fetchPrivacySettings,
948
+ sendPeerDataOperationMessage,
949
+ createParticipantNodes,
950
+ getUSyncDevices,
951
+ messageRetryManager,
952
+ updateMemberLabel,
638
953
  updateMediaMessage: async (message) => {
639
- const content = (0, Utils_1.assertMediaContent)(message.message);
954
+ const content = assertMediaContent(message.message);
640
955
  const mediaKey = content.mediaKey;
641
956
  const meId = authState.creds.me.id;
642
- const node = (0, Utils_1.encryptMediaRetryRequest)(message.key, mediaKey, meId);
957
+ const node = encryptMediaRetryRequest(message.key, mediaKey, meId);
643
958
  let error = undefined;
644
959
  await Promise.all([
645
960
  sendNode(node),
646
- waitForMsgMediaUpdate(update => {
961
+ waitForMsgMediaUpdate(async (update) => {
647
962
  const result = update.find(c => c.key.id === message.key.id);
648
963
  if (result) {
649
964
  if (result.error) {
@@ -651,13 +966,16 @@ const makeMessagesSocket = (config) => {
651
966
  }
652
967
  else {
653
968
  try {
654
- const media = (0, Utils_1.decryptMediaRetryData)(result.media, mediaKey, result.key.id);
655
- if (media.result !== WAProto_1.proto.MediaRetryNotification.ResultType.SUCCESS) {
656
- const resultStr = WAProto_1.proto.MediaRetryNotification.ResultType[media.result];
657
- throw new boom_1.Boom(`Media re-upload failed by device (${resultStr})`, { data: media, statusCode: (0, Utils_1.getStatusCodeForMediaRetry)(media.result) || 404 });
969
+ const media = decryptMediaRetryData(result.media, mediaKey, result.key.id);
970
+ if (media.result !== proto.MediaRetryNotification.ResultType.SUCCESS) {
971
+ const resultStr = proto.MediaRetryNotification.ResultType[media.result];
972
+ throw new Boom(`Media re-upload failed by device (${resultStr})`, {
973
+ data: media,
974
+ statusCode: getStatusCodeForMediaRetry(media.result) || 404
975
+ });
658
976
  }
659
977
  content.directPath = media.directPath;
660
- content.url = (0, Utils_1.getUrlFromDirectPath)(content.directPath);
978
+ content.url = getUrlFromDirectPath(content.directPath);
661
979
  logger.debug({ directPath: media.directPath, key: result.key }, 'media update successful');
662
980
  }
663
981
  catch (err) {
@@ -671,145 +989,258 @@ const makeMessagesSocket = (config) => {
671
989
  if (error) {
672
990
  throw error;
673
991
  }
674
- ev.emit('messages.update', [
675
- {
676
- key: message.key,
677
- update: {
678
- message: message.message
679
- }
680
- }
681
- ]);
992
+ ev.emit('messages.update', [{ key: message.key, update: { message: message.message } }]);
682
993
  return message;
683
994
  },
684
- setLabelGroup: async (id, text) => {
685
- await relayMessage(id, {
686
- protocolMessage: {
687
- type: 30,
688
- memberLabel: {
689
- label: text.slice(0, 30)
995
+ // vltcs@changes 30-01-26 --- Add support for modifying additionalNodes and additionalAttributes using "options" in sendMessage()
996
+ sendMessage: async (jid, content, options = {}) => {
997
+ const userJid = authState.creds.me.id;
998
+ // vltcs@changes 13-03-26 --- Add status mentions!
999
+ if (Array.isArray(jid)) {
1000
+ const { delayMs = 1500 } = options;
1001
+ const allUsers = new Set();
1002
+ const fullMsg = await generateWAMessage('status@broadcast', content, {
1003
+ logger,
1004
+ userJid,
1005
+ upload: waUploadToServer,
1006
+ mediaCache: config.mediaCache,
1007
+ options: config.options,
1008
+ ...options,
1009
+ messageId: generateMessageIDV2(userJid)
1010
+ });
1011
+ for (const id of jid) {
1012
+ if (isJidGroup(id)) {
1013
+ try {
1014
+ const groupData = (cachedGroupMetadata ? await cachedGroupMetadata(id) : null) || await groupMetadata(id);
1015
+ for (const participant of groupData.participants) {
1016
+ if (allUsers.has(participant.id))
1017
+ continue;
1018
+ allUsers.add(participant.id);
1019
+ }
1020
+ }
1021
+ catch (error) {
1022
+ logger.error(`Error getting metadata group from ${id}: ${error}`);
1023
+ }
1024
+ }
1025
+ else if (!allUsers.has(id)) {
1026
+ allUsers.add(id);
690
1027
  }
691
1028
  }
692
- }, {
693
- additionalNodes: [
694
- {
695
- tag: "meta",
696
- attrs: {
697
- tag_reason: "user_update",
698
- appdata: "member_tag"
1029
+ await relayMessage('status@broadcast', fullMsg.message, {
1030
+ messageId: fullMsg.key.id,
1031
+ statusJidList: Array.from(allUsers),
1032
+ additionalNodes: [
1033
+ {
1034
+ tag: 'meta',
1035
+ attrs: {},
1036
+ content: [
1037
+ {
1038
+ tag: 'mentioned_users',
1039
+ attrs: {},
1040
+ content: jid.map(id => ({
1041
+ tag: 'to',
1042
+ attrs: { jid: id },
1043
+ content: undefined
1044
+ }))
1045
+ }
1046
+ ]
1047
+ }
1048
+ ]
1049
+ });
1050
+ if (config.emitOwnEvents) {
1051
+ process.nextTick(async () => {
1052
+ await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
1053
+ });
1054
+ }
1055
+ for (const id of jid) {
1056
+ const isGroup = isJidGroup(id)
1057
+ const sendType = isGroup ? 'groupStatusMentionMessage' : 'statusMentionMessage';
1058
+ const mentionMsg = generateWAMessageFromContent(id, {
1059
+ messageContextInfo: {
1060
+ messageSecret: randomBytes(32)
699
1061
  },
700
- content: undefined
1062
+ [sendType]: {
1063
+ message: {
1064
+ protocolMessage: {
1065
+ key: fullMsg.key,
1066
+ type: 25
1067
+ }
1068
+ }
1069
+ }
1070
+ }, {
1071
+ userJid
1072
+ });
1073
+ await relayMessage(id, mentionMsg.message, {
1074
+ additionalNodes: [
1075
+ {
1076
+ tag: 'meta',
1077
+ attrs: isGroup ?
1078
+ { is_group_status_mention: 'true' } :
1079
+ { is_status_mention: 'true' },
1080
+ content: undefined
1081
+ }
1082
+ ]
1083
+ });
1084
+ if (config.emitOwnEvents) {
1085
+ process.nextTick(async () => {
1086
+ await messageMutex.mutex(() => upsertMessage(mentionMsg, 'append'));
1087
+ });
701
1088
  }
702
- ]
703
- })
704
- },
705
- // Violetics Direct Exports
706
- sendStatusWhatsApp: async (jids, content, options = {}) => violeticsHandler.sendStatusWhatsApp(content, jids),
707
- handleEvent: async (jid, content, options = {}) => violeticsHandler.handleEvent(content, jid, options.quoted),
708
- handleAlbum: async (jid, content, options = {}) => violeticsHandler.handleAlbum(content, jid, options.quoted),
709
- handleInteractive: async (jid, content, options = {}) => violeticsHandler.handleInteractive(content, jid, options.quoted),
710
- handleProduct: async (jid, content, options = {}) => violeticsHandler.handleProduct(content, jid, options.quoted),
711
- handlePayment: async (jid, content, options = {}) => violeticsHandler.handlePayment(content, jid, options.quoted),
712
- handleGroupStory: async (jid, content, options = {}) => violeticsHandler.handleGroupStory(content, jid, options.quoted),
713
- handlePollResult: async (jid, content, options = {}) => violeticsHandler.handlePollResult(content, jid, options.quoted),
714
-
715
-
716
- sendMessage: async (jid, content, options = {}) => {
717
- const userJid = authState.creds.me.id;
718
- delete options.ephemeralExpiration
719
- const { filter = false, quoted } = options;
720
- const getParticipantAttr = () => filter ? { participant: { jid } } : {};
721
- const messageType = violeticsHandler.detectType(content);
722
- if (typeof content === 'object' && 'disappearingMessagesInChat' in content &&
723
- typeof content['disappearingMessagesInChat'] !== 'undefined' && WABinary_1.isJidGroup(jid)) {
724
- const { disappearingMessagesInChat } = content
725
- const value = typeof disappearingMessagesInChat === 'boolean' ?
726
- (disappearingMessagesInChat ? Defaults_1.WA_DEFAULT_EPHEMERAL : 0) :
727
- disappearingMessagesInChat
728
- await groupToggleEphemeral(jid, value)
1089
+ await delay(delayMs);
1090
+ }
1091
+ return fullMsg;
1092
+ }
1093
+ else if ('disappearingMessagesInChat' in content && isJidGroup(jid)) {
1094
+ const { disappearingMessagesInChat } = content;
1095
+ const value = typeof disappearingMessagesInChat === 'boolean'
1096
+ ? disappearingMessagesInChat
1097
+ ? WA_DEFAULT_EPHEMERAL
1098
+ : 0
1099
+ : disappearingMessagesInChat;
1100
+ await groupToggleEphemeral(jid, value);
729
1101
  }
730
1102
  else {
731
- let mediaHandle
732
- if (messageType) {
733
- switch (messageType) {
734
- case 'PAYMENT':
735
- const paymentContent = await violeticsHandler.handlePayment(content, quoted);
736
- return await relayMessage(jid, paymentContent, {
737
- messageId: Utils_1.generateMessageID(),
738
- ...getParticipantAttr()
739
- });
740
- case 'PRODUCT':
741
- const productContent = await violeticsHandler.handleProduct(content, jid, quoted);
742
- const productMsg = await Utils_1.generateWAMessageFromContent(jid, productContent, { quoted });
743
- return await relayMessage(jid, productMsg.message, {
744
- messageId: productMsg.key.id,
745
- ...getParticipantAttr()
746
- });
747
- case 'INTERACTIVE':
748
- const interactiveContent = await violeticsHandler.handleInteractive(content, jid, quoted);
749
- const interactiveMsg = await Utils_1.generateWAMessageFromContent(jid, interactiveContent, { quoted });
750
- return await relayMessage(jid, interactiveMsg.message, {
751
- messageId: interactiveMsg.key.id,
752
- ...getParticipantAttr()
753
- });
754
- case 'ALBUM':
755
- return await violeticsHandler.handleAlbum(content, jid, quoted)
756
- case 'EVENT':
757
- return await violeticsHandler.handleEvent(content, jid, quoted)
758
- case 'POLL_RESULT':
759
- return await violeticsHandler.handlePollResult(content, jid, quoted)
760
- case 'GROUP_STORY':
761
- return await violeticsHandler.handleGroupStory(content, jid, quoted)
762
- }
763
- }
764
- const fullMsg = await Utils_1.generateWAMessage(jid, content, {
1103
+ const fullMsg = await generateWAMessage(jid, content, {
765
1104
  logger,
766
1105
  userJid,
767
- quoted,
768
- getUrlInfo: text => link_preview_1.getUrlInfo(text, {
1106
+ getUrlInfo: text => getUrlInfo(text, {
769
1107
  thumbnailWidth: linkPreviewImageThumbnailWidth,
770
1108
  fetchOpts: {
771
1109
  timeout: 3000,
772
- ...axiosOptions || {}
1110
+ ...(httpRequestOptions || {})
773
1111
  },
774
1112
  logger,
775
1113
  uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined
776
1114
  }),
777
- upload: async (readStream, opts) => {
778
- const up = await waUploadToServer(readStream, {
779
- ...opts,
780
- newsletter: WABinary_1.isJidNewsLetter(jid)
781
- });
782
- return up;
783
- },
1115
+ //TODO: CACHE
1116
+ getProfilePicUrl: sock.profilePictureUrl,
1117
+ getCallLink: sock.createCallLink,
1118
+ upload: waUploadToServer,
784
1119
  mediaCache: config.mediaCache,
785
1120
  options: config.options,
786
- ...options
1121
+ ...options,
1122
+ messageId: generateMessageIDV2(userJid)
787
1123
  });
1124
+ const isNewsletter = isJidNewsletter(jid);
1125
+ const isEventMsg = 'event' in content && !!content.event;
788
1126
  const isDeleteMsg = 'delete' in content && !!content.delete;
789
1127
  const isEditMsg = 'edit' in content && !!content.edit;
790
- const additionalAttributes = {};
791
- if (isDeleteMsg) {
792
- const fromMe = content.delete?.fromMe;
793
- const isGroup = WABinary_1.isJidGroup(content.delete?.remoteJid);
794
- additionalAttributes.edit = (isGroup && !fromMe) || WABinary_1.isJidNewsLetter(jid) ? '8' : '7';
795
- } else if (isEditMsg) {
796
- additionalAttributes.edit = WABinary_1.isJidNewsLetter(jid) ? '3' : '1';
1128
+ const isPinMsg = 'pin' in content && !!content.pin;
1129
+ const isKeepMsg = 'keep' in content && !!content.keep;
1130
+ const isPollMsg = 'poll' in content && !!content.poll;
1131
+ const isQuizMsg = 'poll' in content && !!content.poll.pollType;
1132
+ const isPollResultMsg = 'pollResult' in content && !!content.pollResult;
1133
+ const isPollUpdateMsg = 'pollUpdate' in content && !!content.pollUpdate;
1134
+ const isAiMsg = 'ai' in content && !!content.ai;
1135
+ const additionalAttributes = options.additionalAttributes || {};
1136
+ const additionalNodes = options.additionalNodes || [];
1137
+ // required for delete
1138
+ if (isDeleteMsg || isKeepMsg) {
1139
+ // if the chat is a group, and I am not the author, then delete the message as an admin
1140
+ if (isJidGroup(content.delete?.remoteJid) && !content.delete?.fromMe) {
1141
+ additionalAttributes.edit = '8';
1142
+ }
1143
+ else {
1144
+ additionalAttributes.edit = '7';
1145
+ }
1146
+ }
1147
+ else if (isEditMsg) {
1148
+ additionalAttributes.edit = isNewsletter ? '3' : '1';
1149
+ }
1150
+ else if (isPinMsg) {
1151
+ additionalAttributes.edit = '2';
1152
+ }
1153
+ else if (isPollMsg) {
1154
+ if (!isJidNewsletter(jid) && isQuizMsg) {
1155
+ throw new Boom('Quiz are only allowed for newsletter', { statusCode: 400 });
1156
+ }
1157
+ additionalNodes.push({
1158
+ tag: 'meta',
1159
+ attrs: {
1160
+ // Lia@Note 08-02-26 --- Still a hypothesis regarding PollResult ༎ຶ⁠‿⁠༎ຶ
1161
+ polltype: isQuizMsg ? 'quiz_creation' : isPollResultMsg || isPollUpdateMsg ? 'vote' : 'creation',
1162
+ contenttype: isPollMsg && isNewsletter ? 'text' : undefined
1163
+ },
1164
+ content: undefined
1165
+ });
1166
+ }
1167
+ else if (isEventMsg) {
1168
+ additionalNodes.push({
1169
+ tag: 'meta',
1170
+ attrs: {
1171
+ event_type: 'creation'
1172
+ },
1173
+ content: undefined
1174
+ });
1175
+ }
1176
+ // vltcs@changes 30-01-26 --- Add support for AI label in message when "ai" is true, but works only in private chat
1177
+ else if (isAiMsg) {
1178
+ if (!(isPnUser(jid) || isLidUser(jid))) {
1179
+ throw new Boom('AI labeled message are only allowed in private chat', { statusCode: 400 });
1180
+ }
1181
+ if ('messageContextInfo' in fullMsg.message && !!fullMsg.message.messageContextInfo) {
1182
+ fullMsg.message.messageContextInfo.supportPayload = BIZ_BOT_SUPPORT_PAYLOAD;
1183
+ };
1184
+ additionalNodes.push({
1185
+ tag: 'bot',
1186
+ attrs: {
1187
+ biz_bot: '1'
1188
+ },
1189
+ content: undefined
1190
+ });
1191
+ delete content.ai;
797
1192
  }
798
1193
  await relayMessage(jid, fullMsg.message, {
799
1194
  messageId: fullMsg.key.id,
800
- cachedGroupMetadata: options.cachedGroupMetadata,
801
- additionalNodes: options.additionalNodes,
1195
+ useCachedGroupMetadata: options.useCachedGroupMetadata,
1196
+ statusJidList: options.statusJidList,
802
1197
  additionalAttributes,
803
- statusJidList: options.statusJidList
1198
+ additionalNodes
804
1199
  });
805
1200
  if (config.emitOwnEvents) {
806
- process.nextTick(() => {
807
- processingMutex.mutex(() => upsertMessage(fullMsg, 'append'));
1201
+ process.nextTick(async () => {
1202
+ await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
808
1203
  });
809
1204
  }
1205
+ // vltcs@changes 31-01-26 --- Add support for album messages
1206
+ // Lia@Note 06-02-26 --- Refactored to reduce high RSS usage (⁠╥⁠﹏⁠╥⁠)
1207
+ if ('album' in content) {
1208
+ const { delayMs = 1500 } = options;
1209
+ for (const albumMedia of content.album) {
1210
+ const albumMsg = await generateWAMessage(jid, albumMedia, {
1211
+ logger,
1212
+ userJid,
1213
+ upload: waUploadToServer,
1214
+ mediaCache: config.mediaCache,
1215
+ options: config.options,
1216
+ ...options,
1217
+ messageId: generateMessageIDV2(userJid)
1218
+ });
1219
+ if (!hasValidAlbumMedia(normalizeMessageContent(albumMsg.message))) {
1220
+ throw new Boom('Invalid message type for album', { statusCode: 400 });
1221
+ }
1222
+ albumMsg.message.messageContextInfo ||= {};
1223
+ albumMsg.message.messageContextInfo.messageAssociation = {
1224
+ parentMessageKey: fullMsg.key,
1225
+ associationType: AssociationType.MEDIA_ALBUM
1226
+ };
1227
+ await relayMessage(jid, albumMsg.message, {
1228
+ messageId: albumMsg.key.id,
1229
+ useCachedGroupMetadata: options.useCachedGroupMetadata,
1230
+ statusJidList: options.statusJidList,
1231
+ additionalAttributes,
1232
+ additionalNodes
1233
+ });
1234
+ if (config.emitOwnEvents) {
1235
+ process.nextTick(async () => {
1236
+ await messageMutex.mutex(() => upsertMessage(albumMsg, 'append'));
1237
+ });
1238
+ }
1239
+ await delay(delayMs);
1240
+ }
1241
+ }
810
1242
  return fullMsg;
811
1243
  }
812
1244
  }
813
- }
814
- };
815
- exports.makeMessagesSocket = makeMessagesSocket;
1245
+ };
1246
+ };