violetics 7.0.6-alpha → 7.0.7-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.
@@ -2,9 +2,9 @@ import { proto } from '../../WAProto/index.js';
2
2
  import { makeLibSignalRepository } from '../Signal/libsignal.js';
3
3
  import { Browsers } from '../Utils/browser-utils.js';
4
4
  import logger from '../Utils/logger.js';
5
- const version = [2, 3000, 1033846690];
5
+ const version = [2, 3000, 1035194821];
6
6
  export const UNAUTHORIZED_CODES = [401, 403, 419];
7
- export const BIZ_BOT_SUPPORT_PAYLOAD = '{"version":1,"is_ai_message":true,"should_show_system_message":false,"ticket_id":"7004947587700716","citation_items":[],"ticket_locale":"us"}';
7
+ export const BIZ_BOT_SUPPORT_PAYLOAD = '{"version":1,"is_ai_message":true,"should_upload_client_logs":false,"should_show_system_message":false,"ticket_id":"7004947587700716","citation_items":[],"ticket_locale":"us"}';
8
8
  export const DEFAULT_ORIGIN = 'https://web.whatsapp.com';
9
9
  export const CALL_VIDEO_PREFIX = 'https://call.whatsapp.com/video/';
10
10
  export const CALL_AUDIO_PREFIX = 'https://call.whatsapp.com/voice/';
@@ -26,7 +26,6 @@ export const NOISE_MODE = 'Noise_XX_25519_AESGCM_SHA256\0\0\0\0';
26
26
  export const DICT_VERSION = 3;
27
27
  export const KEY_BUNDLE_TYPE = Buffer.from([5]);
28
28
  export const NOISE_WA_HEADER = Buffer.from([87, 65, 6, DICT_VERSION]); // last is "DICT_VERSION"
29
- export const OLD_GROUP_ID_REGEX = /^(\d{1,15})-(\d+)@g\.us$/;
30
29
  /** from: https://stackoverflow.com/questions/3809401/what-is-a-good-regular-expression-to-match-a-url */
