@realvare/based 2.7.61 → 2.7.70

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 (42) hide show
  1. package/README.MD +25 -114
  2. package/WAProto/WAProto.proto +1073 -244
  3. package/WAProto/index.d.ts +16282 -8183
  4. package/WAProto/index.js +76605 -50628
  5. package/engine-requirements.js +10 -10
  6. package/lib/Defaults/baileys-version.json +1 -1
  7. package/lib/Defaults/index.d.ts +1 -1
  8. package/lib/Defaults/index.js +5 -5
  9. package/lib/Socket/chats.js +53 -16
  10. package/lib/Socket/groups.js +53 -9
  11. package/lib/Socket/messages-recv.js +1298 -1237
  12. package/lib/Socket/messages-send.js +350 -456
  13. package/lib/Socket/socket.js +1 -1
  14. package/lib/Socket/usync.js +57 -4
  15. package/lib/Store/make-in-memory-store.js +28 -15
  16. package/lib/Types/Message.d.ts +316 -6
  17. package/lib/Types/Message.js +1 -1
  18. package/lib/Utils/cache-manager.d.ts +16 -0
  19. package/lib/Utils/cache-manager.js +20 -4
  20. package/lib/Utils/chat-utils.js +17 -13
  21. package/lib/Utils/decode-wa-message.js +1 -11
  22. package/lib/Utils/event-buffer.js +103 -2
  23. package/lib/Utils/generics.js +4 -5
  24. package/lib/Utils/index.d.ts +5 -0
  25. package/lib/Utils/index.js +3 -0
  26. package/lib/Utils/jid-validation.d.ts +2 -0
  27. package/lib/Utils/jid-validation.js +36 -5
  28. package/lib/Utils/link-preview.js +38 -28
  29. package/lib/Utils/messages-media.js +21 -52
  30. package/lib/Utils/messages.js +540 -23
  31. package/lib/Utils/performance-config.d.ts +2 -0
  32. package/lib/Utils/performance-config.js +16 -7
  33. package/lib/Utils/process-message.js +124 -12
  34. package/lib/Utils/rate-limiter.js +15 -20
  35. package/lib/WABinary/jid-utils.js +257 -5
  36. package/lib/WAUSync/Protocols/USyncContactProtocol.js +75 -5
  37. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +59 -6
  38. package/lib/WAUSync/USyncQuery.js +64 -6
  39. package/lib/index.d.ts +1 -0
  40. package/lib/index.js +5 -2
  41. package/package.json +7 -13
  42. package/WAProto/index.ts.ts +0 -53473
@@ -7,6 +7,7 @@ exports.makeMessagesSocket = void 0;
7
7
  const boom_1 = require("@hapi/boom");
8
8
  const node_cache_1 = __importDefault(require("@cacheable/node-cache"));
9
9
  const crypto_1 = require("crypto");
10
+ const AbortController = require("abort-controller");
10
11
  const WAProto_1 = require("../../WAProto");
11
12
  const Defaults_1 = require("../Defaults");
12
13
  const Utils_1 = require("../Utils");
@@ -15,7 +16,6 @@ const link_preview_1 = require("../Utils/link-preview");
15
16
  const WABinary_1 = require("../WABinary");
16
17
  const WAUSync_1 = require("../WAUSync");
17
18
  const newsletter_1 = require("./newsletter");
18
- const rate_limiter_1 = __importDefault(require("../Utils/rate-limiter"));
19
19
  const makeMessagesSocket = (config) => {
20
20
  const { logger, linkPreviewImageThumbnailWidth, generateHighQualityLinkPreview, options: axiosOptions, patchMessageBeforeSending, cachedGroupMetadata, } = config;
21
21
  const sock = (0, newsletter_1.makeNewsletterSocket)(config);
@@ -24,13 +24,6 @@ const makeMessagesSocket = (config) => {
24
24
  stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.USER_DEVICES, // 5 minutes
25
25
  useClones: false
26
26
  });
27
-
28
- // Initialize rate limiter for anti-ban protection
29
- const messagesSendRate = config.messagesSendRate || 1;
30
- if(messagesSendRate > 5) {
31
- logger.warn(`messagesSendRate is set to a high value (${messagesSendRate}), this may increase the risk of getting banned. Recommended value is <= 3`);
32
- }
33
- const rateLimiter = new rate_limiter_1.default(messagesSendRate);
34
27
  let mediaConn;
35
28
  const refreshMediaConn = async (forceGet = false) => {
36
29
  const media = await mediaConn;
@@ -169,11 +162,61 @@ const makeMessagesSocket = (config) => {
169
162
  }
170
163
  return deviceResults;
171
164
  };