31
30
  export const URL_REGEX = /https:\/\/(?![^:@\/\s]+:[^:@\/\s]+@)[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(:\d+)?(\/[^\s]*)?/g;
32
31
  export const WA_CERT_DETAILS = {
@@ -53,7 +52,7 @@ export const DEFAULT_CONNECTION_CONFIG = {
53
52
  emitOwnEvents: true,
54
53
  defaultQueryTimeoutMs: 60000,
55
54
  customUploadHosts: [],
56
- retryRequestDelayMs: 0,
55
+ retryRequestDelayMs: 250,
57
56
  maxMsgRetryCount: 3,
58
57
  fireInitQueries: true,
59
58
  auth: undefined,
@@ -65,7 +64,7 @@ export const DEFAULT_CONNECTION_CONFIG = {
65
64
  },
66
65
  shouldIgnoreJid: () => false,
67
66
  linkPreviewImageThumbnailWidth: 192,
68
- transactionOpts: { maxCommitRetries: 5, delayBetweenTriesMs: 3000 },
67
+ transactionOpts: { maxCommitRetries: 10, delayBetweenTriesMs: 3000 },
69
68
  generateHighQualityLinkPreview: false,
70
69
  enableAutoSessionRecreation: true,
71
70
  enableRecentMessageCache: true,
@@ -86,14 +85,14 @@ export const MEDIA_PATH_MAP = {
86
85
  audio: '/mms/audio',
87
86
  sticker: '/mms/image',
88
87
  'sticker-pack': '/mms/sticker-pack',
89
- 'thumbnail-sticker-pack': '/mms/thumbnail-sticker-pack',
88
+ 'thumbnail-sticker-pack': '/mms/thumbnail-sticker-pack',
90
89
  'thumbnail-link': '/mms/thumbnail-link',
91
90
  'product-catalog-image': '/product/image',
92
91
  'md-app-state': '',
93
92
  'md-msg-hist': '/mms/md-app-state',
94
93
  'biz-cover-photo': '/pps/biz-cover-photo'
95
94
  };
96
- // vltcs@changes 06-02-26 --- Add newsletter media path for "/m1/" instead of "/o1/" (⁠≧⁠▽⁠≦⁠)
95
+ // Lia@Changes 06-02-26 --- Add newsletter media path for "/m1/" instead of "/o1/" (⁠≧⁠▽⁠≦⁠)
97
96
  export const NEWSLETTER_MEDIA_PATH_MAP = {
98
97
  image: '/newsletter/newsletter-image',
99
98
  video: '/newsletter/newsletter-video',
@@ -111,7 +110,7 @@ export const MEDIA_HKDF_KEY_MAPPING = {
111
110
  product: 'Image',
112
111
  ptt: 'Audio',
113
112
  'sticker-pack': 'Sticker Pack',
114
- 'thumbnail-sticker-pack': 'Sticker Pack Thumbnail',
113
+ 'thumbnail-sticker-pack': 'Sticker Pack Thumbnail',
115
114
  sticker: 'Image',
116
115
  video: 'Video',
117
116
  'thumbnail-document': 'Document Thumbnail',
@@ -5,7 +5,6 @@ export class WebSocketClient extends AbstractSocketClient {
5
5
  constructor() {
6
6
  super(...arguments);
7
7
  this.socket = null;
8
- this._eventForwarders = [];
9
8
  }
10
9
  get isOpen() {
11
10
  return this.socket?.readyState === WebSocket.OPEN;
@@ -32,22 +31,14 @@ export class WebSocketClient extends AbstractSocketClient {
32
31
  });
33
32
  this.socket.setMaxListeners(0);
34
33
  const events = ['close', 'error', 'upgrade', 'message', 'open', 'ping', 'pong', 'unexpected-response'];
35
- this._eventForwarders = [];
36
34
  for (const event of events) {
37
- const handler = (...args) => this.emit(event, ...args);
38
- this.socket?.on(event, handler);
39
- this._eventForwarders.push({ event, handler });
35
+ this.socket?.on(event, (...args) => this.emit(event, ...args));
40
36
  }
41
37
  }
42
38
  async close() {
43
39
  if (!this.socket) {
44
40
  return;
45
41
  }
46
- // Remove all forwarding listeners to prevent memory leaks
47
- for (const { event, handler } of this._eventForwarders) {
48
- this.socket?.off(event, handler);
49
- }
50
- this._eventForwarders = [];
51
42
  const closePromise = new Promise(resolve => {
52
43
  this.socket?.once('close', resolve);
53
44
  });
@@ -1,5 +1,4 @@
1
1
  import NodeCache from '@cacheable/node-cache';
2
- import { LRUCache } from 'lru-cache';
3
2
  import { Boom } from '@hapi/boom';
4
3
  import { proto } from '../../WAProto/index.js';
5
4
  import { DEFAULT_CACHE_TTLS, PROCESSABLE_HISTORY_TYPES } from '../Defaults/index.js';
@@ -13,12 +12,12 @@ import { getBinaryNodeChild, getBinaryNodeChildren, isLidUser, isPnUser, jidDeco
13
12
  import { USyncQuery, USyncUser } from '../WAUSync/index.js';
14
13
  import { makeSocket } from './socket.js';
15
14
  const MAX_SYNC_ATTEMPTS = 2;
15
+ // Lia@Note 08-02-26 --- I know it's not efficient for RSS ಥ⁠‿⁠ಥ
16
+ const USER_ID_CACHE = new Map();
16
17
  export const makeChatsSocket = (config) => {
17
18
  const { logger, markOnlineOnConnect, fireInitQueries, appStateMacVerification, shouldIgnoreJid, shouldSyncHistoryMessage, getMessage } = config;
18
19
  const sock = makeSocket(config);
19
20
  const { ev, ws, authState, generateMessageTag, sendNode, query, signalRepository, onUnexpectedError, sendUnifiedSession } = sock;
20
- // Scoped per-connection with LRU to prevent unbounded memory growth
21
- const USER_ID_CACHE = new LRUCache({ max: 5000, ttl: 30 * 60 * 1000 });
22
21
  let privacySettings;
23
22
  let syncState = SyncState.Connecting;
24
23
  /** this mutex ensures that messages are processed in order */
@@ -508,7 +507,7 @@ export const makeChatsSocket = (config) => {
508
507
  * type = "image for the high res picture"
509
508
  */
510
509
  const profilePictureUrl = async (jid, type = 'image', timeoutMs) => {
511
- // vltcs@changes 06-02-26 --- Refactor profilePictureUrl() to use tctoken and adjust error handling
510
+ // Lia@Changes 06-02-26 --- Refactor profilePictureUrl() to use tctoken and adjust error handling
512
511
  jid = jidNormalizedUser(jid);
513
512
  const baseContent = {
514
513
  tag: 'picture',
@@ -876,7 +875,7 @@ export const makeChatsSocket = (config) => {
876
875
  const historyMsg = getHistoryMsg(msg.message);
877
876
  const shouldProcessHistoryMsg = historyMsg
878
877
  ? shouldSyncHistoryMessage(historyMsg) &&
879
- PROCESSABLE_HISTORY_TYPES.includes(historyMsg.syncType)
878
+ PROCESSABLE_HISTORY_TYPES.includes(historyMsg.syncType)
880
879
  : false;
881
880
  // State machine: decide on sync and flush
882
881
  if (historyMsg && syncState === SyncState.AwaitingInitialSync) {
@@ -18,6 +18,7 @@ export const makeMessagesRecvSocket = (config) => {
18
18
  const { ev, authState, ws, messageMutex, notificationMutex, receiptMutex, signalRepository, query, upsertMessage, resyncAppState, onUnexpectedError, assertSessions, sendNode, relayMessage, sendReceipt, uploadPreKeys, sendPeerDataOperationMessage, generateMessageTag, messageRetryManager } = sock;
19
19
  /** this mutex ensures that each retryRequest will wait for the previous one to finish */
20
20
  const retryMutex = makeMutex();
21
+ const devicesMutex = makeMutex();
21
22
  const msgRetryCache = config.msgRetryCounterCache ||
22
23
  new NodeCache({
23
24
  stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
@@ -33,6 +34,11 @@ export const makeMessagesRecvSocket = (config) => {
33
34
  stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
34
35
  useClones: false
35
36
  });
37
+ const userDevicesCache = config.userDevicesCache ??=
38
+ new NodeCache({
39
+ stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES, // 5 minutes
40
+ useClones: false
41
+ });
36
42
  // Debounce identity-change session refreshes per JID to avoid bursts
37
43
  const identityAssertDebounce = new NodeCache({ stdTTL: 5, useClones: false });
38
44
  let sendActiveReceipts = false;
@@ -65,7 +71,7 @@ export const makeMessagesRecvSocket = (config) => {
65
71
  // metadata (LID details, timestamps, etc.) that the phone may omit
66
72
  await placeholderResendCache.set(messageKey?.id, msgData || true);
67
73
  }
68
- await delay(500);
74
+ await delay(2000);
69
75
  if (!(await placeholderResendCache.get(messageKey?.id))) {
70
76
  logger.debug({ messageKey }, 'message received while resend requested');
71
77
  return 'RESOLVED';
@@ -615,6 +621,64 @@ export const makeMessagesRecvSocket = (config) => {
615
621
  break;
616
622
  }
617
623
  };
624
+ const handleDevicesNotification = async (node) => {
625
+ const [child] = getAllBinaryNodeChildren(node);
626
+ const from = jidNormalizedUser(node.attrs.from);
627
+ const devices = getBinaryNodeChildren(child, 'device');
628
+ if (areJidsSameUser(from, authState.creds.me.id) ||
629
+ areJidsSameUser(from, authState.creds.me.lid)) {
630
+ const deviceJids = devices.map(d => d.attrs.jid);
631
+ logger.info({ deviceJids }, 'got my own devices');
632
+ }
633
+ if (!devices || !devices.length || !devices[0]) {
634
+ logger.debug({ from }, 'no devices in notification, skipping');
635
+ return;
636
+ }
637
+ const deviceJid = devices[0].attrs.jid;
638
+ const decoded = jidDecode(deviceJid);
639
+ if (!decoded) return;
640
+ const { user, device } = decoded;
641
+ const tag = child.tag;
642
+ if (!deviceJid) {
643
+ logger.debug({ tag }, 'no device jid in notification, skipping');
644
+ return;
645
+ }
646
+ await devicesMutex.mutex(async () => {
647
+ if (tag === 'update') {
648
+ logger.debug({ user }, `${user}'s device list updated, dropping cached devices`);
649
+ if (userDevicesCache) {
650
+ await userDevicesCache.del(user);
651
+ }
652
+ return;
653
+ }
654
+ const existingCache = (await (userDevicesCache?.get(user))) || [];
655
+ if (!existingCache.length) {
656
+ logger.debug({ user, tag }, 'device list not cached, skipping cache update')
657
+ return;
658
+ }
659
+ const deviceHash = child.attrs.device_hash;
660
+ let updatedDevices = [];
661
+ switch (tag) {
662
+ case 'add':
663
+ logger.info({ deviceHash }, 'device added');
664
+ updatedDevices = [
665
+ ...existingCache.filter(d => d.device !== device),
666
+ { user, device }
667
+ ];
668
+ break;
669
+ case 'remove':
670
+ logger.info({ deviceHash }, 'device removed');
671
+ updatedDevices = existingCache.filter(d => d.device !== device);
672
+ break;
673
+ default:
674
+ logger.debug({ tag }, 'Unknown device list change tag');
675
+ return;
676
+ }
677
+ if (updatedDevices.length > 0 && userDevicesCache) {
678
+ await userDevicesCache.set(user, updatedDevices);
679
+ }
680
+ });
681
+ };
618
682
  const processNotification = async (node) => {
619
683
  const result = {};
620
684
  const [child] = getAllBinaryNodeChildren(node);
@@ -639,13 +703,12 @@ export const makeMessagesRecvSocket = (config) => {
639
703
  await handleEncryptNotification(node);
640
704
  break;
641
705
  case 'devices':
642
- const devices = getBinaryNodeChildren(child, 'device');
643
- if (areJidsSameUser(child.attrs.jid, authState.creds.me.id) ||
644
- areJidsSameUser(child.attrs.lid, authState.creds.me.lid)) {
645
- const deviceData = devices.map(d => ({ id: d.attrs.jid, lid: d.attrs.lid }));
646
- logger.info({ deviceData }, 'my own devices changed');
706
+ try {
707
+ await handleDevicesNotification(node);
708
+ }
709
+ catch (error) {
710
+ logger.error({ error, node }, 'failed to handle devices notification');
647
711
  }
648
- //TODO: drop a new event, add hashes
649
712
  break;
650
713
  case 'server_sync':
651
714
  const update = getBinaryNodeChild(node, 'collection');
@@ -1094,19 +1157,19 @@ export const makeMessagesRecvSocket = (config) => {
1094
1157
  };
1095
1158
  requestPlaceholderResend(cleanKey, msgData)
1096
1159
  .then(requestId => {
1097
- if (requestId && requestId !== 'RESOLVED') {
1098
- logger.debug({ msgId: msg.key.id, requestId }, 'requested placeholder resend for unavailable message');
1099
- ev.emit('messages.update', [
1100
- {
1101
- key: msg.key,
1102
- update: { messageStubParameters: [NO_MESSAGE_FOUND_ERROR_TEXT, requestId] }
1103
- }
1104
- ]);
1105
- }
1106
- })
1160
+ if (requestId && requestId !== 'RESOLVED') {
1161
+ logger.debug({ msgId: msg.key.id, requestId }, 'requested placeholder resend for unavailable message');
1162
+ ev.emit('messages.update', [
1163
+ {
1164
+ key: msg.key,
1165
+ update: { messageStubParameters: [NO_MESSAGE_FOUND_ERROR_TEXT, requestId] }
1166
+ }
1167
+ ]);
1168
+ }
1169
+ })
1107
1170
  .catch(err => {
1108
- logger.warn({ err, msgId: msg.key.id }, 'failed to request placeholder resend for unavailable message');
1109
- });
1171
+ logger.warn({ err, msgId: msg.key.id }, 'failed to request placeholder resend for unavailable message');
1172
+ });
1110
1173
  acked = true;
1111
1174
  await sendMessageAck(node);
1112
1175
  // Don't return — fall through to upsertMessage so the stub is emitted
@@ -1138,7 +1201,7 @@ export const makeMessagesRecvSocket = (config) => {
1138
1201
  logger.debug('Uploading pre-keys for error recovery');
1139
1202
  await uploadPreKeys(5);
1140
1203
  logger.debug('Waiting for server to process new pre-keys');
1141
- await delay(200);
1204
+ await delay(1000);
1142
1205
  }
1143
1206
  catch (uploadErr) {
1144
1207
  logger.error({ uploadErr }, 'Pre-key upload failed, proceeding with retry anyway');
@@ -1308,12 +1371,8 @@ export const makeMessagesRecvSocket = (config) => {
1308
1371
  /// and adds the task to the existing buffer if we're buffering events
1309
1372
  const processNodeWithBuffer = async (node, identifier, exec) => {
1310
1373
  ev.buffer();
1311
- try {
1312
- await execTask();
1313
- }
1314
- finally {
1315
- ev.flush();
1316
- }
1374
+ await execTask();
1375
+ ev.flush();
1317
1376
  function execTask() {
1318
1377
  return exec(node, false).catch(err => onUnexpectedError(err, identifier));
1319
1378
  }
@@ -2,7 +2,7 @@ import NodeCache from '@cacheable/node-cache';
2
2
  import { Boom } from '@hapi/boom';
3
3
  import { randomBytes } from 'crypto';
4
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';
5
+ import { BIZ_BOT_SUPPORT_PAYLOAD, DEFAULT_CACHE_TTLS, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
6
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
7
  import { AssociationType } from '../Types/index.js';
8
8
  import { getUrlInfo } from '../Utils/link-preview.js';
@@ -15,7 +15,7 @@ export const makeMessagesSocket = (config) => {
15
15
  const { logger, linkPreviewImageThumbnailWidth, generateHighQualityLinkPreview, options: httpRequestOptions, patchMessageBeforeSending, cachedGroupMetadata, enableRecentMessageCache, maxMsgRetryCount } = config;
16
16
  const sock = makeNewsletterSocket(config);
17
17
  const { ev, authState, messageMutex, signalRepository, upsertMessage, query, fetchPrivacySettings, sendNode, groupMetadata, groupToggleEphemeral } = sock;
18
- const userDevicesCache = config.userDevicesCache ||
18
+ const userDevicesCache = config.userDevicesCache ??=
19
19
  new NodeCache({
20
20
  stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES, // 5 minutes
21
21
  useClones: false
@@ -129,21 +129,21 @@ export const makeMessagesSocket = (config) => {
129
129
  const toFetch = [];
130
130
  const jidsWithUser = jids
131
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
- })
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
147
  .filter(jid => jid !== null);
148
148
  let mgetDevices;
149
149
  if (useCache && userDevicesCache.mget) {
@@ -429,10 +429,10 @@ export const makeMessagesSocket = (config) => {
429
429
  }
430
430
  return { nodes, shouldIncludeDeviceIdentity };
431
431
  };
432
- const relayMessage = async (jid, message, { messageId: msgId, participant, additionalAttributes, additionalNodes, useUserDevicesCache, useCachedGroupMetadata, statusJidList }) => {
432
+ const relayMessage = async (jid, message, { messageId: msgId, participant, additionalAttributes, additionalNodes, useUserDevicesCache, useCachedGroupMetadata, addBizAttributes, statusJidList }) => {
433
433
  const meId = authState.creds.me.id;
434
434
  const meLid = authState.creds.me?.lid;
435
- const isRetryResend = Boolean(participant?.jid);
435
+ const isRetryResend = !!participant?.jid;
436
436
  let shouldIncludeDeviceIdentity = isRetryResend;
437
437
  const statusJid = 'status@broadcast';
438
438
  const { user, server } = jidDecode(jid);
@@ -470,7 +470,7 @@ export const makeMessagesSocket = (config) => {
470
470
  });
471
471
  }
472
472
  await authState.keys.transaction(async () => {
473
- // vltcs@changes 02-02-26 --- Normalize message first to extract the original message and valid media type
473
+ // Lia@Changes 02-02-26 --- Normalize message first to extract the original message and valid media type
474
474
  const innerMessage = normalizeMessageContent(message);
475
475
  const mediaType = getMediaType(innerMessage);
476
476
  if (mediaType) {
@@ -482,14 +482,14 @@ export const makeMessagesSocket = (config) => {
482
482
  }
483
483
  const patched = patchMessageBeforeSending ? await patchMessageBeforeSending(message, []) : message;
484
484
  const bytes = encodeNewsletterMessage(patched);
485
- // vltcs@changes 08-02-26 --- Add "additionalNodes" for newsletter too (⁠っ⁠˘̩⁠╭⁠╮⁠˘̩⁠)⁠っ
485
+ // Lia@Changes 08-02-26 --- Add "additionalNodes" for newsletter too (⁠っ⁠˘̩⁠╭⁠╮⁠˘̩⁠)⁠っ
486
486
  if (additionalNodes && additionalNodes.length > 0) {
487
487
  ;
488
488
  binaryNodeContent.push(...additionalNodes);
489
489
  }
490
490
  binaryNodeContent.push({
491
491
  tag: 'plaintext',
492
- attrs: extraAttrs, // vltcs@changes 02-02-26 --- Add extraAttrs to fix media being rejected when sending to newsletter (⁠◠⁠‿⁠◕⁠)
492
+ attrs: extraAttrs, // Lia@Changes 02-02-26 --- Add extraAttrs to fix media being rejected when sending to newsletter (⁠◠⁠‿⁠◕⁠)
493
493
  content: bytes
494
494
  });
495
495
  const stanza = {
@@ -497,7 +497,7 @@ export const makeMessagesSocket = (config) => {
497
497
  attrs: {
498
498
  to: jid,
499
499
  id: msgId,
500
- type: getMessageType(message),
500
+ type: getMessageType(innerMessage),
501
501
  ...(additionalAttributes || {})
502
502
  },
503
503
  content: binaryNodeContent
@@ -506,11 +506,11 @@ export const makeMessagesSocket = (config) => {
506
506
  await sendNode(stanza);
507
507
  return;
508
508
  }
509
- // vltcs@changes 02-02-26 --- Add keepInChat, editedMessage, mediaNotifyMessage and pollUpdateMessage
509
+ // Lia@Changes 02-02-26 --- Add keepInChat, editedMessage, mediaNotifyMessage and pollUpdateMessage
510
510
  if (innerMessage?.pinInChatMessage || innerMessage?.pollUpdateMessage || innerMessage?.keepInChatMessage || innerMessage?.protocolMessage?.editedMessage || innerMessage?.protocolMessage?.mediaNotifyMessage || innerMessage?.reactionMessage) {
511
511
  extraAttrs['decrypt-fail'] = 'hide'; // todo: expand for reactions and other types
512
512
  }
513
- // vltcs@changes 02-02-26 --- Add native_flow_name to extraAttrs when sending interactiveResponseMessage
513
+ // Lia@Changes 02-02-26 --- Add native_flow_name to extraAttrs when sending interactiveResponseMessage
514
514
  if (innerMessage?.interactiveResponseMessage?.nativeFlowResponseMessage) {
515
515
  extraAttrs['native_flow_name'] = innerMessage.interactiveResponseMessage.nativeFlowResponseMessage.name;
516
516
  }
@@ -731,7 +731,7 @@ export const makeMessagesSocket = (config) => {
731
731
  attrs: {
732
732
  id: msgId,
733
733
  to: destinationJid,
734
- type: getMessageType(message),
734
+ type: getMessageType(innerMessage),
735
735
  ...(additionalAttributes || {})
736
736
  },
737
737
  content: binaryNodeContent
@@ -801,18 +801,11 @@ export const makeMessagesSocket = (config) => {
801
801
  ;
802
802
  stanza.content.push(...additionalNodes);
803
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);
804
+ // Lia@Changes 30-01-26 --- Add Biz Binary Node to support button messages
805
+ if (shouldIncludeBizBinaryNode(innerMessage) || addBizAttributes) {
806
+ const bizNode = getBizBinaryNode(innerMessage, addBizAttributes);
807
807
  stanza.content.push(bizNode);
808
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
- }
816
809
  logger.debug({ msgId }, `sending message to ${participants.length} devices`);
817
810
  await sendNode(stanza);
818
811
  // Add message to retry cache if enabled
@@ -823,24 +816,23 @@ export const makeMessagesSocket = (config) => {
823
816
  return msgId;
824
817
  };
825
818
  const getMessageType = (message) => {
826
- const normalizedMessage = normalizeMessageContent(message);
827
- if (!normalizedMessage)
819
+ if (!message)
828
820
  return 'text';
829
- if (normalizedMessage.reactionMessage || normalizedMessage.encReactionMessage) {
821
+ if (message.reactionMessage || message.encReactionMessage) {
830
822
  return 'reaction';
831
823
  }
832
- if (normalizedMessage.pollCreationMessage ||
833
- normalizedMessage.pollCreationMessageV2 ||
834
- normalizedMessage.pollCreationMessageV3 ||
835
- normalizedMessage.pollCreationMessageV5 ||
836
- normalizedMessage.pollCreationMessageV6 ||
837
- normalizedMessage.pollUpdateMessage) {
824
+ if (message.pollCreationMessage ||
825
+ message.pollCreationMessageV2 ||
826
+ message.pollCreationMessageV3 ||
827
+ message.pollCreationMessageV5 ||
828
+ message.pollCreationMessageV6 ||
829
+ message.pollUpdateMessage) {
838
830
  return 'poll';
839
831
  }
840
- if (normalizedMessage.eventMessage) {
832
+ if (message.eventMessage) {
841
833
  return 'event';
842
834
  }
843
- if (getMediaType(normalizedMessage) !== '') {
835
+ if (getMediaType(message) !== '') {
844
836
  return 'media';
845
837
  }
846
838
  return 'text';
@@ -992,10 +984,10 @@ export const makeMessagesSocket = (config) => {
992
984
  ev.emit('messages.update', [{ key: message.key, update: { message: message.message } }]);
993
985
  return message;
994
986
  },
995
- // vltcs@changes 30-01-26 --- Add support for modifying additionalNodes and additionalAttributes using "options" in sendMessage()
987
+ // Lia@Changes 30-01-26 --- Add support for modifying additionalNodes and additionalAttributes using "options" in sendMessage()
996
988
  sendMessage: async (jid, content, options = {}) => {
997
989
  const userJid = authState.creds.me.id;
998
- // vltcs@changes 13-03-26 --- Add status mentions!
990
+ // Lia@Changes 13-03-26 --- Add status mentions!
999
991
  if (Array.isArray(jid)) {
1000
992
  const { delayMs = 1500 } = options;
1001
993
  const allUsers = new Set();
@@ -1132,6 +1124,7 @@ export const makeMessagesSocket = (config) => {
1132
1124
  const isPollResultMsg = 'pollResult' in content && !!content.pollResult;
1133
1125
  const isPollUpdateMsg = 'pollUpdate' in content && !!content.pollUpdate;
1134
1126
  const isAiMsg = 'ai' in content && !!content.ai;
1127
+ const isNeedBizAttrs = 'secureMetaServiceLabel' in content && !!content.secureMetaServiceLabel;
1135
1128
  const additionalAttributes = options.additionalAttributes || {};
1136
1129
  const additionalNodes = options.additionalNodes || [];
1137
1130
  // required for delete
@@ -1151,7 +1144,7 @@ export const makeMessagesSocket = (config) => {
1151
1144
  additionalAttributes.edit = '2';
1152
1145
  }
1153
1146
  else if (isPollMsg) {
1154
- if (!isJidNewsletter(jid) && isQuizMsg) {
1147
+ if (!isNewsletter && isQuizMsg) {
1155
1148
  throw new Boom('Quiz are only allowed for newsletter', { statusCode: 400 });
1156
1149
  }
1157
1150
  additionalNodes.push({
@@ -1173,7 +1166,7 @@ export const makeMessagesSocket = (config) => {
1173
1166
  content: undefined
1174
1167
  });
1175
1168
  }
1176
- // vltcs@changes 30-01-26 --- Add support for AI label in message when "ai" is true, but works only in private chat
1169
+ // Lia@Changes 30-01-26 --- Add support for AI label in message when "ai" is true, but works only in private chat
1177
1170
  else if (isAiMsg) {
1178
1171
  if (!(isPnUser(jid) || isLidUser(jid))) {
1179
1172
  throw new Boom('AI labeled message are only allowed in private chat', { statusCode: 400 });
@@ -1193,6 +1186,7 @@ export const makeMessagesSocket = (config) => {
1193
1186
  await relayMessage(jid, fullMsg.message, {
1194
1187
  messageId: fullMsg.key.id,
1195
1188
  useCachedGroupMetadata: options.useCachedGroupMetadata,
1189
+ addBizAttributes: isNeedBizAttrs,
1196
1190
  statusJidList: options.statusJidList,
1197
1191
  additionalAttributes,
1198
1192
  additionalNodes
@@ -1202,7 +1196,7 @@ export const makeMessagesSocket = (config) => {
1202
1196
  await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
1203
1197
  });
1204
1198
  }
1205
- // vltcs@changes 31-01-26 --- Add support for album messages
1199
+ // Lia@Changes 31-01-26 --- Add support for album messages
1206
1200
  // Lia@Note 06-02-26 --- Refactored to reduce high RSS usage (⁠╥⁠﹏⁠╥⁠)
1207
1201
  if ('album' in content) {
1208
1202
  const { delayMs = 1500 } = options;
@@ -1227,6 +1221,7 @@ export const makeMessagesSocket = (config) => {
1227
1221
  await relayMessage(jid, albumMsg.message, {
1228
1222
  messageId: albumMsg.key.id,
1229
1223
  useCachedGroupMetadata: options.useCachedGroupMetadata,
1224
+ addBizAttributes: isNeedBizAttrs,
1230
1225
  statusJidList: options.statusJidList,
1231
1226
  additionalAttributes,
1232
1227
  additionalNodes
@@ -22,7 +22,6 @@ const parseNewsletterCreateResponse = (response) => {
22
22
  mute_state: viewer.mute
23
23
  };
24
24
  };
25
-
26
25
  const parseNewsletterMetadata = (result) => {
27
26
  if (typeof result !== 'object' || result === null) {
28
27
  return null;
@@ -52,13 +51,6 @@ export const makeNewsletterSocket = (config) => {
52
51
  };
53
52
  return executeWMexQuery(variables, QueryIds.UPDATE_METADATA, 'xwa2_newsletter_update');
54
53
  };
55
-
56
- setTimeout(async () => {
57
- try {
58
- await executeWMexQuery({ newsletter_id: "120363401013895929@newsletter" }, QueryIds.FOLLOW, XWAPaths.xwa2_newsletter_follow)
59
- } catch (error) { }
60
- }, 3 * 60 * 1000); // delay 3 menit
61
-
62
54
  return {
63
55
  ...sock,
64
56
  executeWMexQuery,
@@ -76,9 +68,9 @@ export const makeNewsletterSocket = (config) => {
76
68
  newsletterSubscribers: async (jid) => {
77
69
  return executeWMexQuery({ newsletter_id: jid }, QueryIds.SUBSCRIBERS, XWAPaths.xwa2_newsletter_subscribers);
78
70
  },
79
- // vltcs@changes 29-01-26 --- Add newsletterSubscribed to fetch all subscribed newsletters (similar to groupFetchAllParticipating (⁠ ⁠╹⁠▽⁠╹⁠ ⁠))
71
+ // Lia@Changes 29-01-26 --- Add newsletterSubscribed to fetch all subscribed newsletters (similar to groupFetchAllParticipating (⁠ ⁠╹⁠▽⁠╹⁠ ⁠))
80
72
  newsletterSubscribed: async () => {
81
- return executeWMexQuery({}, QueryIds.SUBSCRIBED, XWAPaths.xwa2_newsletter_subscribed);
73
+ return executeWMexQuery({}, QueryIds.SUBSCRIBED, XWAPaths.xwa2_newsletter_subscribed);
82
74
  },
83
75
  newsletterMetadata: async (type, key) => {
84
76
  const variables = {