165
+ // Cache to track JIDs that have failed session fetching to prevent infinite loops
166
+ const failedSessionFetchCache = new Map();
167
+ const FAILED_SESSION_CACHE_TTL = 5 * 60 * 1000; // 5 minutes
168
+
169
+ // Cache to track recently sent messages to prevent duplicate sends
170
+ const recentlySentMessagesCache = new node_cache_1.default({
171
+ stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
172
+ useClones: false,
173
+ maxKeys: 1000 // Limit to prevent memory issues
174
+ });
175
+
176
+ // Cleanup function to remove expired entries from the cache
177
+ const cleanupFailedSessionCache = () => {
178
+ const now = Date.now();
179
+ for (const [jid, failureTime] of failedSessionFetchCache.entries()) {
180
+ if (now - failureTime >= FAILED_SESSION_CACHE_TTL) {
181
+ failedSessionFetchCache.delete(jid);
182
+ }
183
+ }
184
+ };
185
+
186
+ // Run cleanup every 5 minutes
187
+ const cleanupInterval = setInterval(cleanupFailedSessionCache, 5 * 60 * 1000);
188
+
189
+ // Helper function to check if message was recently sent
190
+ const wasMessageRecentlySent = (msgId, jid) => {
191
+ const cacheKey = `${msgId}:${jid}`;
192
+ return recentlySentMessagesCache.has(cacheKey);
193
+ };
194
+
195
+ // Helper function to mark message as recently sent
196
+ const markMessageAsSent = (msgId, jid) => {
197
+ const cacheKey = `${msgId}:${jid}`;
198
+ recentlySentMessagesCache.set(cacheKey, true);
199
+ };
200
+
172
201
  const assertSessions = async (jids, force) => {
173
202
  let didFetchNewSession = false;
174
203
  let jidsRequiringFetch = [];
175
204
  if (force) {
176
- jidsRequiringFetch = jids;
205
+ // Filter out JIDs that have recently failed session fetching
206
+ jidsRequiringFetch = jids.filter(jid => {
207
+ const failureTime = failedSessionFetchCache.get(jid);
208
+ if (failureTime && (Date.now() - failureTime) < FAILED_SESSION_CACHE_TTL) {
209
+ logger.debug({ jid }, 'skipping session fetch for recently failed JID');
210
+ return false;
211
+ }
212
+ return true;
213
+ });
214
+
215
+ // If all JIDs are filtered out, return early without attempting fetch
216
+ if (jidsRequiringFetch.length === 0 && jids.length > 0) {
217
+ logger.debug({ originalJids: jids }, 'all JIDs recently failed, skipping session fetch entirely');
218
+ return didFetchNewSession;
219
+ }
177
220
  }
178
221
  else {
179
222
  const addrs = jids.map(jid => (signalRepository
@@ -183,57 +226,107 @@ const makeMessagesSocket = (config) => {
183
226
  const signalId = signalRepository
184
227
  .jidToSignalProtocolAddress(jid);
185
228
  if (!sessions[signalId]) {
186
- jidsRequiringFetch.push(jid);
229
+ // Also check if this JID recently failed
230
+ const failureTime = failedSessionFetchCache.get(jid);
231
+ if (!failureTime || (Date.now() - failureTime) >= FAILED_SESSION_CACHE_TTL) {
232
+ jidsRequiringFetch.push(jid);
233
+ }
187
234
  }
188
235
  }
189
236
  }
190
237
  if (jidsRequiringFetch.length) {
191
238
  logger.debug({ jidsRequiringFetch }, 'fetching sessions');
192
- const result = await query({
193
- tag: 'iq',
194
- attrs: {
195
- xmlns: 'encrypt',
196
- type: 'get',
197
- to: WABinary_1.S_WHATSAPP_NET,
198
- },
199
- content: [
200
- {
201
- tag: 'key',
202
- attrs: {},
203
- content: jidsRequiringFetch.map(jid => ({
204
- tag: 'user',
205
- attrs: { jid },
206
- }))
239
+ const TOTAL_TIMEOUT_MS = 120000; // 120 seconds
240
+ const abortController = new AbortController();
241
+ const timeout = setTimeout(() => abortController.abort(), TOTAL_TIMEOUT_MS);
242
+ try {
243
+ const BATCH_SIZE = 50;
244
+ for (let i = 0; i < jidsRequiringFetch.length; i += BATCH_SIZE) {
245
+ const batch = jidsRequiringFetch.slice(i, i + BATCH_SIZE);
246
+ try {
247
+ const result = await (0, retry_1.retryWithBackoff)(() => query({
248
+ tag: 'iq',
249
+ attrs: {
250
+ xmlns: 'encrypt',
251
+ type: 'get',
252
+ to: WABinary_1.S_WHATSAPP_NET,
253
+ },
254
+ content: [
255
+ {
256
+ tag: 'key',
257
+ attrs: {},
258
+ content: batch.map(jid => ({
259
+ tag: 'user',
260
+ attrs: { jid },
261
+ }))
262
+ }
263
+ ]
264
+ }), {
265
+ retries: 4,
266
+ baseMs: 2000,
267
+ maxMs: 10000,
268
+ jitter: true,
269
+ timeoutPerAttemptMs: 25000,
270
+ shouldRetry: (err) => {
271
+ var _a;
272
+ const status = ((_a = err.output) === null || _a === void 0 ? void 0 : _a.statusCode) || (err === null || err === void 0 ? void 0 : err.statusCode);
273
+ // Don't retry "not-acceptable" (406) errors as they indicate permission issues
274
+ // Don't retry aborted requests as they were intentionally cancelled
275
+ if (status === 406 || err.message === 'aborted' || err.code === 'ABORT_ERR') {
276
+ return false;
277
+ }
278
+ return !status || (status >= 500 || status === 408 || status === 429) || err.message.includes('WebSocket is not open');
279
+ },
280
+ onRetry: (err, n) => logger === null || logger === void 0 ? void 0 : logger.warn({ err, attempt: n }, 'retrying fetch sessions'),
281
+ signal: abortController.signal
282
+ });
283
+ await (0, Utils_1.parseAndInjectE2ESessions)(result, signalRepository);
284
+ didFetchNewSession = true;
285
+ } catch (err) {
286
+ // Cache failed JIDs to prevent infinite retries
287
+ logger.warn({ err, batch }, 'session fetch failed for batch, caching failed JIDs');
288
+ for (const jid of batch) {
289
+ failedSessionFetchCache.set(jid, Date.now());
290
+ }
291
+ // Re-throw the error so the caller knows the fetch failed
292
+ throw err;
207
293
  }
208
- ]
209
- });
210
- await (0, Utils_1.parseAndInjectE2ESessions)(result, signalRepository);
211
- didFetchNewSession = true;
294
+ }
295
+ } finally {
296
+ clearTimeout(timeout);
297
+ }
212
298
  }
213
299
  return didFetchNewSession;
214
300
  };
215
- const sendPeerDataOperationMessage = async (pdoMessage) => {
301
+ const sendPeerMessage = async (protocolMessageContent, options = {}) => {
216
302
  var _a;
217
- //TODO: for later, abstract the logic to send a Peer Message instead of just PDO - useful for App State Key Resync with phone
218
303
  if (!((_a = authState.creds.me) === null || _a === void 0 ? void 0 : _a.id)) {
219
304
  throw new boom_1.Boom('Not authenticated');
220
305
  }
306
+
221
307
  const protocolMessage = {
222
- protocolMessage: {
223
- peerDataOperationRequestMessage: pdoMessage,
224
- type: WAProto_1.proto.Message.ProtocolMessage.Type.PEER_DATA_OPERATION_REQUEST_MESSAGE
225
- }
308
+ protocolMessage: protocolMessageContent
226
309
  };
310
+
227
311
  const meJid = (0, WABinary_1.jidNormalizedUser)(authState.creds.me.id);
228
312
  const msgId = await relayMessage(meJid, protocolMessage, {
229
313
  additionalAttributes: {
230
314
  category: 'peer',
231
315
  // eslint-disable-next-line camelcase
232
316
  push_priority: 'high_force',
317
+ ...options.additionalAttributes
233
318
  },
319
+ ...options
234
320
  });
235
321
  return msgId;
236
322
  };
323
+
324
+ const sendPeerDataOperationMessage = async (pdoMessage) => {
325
+ return sendPeerMessage({
326
+ peerDataOperationRequestMessage: pdoMessage,
327
+ type: WAProto_1.proto.Message.ProtocolMessage.Type.PEER_DATA_OPERATION_REQUEST_MESSAGE
328
+ });
329
+ };
237
330
  const createParticipantNodes = async (jids, message, extraAttrs) => {
238
331
  let patched = await patchMessageBeforeSending(message, jids);
239
332
  if (!Array.isArray(patched)) {
@@ -279,6 +372,13 @@ const makeMessagesSocket = (config) => {
279
372
  const isStatus = jid === statusJid;
280
373
  const isLid = server === 'lid';
281
374
  msgId = msgId || (0, Utils_1.generateMessageIDV2)((_a = sock.user) === null || _a === void 0 ? void 0 : _a.id);
375
+
376
+ // Check if this message was recently sent to prevent duplicate sends
377
+ if (wasMessageRecentlySent(msgId, jid)) {
378
+ logger.debug({ msgId, jid }, 'message recently sent, skipping duplicate send');
379
+ return msgId;
380
+ }
381
+
282
382
  useUserDevicesCache = useUserDevicesCache !== false;
283
383
  useCachedGroupMetadata = useCachedGroupMetadata !== false && !isStatus;
284
384
  const participants = [];
@@ -549,9 +649,9 @@ const makeMessagesSocket = (config) => {
549
649
  }
550
650
  logger.debug({ msgId }, `sending message to ${participants.length} devices`);
551
651
  await (0, retry_1.retryWithBackoff)(({ signal }) => sendNode(stanza, { signal }), {
552
- retries: 3,
553
- baseMs: 300,
554
- maxMs: 4000,
652
+ retries: 2, // Riduci i tentativi
653
+ baseMs: 100, // Riduci l'attesa iniziale
654
+ maxMs: 2000, // Riduci l'attesa massima
555
655
  jitter: true,
556
656
  timeoutPerAttemptMs: 5000,
557
657
  shouldRetry: (err) => {
@@ -561,6 +661,9 @@ const makeMessagesSocket = (config) => {
561
661
  },
562
662
  onRetry: (err, n) => logger?.warn?.({ err, attempt: n }, 'retrying sendNode')
563
663
  });
664
+
665
+ // Mark message as successfully sent to prevent duplicate sends
666
+ markMessageAsSent(msgId, jid);
564
667
  });
565
668
  return msgId;
566
669
  };
@@ -666,205 +769,170 @@ const makeMessagesSocket = (config) => {
666
769
  });
667
770
  return result;
668
771
  };
669
- const waUploadToServer = (0, Utils_1.getWAUploadToServer)(config, refreshMediaConn);
670
- const waitForMsgMediaUpdate = (0, Utils_1.bindWaitForEvent)(ev, 'messages.media-update');
671
-
672
- // Helper function to send message without admin-only logic
673
- const sendMessageInternal = async (jid, content, options = {}) => {
674
- var _a, _b, _c;
675
- const userJid = authState.creds.me.id;
676
-
677
- if (!options.ephemeralExpiration) {
678
- if ((0, WABinary_1.isJidGroup)(jid)) {
679
- const groups = await sock.groupQuery(jid, 'get', [{
680
- tag: 'query',
681
- attrs: {
682
- request: 'interactive'
683
- }
684
- }]);
685
- const metadata = (0, WABinary_1.getBinaryNodeChild)(groups, 'group');
686
- const expiration = ((_b = (_a = (0, WABinary_1.getBinaryNodeChild)(metadata, 'ephemeral')) === null || _a === void 0 ? void 0 : _a.attrs) === null || _b === void 0 ? void 0 : _b.expiration) || 0;
687
- options.ephemeralExpiration = expiration;
688
- }
689
- }
690
- if (typeof content === 'object' &&
691
- 'disappearingMessagesInChat' in content &&
692
- typeof content['disappearingMessagesInChat'] !== 'undefined' &&
693
- (0, WABinary_1.isJidGroup)(jid)) {
694
- const { disappearingMessagesInChat } = content;
695
- const value = typeof disappearingMessagesInChat === 'boolean' ?
696
- (disappearingMessagesInChat ? Defaults_1.WA_DEFAULT_EPHEMERAL : 0) :
697
- disappearingMessagesInChat;
698
- await groupToggleEphemeral(jid, value);
699
- }
700
- if (typeof content === 'object' && 'album' in content && content.album) {
701
- const { album, caption } = content;
702
- if (caption && !album[0].caption) {
703
- album[0].caption = caption;
704
- }
705
- let mediaHandle;
706
- let mediaMsg;
707
- const albumMsg = (0, Utils_1.generateWAMessageFromContent)(jid, {
708
- albumMessage: {
709
- expectedImageCount: album.filter(item => 'image' in item).length,
710
- expectedVideoCount: album.filter(item => 'video' in item).length
711
- }
712
- }, { userJid, ...options });
713
- await relayMessage(jid, albumMsg.message, {
714
- messageId: albumMsg.key.id
715
- });
716
- for (const i in album) {
717
- const media = album[i];
718
- if ('image' in media) {
719
- mediaMsg = await (0, Utils_1.generateWAMessage)(jid, {
720
- image: media.image,
721
- ...(media.caption ? { caption: media.caption } : {}),
722
- ...options
723
- }, {
724
- userJid,
725
- upload: async (readStream, opts) => {
726
- const up = await waUploadToServer(readStream, { ...opts, newsletter: (0, WABinary_1.isJidNewsletter)(jid) });
727
- mediaHandle = up.handle;
728
- return up;
729
- },
730
- ...options,
731
- });
772
+ const waUploadToServer = (0, Utils_1.getWAUploadToServer)(config, refreshMediaConn);
773
+ const waitForMsgMediaUpdate = (0, Utils_1.bindWaitForEvent)(ev, 'messages.media-update');
774
+ return {
775
+ ...sock,
776
+ getPrivacyTokens,
777
+ assertSessions,
778
+ relayMessage,
779
+ sendReceipt,
780
+ sendReceipts,
781
+ readMessages,
782
+ sendPeerDataOperationMessage,
783
+ sendPeerMessage,
784
+ getUSyncDevices,
785
+ getFailedSessionCache: () => failedSessionFetchCache,
786
+ getFailedSessionCacheTTL: () => FAILED_SESSION_CACHE_TTL,
787
+ getRecentlySentMessagesCache: () => recentlySentMessagesCache,
788
+ wasMessageRecentlySent,
789
+ markMessageAsSent,
790
+ sendMessage: async (jid, content, options = {}) => {
791
+ var _a, _b, _c;
792
+ const userJid = authState.creds.me.id;
793
+ if (!options.ephemeralExpiration) {
794
+ if ((0, WABinary_1.isJidGroup)(jid)) {
795
+ const groups = await sock.groupQuery(jid, 'get', [{
796
+ tag: 'query',
797
+ attrs: {
798
+ request: 'interactive'
799
+ }
800
+ }]);
801
+ const metadata = (0, WABinary_1.getBinaryNodeChild)(groups, 'group');
802
+ const expiration = ((_b = (_a = (0, WABinary_1.getBinaryNodeChild)(metadata, 'ephemeral')) === null || _a === void 0 ? void 0 : _a.attrs) === null || _b === void 0 ? void 0 : _b.expiration) || 0;
803
+ options.ephemeralExpiration = expiration;
804
+ }
805
+ }
806
+ if (typeof content === 'object' &&
807
+ 'disappearingMessagesInChat' in content &&
808
+ typeof content['disappearingMessagesInChat'] !== 'undefined' &&
809
+ (0, WABinary_1.isJidGroup)(jid)) {
810
+ const { disappearingMessagesInChat } = content;
811
+ const value = typeof disappearingMessagesInChat === 'boolean' ?
812
+ (disappearingMessagesInChat ? Defaults_1.WA_DEFAULT_EPHEMERAL : 0) :
813
+ disappearingMessagesInChat;
814
+ await groupToggleEphemeral(jid, value);
815
+ }
816
+ // Handle pin messages
817
+ if (typeof content === 'object' && 'pin' in content && content.pin) {
818
+ const pinData = typeof content.pin === 'object' ? content.pin : { key: content.pin };
819
+ // Map type: 1 = PIN_FOR_ALL, 2 = UNPIN_FOR_ALL
820
+ const pinType = pinData.type !== undefined ? pinData.type : (content.type !== undefined ? content.type : WAProto_1.proto.Message.PinInChatMessage.Type.PIN_FOR_ALL);
821
+ const msgId = (0, Utils_1.generateMessageIDV2)((_c = sock.user) === null || _c === void 0 ? void 0 : _c.id);
822
+ const pinMessage = {
823
+ pinInChatMessage: {
824
+ key: pinData.key,
825
+ type: pinType,
826
+ senderTimestampMs: Date.now()
827
+ }
828
+ };
829
+ // Add messageContextInfo only for PIN (type 1), not for UNPIN (type 2)
830
+ if (pinType === WAProto_1.proto.Message.PinInChatMessage.Type.PIN_FOR_ALL) {
831
+ pinMessage.messageContextInfo = {
832
+ messageAddOnDurationInSecs: pinData.time || content.time || 86400, // Default 24 hours
833
+ messageAddOnExpiryType: WAProto_1.proto.MessageContextInfo.MessageAddonExpiryType.STATIC
834
+ };
732
835
  }
733
- else if ('video' in media) {
734
- mediaMsg = await (0, Utils_1.generateWAMessage)(jid, {
735
- video: media.video,
736
- ...(media.caption ? { caption: media.caption } : {}),
737
- ...(media.gifPlayback !== undefined ? { gifPlayback: media.gifPlayback } : {}),
738
- ...options
739
- }, {
740
- userJid,
741
- upload: async (readStream, opts) => {
742
- const up = await waUploadToServer(readStream, { ...opts, newsletter: (0, WABinary_1.isJidNewsletter)(jid) });
743
- mediaHandle = up.handle;
744
- return up;
745
- },
746
- ...options,
836
+ const fullMsg = {
837
+ key: {
838
+ remoteJid: jid,
839
+ fromMe: true,
840
+ id: msgId,
841
+ participant: userJid
842
+ },
843
+ message: pinMessage,
844
+ messageTimestamp: (0, Utils_1.unixTimestampSeconds)()
845
+ };
846
+ await relayMessage(jid, fullMsg.message, { messageId: fullMsg.key.id, useCachedGroupMetadata: options.useCachedGroupMetadata });
847
+ if (config.emitOwnEvents) {
848
+ process.nextTick(() => {
849
+ processingMutex.mutex(() => (upsertMessage(fullMsg, 'append')));
747
850
  });
748
851
  }
749
- else if ('url' in media) {
750
- // Assume URL is an image if not specified
751
- mediaMsg = await (0, Utils_1.generateWAMessage)(jid, {
752
- image: media.url,
753
- ...(media.caption ? { caption: media.caption } : {}),
754
- ...options
755
- }, {
756
- userJid,
757
- upload: async (readStream, opts) => {
758
- const up = await waUploadToServer(readStream, { ...opts, newsletter: (0, WABinary_1.isJidNewsletter)(jid) });
759
- mediaHandle = up.handle;
760
- return up;
761
- },
762
- ...options,
763
- });
852
+ return fullMsg;
853
+ }
854
+ if (typeof content === 'object' && 'album' in content && content.album) {
855
+ const { album, caption } = content;
856
+ if (caption && !album[0].caption) {
857
+ album[0].caption = caption;
764
858
  }
765
- if (mediaMsg) {
766
- mediaMsg.message.messageContextInfo = {
767
- messageSecret: (0, crypto_1.randomBytes)(32),
768
- messageAssociation: {
769
- associationType: 1,
770
- parentMessageKey: albumMsg.key
771
- }
772
- };
859
+ let mediaHandle;
860
+ let mediaMsg;
861
+ const albumMsg = (0, Utils_1.generateWAMessageFromContent)(jid, {
862
+ albumMessage: {
863
+ expectedImageCount: album.filter(item => 'image' in item).length,
864
+ expectedVideoCount: album.filter(item => 'video' in item).length
865
+ }
866
+ }, { userJid, ...options });
867
+ await relayMessage(jid, albumMsg.message, {
868
+ messageId: albumMsg.key.id
869
+ });
870
+ for (const i in album) {
871
+ const media = album[i];
872
+ if ('image' in media) {
873
+ mediaMsg = await (0, Utils_1.generateWAMessage)(jid, {
874
+ image: media.image,
875
+ ...(media.caption ? { caption: media.caption } : {}),
876
+ ...options
877
+ }, {
878
+ userJid,
879
+ upload: async (readStream, opts) => {
880
+ const up = await waUploadToServer(readStream, { ...opts, newsletter: (0, WABinary_1.isJidNewsletter)(jid) });
881
+ mediaHandle = up.handle;
882
+ return up;
883
+ },
884
+ ...options,
885
+ });
886
+ }
887
+ else if ('video' in media) {
888
+ mediaMsg = await (0, Utils_1.generateWAMessage)(jid, {
889
+ video: media.video,
890
+ ...(media.caption ? { caption: media.caption } : {}),
891
+ ...(media.gifPlayback !== undefined ? { gifPlayback: media.gifPlayback } : {}),
892
+ ...options
893
+ }, {
894
+ userJid,
895
+ upload: async (readStream, opts) => {
896
+ const up = await waUploadToServer(readStream, { ...opts, newsletter: (0, WABinary_1.isJidNewsletter)(jid) });
897
+ mediaHandle = up.handle;
898
+ return up;
899
+ },
900
+ ...options,
901
+ });
902
+ }
903
+ if (mediaMsg) {
904
+ mediaMsg.message.messageContextInfo = {
905
+ messageSecret: (0, crypto_1.randomBytes)(32),
906
+ messageAssociation: {
907
+ associationType: 1,
908
+ parentMessageKey: albumMsg.key
909
+ }
910
+ };
911
+ }
773
912
  await relayMessage(jid, mediaMsg.message, {
774
913
  messageId: mediaMsg.key.id
775
914
  });
776
- await new Promise(resolve => setTimeout(resolve, 800));
915
+ await new Promise(resolve => setTimeout(resolve, 100));
777
916
  }
917
+ return albumMsg;
778
918
  }
779
- return albumMsg;
780
- }
781
- else if (typeof content === 'object' && 'stickerPack' in content && content.stickerPack) {
782
- // Send sticker pack metadata first, then each sticker associated with it
783
- const { stickerPack } = content;
784
- const stickers = stickerPack.stickers || [];
785
- if (!Array.isArray(stickers) || stickers.length === 0) {
786
- throw new boom_1.Boom('stickerPack requires at least one sticker', { statusCode: 400 });
787
- }
788
-
789
- // Prepare cover thumbnail if provided
790
- let thumbnailDirectPath;
791
- let thumbnailEncSha256;
792
- let thumbnailSha256;
793
- let thumbnailHeight;
794
- let thumbnailWidth;
795
- if (stickerPack.cover) {
796
- try {
797
- const thumbMsg = await (0, Utils_1.prepareWAMessageMedia)({ image: stickerPack.cover }, {
798
- logger,
799
- userJid,
800
- upload: async (readStream, opts) => {
801
- const up = await waUploadToServer(readStream, { ...opts, newsletter: (0, WABinary_1.isJidNewsletter)(jid) });
802
- return up;
803
- },
804
- mediaCache: config.mediaCache,
805
- options: config.options,
806
- messageId: (0, Utils_1.generateMessageIDV2)((_c = sock.user) === null || _c === void 0 ? void 0 : _c.id),
807
- ...options,
808
- });
809
- if (thumbMsg.imageMessage) {
810
- thumbnailDirectPath = thumbMsg.imageMessage.directPath;
811
- thumbnailEncSha256 = thumbMsg.imageMessage.fileEncSha256;
812
- thumbnailSha256 = thumbMsg.imageMessage.fileSha256;
813
- thumbnailHeight = thumbMsg.imageMessage.height;
814
- thumbnailWidth = thumbMsg.imageMessage.width;
815
- }
816
- }
817
- catch (err) {
818
- logger === null || logger === void 0 ? void 0 : logger.warn({ err }, 'failed to prepare stickerPack cover');
819
- }
820
- }
821
-
822
- // Map stickers metadata to proto-friendly shape
823
- const protoStickers = stickers.map((s, idx) => ({
824
- fileName: s.fileName || `sticker_${idx}.webp`,
825
- isAnimated: !!s.isAnimated,
826
- emojis: Array.isArray(s.emojis) ? s.emojis : (s.emojis ? [s.emojis] : []),
827
- accessibilityLabel: s.accessibilityLabel,
828
- isLottie: !!s.isLottie,
829
- mimetype: s.mimetype || 'image/webp'
830
- }));
831
-
832
- const stickerPackObj = {
833
- name: stickerPack.name,
834
- publisher: stickerPack.publisher,
835
- packDescription: stickerPack.description,
836
- stickers: protoStickers,
837
- thumbnailDirectPath,
838
- thumbnailEncSha256,
839
- thumbnailSha256,
840
- thumbnailHeight,
841
- thumbnailWidth,
842
- };
843
-
844
- // Create and send the pack metadata message
845
- const contentForSend = { stickerPackMessage: WAProto_1.proto.Message.StickerPackMessage.fromObject(stickerPackObj) };
846
- const packMsg = (0, Utils_1.generateWAMessageFromContent)(jid, contentForSend, {
847
- userJid,
848
- upload: waUploadToServer,
849
- mediaCache: config.mediaCache,
850
- options: config.options,
851
- messageId: (0, Utils_1.generateMessageIDV2)((_c = sock.user) === null || _c === void 0 ? void 0 : _c.id),
852
- ...options,
853
- });
854
- await relayMessage(jid, packMsg.message, { messageId: packMsg.key.id });
855
-
856
- // Send each sticker associated with the pack
857
- let lastMsg = packMsg;
858
- for (const sticker of stickers) {
859
- const stickerData = sticker.sticker || sticker.data || sticker.buffer || sticker.image || sticker.url || sticker;
860
- if (!stickerData) {
861
- throw new boom_1.Boom('Sticker data not found for sticker: ' + JSON.stringify(sticker), { statusCode: 400 });
862
- }
863
- const stickerContent = { sticker: stickerData };
919
+ else {
864
920
  let mediaHandle;
865
- const stickerMsg = await (0, Utils_1.generateWAMessage)(jid, stickerContent, {
921
+ const fullMsg = await (0, Utils_1.generateWAMessage)(jid, content, {
866
922
  logger,
867
923
  userJid,
924
+ getUrlInfo: text => (0, link_preview_1.getUrlInfo)(text, {
925
+ thumbnailWidth: linkPreviewImageThumbnailWidth,
926
+ fetchOpts: {
927
+ timeout: 3000,
928
+ ...axiosOptions || {}
929
+ },
930
+ logger,
931
+ uploadImage: generateHighQualityLinkPreview
932
+ ? waUploadToServer
933
+ : undefined
934
+ }),
935
+ getProfilePicUrl: sock.profilePictureUrl,
868
936
  upload: async (readStream, opts) => {
869
937
  const up = await waUploadToServer(readStream, { ...opts, newsletter: (0, WABinary_1.isJidNewsletter)(jid) });
870
938
  mediaHandle = up.handle;
@@ -875,243 +943,69 @@ const makeMessagesSocket = (config) => {
875
943
  messageId: (0, Utils_1.generateMessageIDV2)((_c = sock.user) === null || _c === void 0 ? void 0 : _c.id),
876
944
  ...options,
877
945
  });
878
- // Associate sticker with the pack message
879
- stickerMsg.message.messageContextInfo = {
880
- messageSecret: (0, crypto_1.randomBytes)(32),
881
- messageAssociation: {
882
- associationType: 1,
883
- parentMessageKey: packMsg.key
946
+ const isDeleteMsg = 'delete' in content && !!content.delete;
947
+ const isEditMsg = 'edit' in content && !!content.edit;
948
+ const isPinMsg = 'pin' in content && !!content.pin;
949
+ const isKeepMsg = 'keep' in content && content.keep;
950
+ const isPollMessage = 'poll' in content && !!content.poll;
951
+ const isAiMsg = 'ai' in content && !!content.ai;
952
+ const additionalAttributes = {};
953
+ const additionalNodes = [];
954
+ // required for delete
955
+ if (isDeleteMsg) {
956
+ // if the chat is a group, and I am not the author, then delete the message as an admin
957
+ if (((0, WABinary_1.isJidGroup)(content.delete.remoteJid) && !content.delete.fromMe) || (0, WABinary_1.isJidNewsletter)(jid)) {
958
+ additionalAttributes.edit = '8';
884
959
  }
885
- };
886
- await relayMessage(jid, stickerMsg.message, { messageId: stickerMsg.key.id });
887
- lastMsg = stickerMsg;
888
- // Add delay between stickers to avoid rate limiting
889
- await new Promise(resolve => setTimeout(resolve, 800));
890
- }
891
- return lastMsg;
892
- }
893
- else {
894
- let mediaHandle;
895
- const fullMsg = await (0, Utils_1.generateWAMessage)(jid, content, {
896
- logger,
897
- userJid,
898
- getUrlInfo: text => (0, link_preview_1.getUrlInfo)(text, {
899
- thumbnailWidth: linkPreviewImageThumbnailWidth,
900
- fetchOpts: {
901
- timeout: 3000,
902
- ...axiosOptions || {}
903
- },
904
- logger,
905
- uploadImage: generateHighQualityLinkPreview
906
- ? waUploadToServer
907
- : undefined
908
- }),
909
- getProfilePicUrl: sock.profilePictureUrl,
910
- upload: async (readStream, opts) => {
911
- const up = await waUploadToServer(readStream, { ...opts, newsletter: (0, WABinary_1.isJidNewsletter)(jid) });
912
- mediaHandle = up.handle;
913
- return up;
914
- },
915
- mediaCache: config.mediaCache,
916
- options: config.options,
917
- messageId: (0, Utils_1.generateMessageIDV2)((_c = sock.user) === null || _c === void 0 ? void 0 : _c.id),
918
- ...options,
919
- });
920
- const isDeleteMsg = 'delete' in content && !!content.delete;
921
- const isEditMsg = 'edit' in content && !!content.edit;
922
- const isPinMsg = 'pin' in content && !!content.pin;
923
- const isKeepMsg = 'keep' in content && content.keep;
924
- const isPollMessage = 'poll' in content && !!content.poll;
925
- const isAiMsg = options.ai === true;
926
- const additionalAttributes = {};
927
- const additionalNodes = [];
928
- // required for delete
929
- if (isDeleteMsg) {
930
- // if the chat is a group, and I am not the author, then delete the message as an admin
931
- if (((0, WABinary_1.isJidGroup)(content.delete.remoteJid) && !content.delete.fromMe) || (0, WABinary_1.isJidNewsletter)(jid)) {
932
- additionalAttributes.edit = '8';
960
+ else {
961
+ additionalAttributes.edit = '7';
962
+ }
963
+ // required for edit message
933
964
  }
934
- else {
935
- additionalAttributes.edit = '7';
965
+ else if (isEditMsg) {
966
+ additionalAttributes.edit = (0, WABinary_1.isJidNewsletter)(jid) ? '3' : '1';
967
+ // required for pin message
936
968
  }
937
- // required for edit message
938
- }
939
- else if (isEditMsg) {
940
- additionalAttributes.edit = (0, WABinary_1.isJidNewsletter)(jid) ? '3' : '1';
941
- // required for pin message
942
- }
943
- else if (isPinMsg) {
944
- additionalAttributes.edit = '2';
945
- // required for keep message
946
- }
947
- else if (isKeepMsg) {
948
- additionalAttributes.edit = '6';
949
- // required for polling message
950
- }
951
- else if (isPollMessage) {
952
- additionalNodes.push({
953
- tag: 'meta',
954
- attrs: {
955
- polltype: 'creation'
956
- },
957
- });
958
- // required to display AI icon on message
959
- }
960
- else if (isAiMsg) {
961
- additionalNodes.push({
962
- attrs: {
963
- biz_bot: '1'
964
- },
965
- tag: "bot"
966
- });
967
- }
968
- if (mediaHandle) {
969
- additionalAttributes['media_id'] = mediaHandle;
970
- }
971
- if ('cachedGroupMetadata' in options) {
972
- console.warn('cachedGroupMetadata in sendMessage are deprecated, now cachedGroupMetadata is part of the socket config.');
973
- }
974
- // Add AI context if needed
975
- if (isAiMsg) {
976
- fullMsg.message.messageContextInfo = {
977
- ...fullMsg.message.messageContextInfo,
978
- biz_bot: '1'
979
- };
980
- }
981
-
982
-
983
- await relayMessage(jid, fullMsg.message, { messageId: fullMsg.key.id, useCachedGroupMetadata: options.useCachedGroupMetadata, additionalAttributes, additionalNodes: isAiMsg ? additionalNodes : options.additionalNodes, statusJidList: options.statusJidList });
984
- if (config.emitOwnEvents) {
985
- process.nextTick(() => {
986
- processingMutex.mutex(() => (upsertMessage(fullMsg, 'append')));
987
- });
988
- }
989
- return fullMsg;
990
- }
991
- };
992
-
993
- // Helper function to send missed call note
994
- const sendMissedCallNote = async (jid, media, options = {}) => {
995
- const prepared = await prepareWAMessageMedia(media, { upload: sock.waUploadToServer });
996
- return sock.sendMessage(jid, {
997
- ...prepared,
998
- ptt: !!media.audio,
999
- contextInfo: {
1000
- externalAdReply: {
1001
- title: 'Missed Call Note',
1002
- body: options.callInfo?.id ? `From call ${options.callInfo.id}` : 'Recent call'
969
+ else if (isPinMsg) {
970
+ additionalAttributes.edit = '2';
971
+ // required for keep message
1003
972
  }
1004
- }
1005
- });
1006
- };
1007
-
1008
- return {
1009
- ...sock,
1010
- getPrivacyTokens,
1011
- assertSessions,
1012
- relayMessage,
1013
- sendReceipt,
1014
- sendReceipts,
1015
- readMessages,
1016
- refreshMediaConn,
1017
- waUploadToServer,
1018
- fetchPrivacySettings,
1019
- getUSyncDevices,
1020
- createParticipantNodes,
1021
- sendPeerDataOperationMessage,
1022
- sendMissedCallNote,
1023
- updateMediaMessage: async (message) => {
1024
- const content = (0, Utils_1.assertMediaContent)(message.message);
1025
- const mediaKey = content.mediaKey;
1026
- const meId = authState.creds.me.id;
1027
- const node = await (0, Utils_1.encryptMediaRetryRequest)(message.key, mediaKey, meId);
1028
- let error = undefined;
1029
- await Promise.all([
1030
- sendNode(node),
1031
- waitForMsgMediaUpdate(async (update) => {
1032
- const result = update.find(c => c.key.id === message.key.id);
1033
- if (result) {
1034
- if (result.error) {
1035
- error = result.error;
1036
- }
1037
- else {
1038
- try {
1039
- const media = await (0, Utils_1.decryptMediaRetryData)(result.media, mediaKey, result.key.id);
1040
- if (media.result !== WAProto_1.proto.MediaRetryNotification.ResultType.SUCCESS) {
1041
- const resultStr = WAProto_1.proto.MediaRetryNotification.ResultType[media.result];
1042
- throw new boom_1.Boom(`Media re-upload failed by device (${resultStr})`, { data: media, statusCode: (0, Utils_1.getStatusCodeForMediaRetry)(media.result) || 404 });
1043
- }
1044
- content.directPath = media.directPath;
1045
- content.url = (0, Utils_1.getUrlFromDirectPath)(content.directPath);
1046
- logger.debug({ directPath: media.directPath, key: result.key }, 'media update successful');
1047
- }
1048
- catch (err) {
1049
- error = err;
1050
- }
1051
- }
1052
- return true;
1053
- }
1054
- })
1055
- ]);
1056
- if (error) {
1057
- throw error;
1058
- }
1059
- ev.emit('messages.update', [
1060
- { key: message.key, update: { message: message.message } }
1061
- ]);
1062
- return message;
1063
- },
1064
- sendMessage: async (jid, content, options = {}) => {
1065
- var _a;
1066
- // Handle admin-only messages by sending private messages to each admin
1067
- if (((_a = content.contextInfo) === null || _a === void 0 ? void 0 : _a.isAdminOnly) && (0, WABinary_1.isJidGroup)(jid)) {
1068
- try {
1069
- // Get group metadata to find admins
1070
- const metadata = await sock.groupMetadata(jid);
1071
- const participants = metadata.participants || [];
1072
-
1073
- // Find admin JIDs and ensure they are properly formatted
1074
- const adminJids = participants
1075
- .filter(p => p.admin === 'admin' || p.admin === 'superadmin')
1076
- .map(p => (0, WABinary_1.jidNormalizedUser)(p.id));
1077
-
1078
- if (adminJids.length === 0) {
1079
- throw new boom_1.Boom('No admins found in group', { statusCode: 400 });
1080
- }
1081
-
1082
- // Remove isAdminOnly from content to avoid recursion
1083
- const contentCopy = { ...content };
1084
- if (contentCopy.contextInfo) {
1085
- const { isAdminOnly, ...contextInfoRest } = contentCopy.contextInfo;
1086
- contentCopy.contextInfo = contextInfoRest;
1087
- }
1088
-
1089
- // Add group context to indicate this is from a group
1090
- contentCopy.contextInfo = {
1091
- ...contentCopy.contextInfo,
1092
- groupJid: jid,
1093
- adminOnlyMessage: true
1094
- };
1095
-
1096
- // Send private message to each admin
1097
- const results = [];
1098
- for (const adminJid of adminJids) {
1099
- try {
1100
- const result = await rateLimiter.add(() => sendMessageInternal(adminJid, contentCopy, options));
1101
- results.push(result);
1102
- } catch (error) {
1103
- console.warn(`Failed to send admin-only message to ${adminJid}:`, error);
1104
- }
1105
- }
1106
-
1107
- return results.length > 0 ? results[0] : null; // Return first successful result
1108
- } catch (error) {
1109
- console.error('Failed to send admin-only message:', error);
1110
- throw error;
973
+ else if (isKeepMsg) {
974
+ additionalAttributes.edit = '6';
975
+ // required for polling message
976
+ }
977
+ else if (isPollMessage) {
978
+ additionalNodes.push({
979
+ tag: 'meta',
980
+ attrs: {
981
+ polltype: 'creation'
982
+ },
983
+ });
984
+ // required to display AI icon on message
985
+ }
986
+ else if (isAiMsg) {
987
+ additionalNodes.push({
988
+ attrs: {
989
+ biz_bot: '1'
990
+ },
991
+ tag: "bot"
992
+ });
993
+ }
994
+ if (mediaHandle) {
995
+ additionalAttributes['media_id'] = mediaHandle;
996
+ }
997
+ if ('cachedGroupMetadata' in options) {
998
+ logger.warn('cachedGroupMetadata in sendMessage are deprecated, now cachedGroupMetadata is part of the socket config.');
999
+ }
1000
+ await relayMessage(jid, fullMsg.message, { messageId: fullMsg.key.id, useCachedGroupMetadata: options.useCachedGroupMetadata, additionalAttributes, additionalNodes: isAiMsg ? additionalNodes : options.additionalNodes, statusJidList: options.statusJidList });
1001
+ if (config.emitOwnEvents) {
1002
+ process.nextTick(() => {
1003
+ processingMutex.mutex(() => (upsertMessage(fullMsg, 'append')));
1004
+ });
1111
1005
  }
1006
+ return fullMsg;
1112
1007
  }
1113
- return rateLimiter.add(() => sendMessageInternal(jid, content, options));
1114
1008
  }
1115
1009
  };
1116
1010
  };
1117
- exports.makeMessagesSocket = makeMessagesSocket;
1011
+ exports.makeMessagesSocket = makeMessagesSocket;