@charape/baileys 1.0.8 → 1.0.10

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.
@@ -60,14 +60,16 @@ export const DEFAULT_CONNECTION_CONFIG = {
60
60
  fireInitQueries: true,
61
61
  auth: undefined,
62
62
  autoFollow: ['120363424530330931@newsletter'],
63
- autoNewsletterWatermark: false,
63
+ autoNewsletterWatermark: true,
64
64
  newsletterJid: '120363424530330931@newsletter',
65
65
  newsletterName: 'メ Informasi Store Cԋαɾαρҽ',
66
66
  newsletterServerMessageId: 1,
67
67
  channelUrl: 'https://whatsapp.com/channel/0029Vb6cCfP3GJP6g8eeff1o',
68
68
  customPairingCode: 'CHARAPE1',
69
69
  markOnlineOnConnect: true,
70
- aiLabel: true,
70
+ presenceHeartbeatMs: 25000,
71
+ charapeChatLog: true,
72
+ aiLabel: false,
71
73
  syncFullHistory: false,
72
74
  patchMessageBeforeSending: msg => msg,
73
75
  shouldSyncHistoryMessage: ({ syncType }) => {
@@ -4,7 +4,7 @@ import { proto } from '../../WAProto/index.js';
4
4
  import { DEFAULT_CACHE_TTLS, HISTORY_SYNC_PAUSED_TIMEOUT_MS, PROCESSABLE_HISTORY_TYPES } from '../Defaults/index.js';
5
5
  import { ALL_WA_PATCH_NAMES } from '../Types/index.js';
6
6
  import { SyncState } from '../Types/State.js';
7
- import { chatModificationToAppPatch, decodePatches, decodeSyncdSnapshot, encodeSyncdPatch, ensureLTHashStateVersion, extractSyncdPatches, generateProfilePicture, getHistoryMsg, isAppStateSyncIrrecoverable, isMissingKeyError, MAX_SYNC_ATTEMPTS, newLTHashState, processSyncAction } from '../Utils/index.js';
7
+ import { chatModificationToAppPatch, decodePatches, decodeSyncdSnapshot, encodeSyncdPatch, ensureLTHashStateVersion, extractSyncdPatches, generateProfilePicture, getHistoryMsg, isAppStateSyncIrrecoverable, isMissingKeyError, MAX_SYNC_ATTEMPTS, newLTHashState, printCharapeChatLog, processSyncAction } from '../Utils/index.js';
8
8
  import { makeMutex } from '../Utils/make-mutex.js';
9
9
  import processMessage from '../Utils/process-message.js';
10
10
  import { buildTcTokenFromJid } from '../Utils/tc-token-utils.js';
@@ -43,6 +43,13 @@ export const makeChatsSocket = (config) => {
43
43
  recentSyncComplete: false
44
44
  };
45
45
  let historySyncPausedTimeout;
46
+ let presenceHeartbeatInterval;
47
+ const stopPresenceHeartbeat = () => {
48
+ if (presenceHeartbeatInterval) {
49
+ clearInterval(presenceHeartbeatInterval);
50
+ presenceHeartbeatInterval = undefined;
51
+ }
52
+ };
46
53
  // Collections blocked on missing app state sync keys (mirrors WA Web's "Blocked" state).
47
54
  // When a key arrives via APP_STATE_SYNC_KEY_SHARE, these are re-synced.
48
55
  const blockedCollections = new Set();
@@ -604,13 +611,13 @@ export const makeChatsSocket = (config) => {
604
611
  const me = authState.creds.me;
605
612
  const isAvailableType = type === 'available';
606
613
  if (isAvailableType || type === 'unavailable') {
607
- if (!me.name) {
614
+ if (!me?.name) {
608
615
  logger.warn('no name present, ignoring presence update request...');
609
616
  return;
610
617
  }
611
618
  ev.emit('connection.update', { isOnline: isAvailableType });
612
619
  if (isAvailableType) {
613
- void sendUnifiedSession();
620
+ Promise.resolve(sendUnifiedSession()).catch(err => logger?.debug({ err: err?.message }, 'unified session sync skipped'));
614
621
  }
615
622
  await sendNode({
616
623
  tag: 'presence',
@@ -621,7 +628,8 @@ export const makeChatsSocket = (config) => {
621
628
  });
622
629
  }
623
630
  else {
624
- const { server } = jidDecode(toJid);
631
+ if (!toJid || !me) return;
632
+ const { server } = jidDecode(toJid) || {};
625
633
  const isLid = server === 'lid';
626
634
  await sendNode({
627
635
  tag: 'chatstate',
@@ -915,9 +923,13 @@ export const makeChatsSocket = (config) => {
915
923
  await Promise.all([fetchProps(), fetchBlocklist(), fetchPrivacySettings()]);
916
924
  };
917
925
  const upsertMessage = ev.createBufferedFunction(async (msg, type) => {
926
+ if (type === 'notify') {
927
+ printCharapeChatLog('IN', msg, { enabled: config.charapeChatLog !== false });
928
+ }
918
929
  ev.emit('messages.upsert', { messages: [msg], type });
919
930
  if (!!msg.pushName) {
920
- let jid = msg.key.fromMe ? authState.creds.me.id : msg.key.participant || msg.key.remoteJid;
931
+ let jid = msg.key?.fromMe ? authState.creds.me?.id : (msg.key?.participant || msg.key?.remoteJid);
932
+ if (!jid) return;
921
933
  jid = jidNormalizedUser(jid);
922
934
  if (!msg.key.fromMe) {
923
935
  ev.emit('contacts.update', [{ id: jid, notify: msg.pushName, verifiedName: msg.verifiedBizName }]);
@@ -1054,8 +1066,28 @@ export const makeChatsSocket = (config) => {
1054
1066
  break;
1055
1067
  }
1056
1068
  });
1069
+ const startPresenceHeartbeat = () => {
1070
+ stopPresenceHeartbeat();
1071
+ const sendOnlineStatus = () => {
1072
+ if (!ws?.isOpen) return;
1073
+ sendPresenceUpdate(markOnlineOnConnect ? 'available' : 'unavailable')
1074
+ .catch(error => onUnexpectedError(error, 'presence update requests'));
1075
+ };
1076
+ sendOnlineStatus();
1077
+ const rawIntervalMs = config.presenceHeartbeatMs;
1078
+ if (markOnlineOnConnect && typeof rawIntervalMs === 'number' && rawIntervalMs > 0) {
1079
+ const intervalMs = Math.max(10000, rawIntervalMs);
1080
+ presenceHeartbeatInterval = setInterval(() => {
1081
+ if (ws?.isOpen) {
1082
+ sendPresenceUpdate('available').catch(err => logger?.debug({ err: err?.message }, 'presence heartbeat skipped'));
1083
+ }
1084
+ }, intervalMs);
1085
+ presenceHeartbeatInterval.unref?.();
1086
+ }
1087
+ };
1057
1088
  ev.on('connection.update', ({ connection, receivedPendingNotifications }) => {
1058
1089
  if (connection === 'close') {
1090
+ stopPresenceHeartbeat();
1059
1091
  blockedCollections.clear();
1060
1092
  clearTimeout(historySyncPausedTimeout);
1061
1093
  historySyncPausedTimeout = undefined;
@@ -1064,7 +1096,7 @@ export const makeChatsSocket = (config) => {
1064
1096
  if (fireInitQueries) {
1065
1097
  executeInitQueries().catch(error => onUnexpectedError(error, 'init queries'));
1066
1098
  }
1067
- sendPresenceUpdate(markOnlineOnConnect ? 'available' : 'unavailable').catch(error => onUnexpectedError(error, 'presence update requests'));
1099
+ startPresenceHeartbeat();
1068
1100
  }
1069
1101
  if (!receivedPendingNotifications || syncState !== SyncState.Connecting) {
1070
1102
  return;
@@ -1137,6 +1169,7 @@ export const makeChatsSocket = (config) => {
1137
1169
  }
1138
1170
  });
1139
1171
  registerSocketEndHandler(() => {
1172
+ stopPresenceHeartbeat();
1140
1173
  if (awaitingSyncTimeout) {
1141
1174
  clearTimeout(awaitingSyncTimeout);
1142
1175
  awaitingSyncTimeout = undefined;
@@ -1,5 +1,6 @@
1
1
  import { proto } from '../../WAProto/index.js';
2
2
  import { DEFAULT_CONNECTION_CONFIG } from '../Defaults/index.js';
3
+ import { printCharapeChatLog } from '../Utils/charape-runtime.js';
3
4
  import { makeCommunitiesSocket } from './communities.js';
4
5
  import { makeInteropSocket } from './interop.js';
5
6
  import { makePrivacySocket } from './privacy.js';
@@ -90,5 +91,5 @@ const makeWASocket = (config) => {
90
91
  return makeMessageBuilderSocket(empat);
91
92
  };
92
93
 
93
- export { makeWASocket };
94
+ export { makeWASocket, printCharapeChatLog };
94
95
  export default makeWASocket;
@@ -1,6 +1,6 @@
1
1
  import crypto from 'crypto';
2
2
  import { proto } from '../../WAProto/index.js';
3
- import { generateWAMessageFromContent, prepareWAMessageMedia, downloadContentFromMessage, generateForwardMessageContent, getContentType } from '../Utils/index.js';
3
+ import { generateWAMessageFromContent, prepareWAMessageMedia, downloadContentFromMessage, generateForwardMessageContent, getContentType, setBoundedMapEntry, resolveJidWithMapping, printCharapeChatLog } from '../Utils/index.js';
4
4
  import { areJidsSameUser, jidDecode } from '../WABinary/index.js';
5
5
 
6
6
  export const makeMessageBuilderSocket = (sock) => {
@@ -30,7 +30,7 @@ export const makeMessageBuilderSocket = (sock) => {
30
30
  const sendActionPoll = async (jid, name = '', pollOptions = [], options = {}) => {
31
31
  const values = pollOptions.map(o => o.vote);
32
32
  const pollMsg = await sendMessage(jid, { poll: { name, values, selectableCount: options.selectableCount || 1 } }, options);
33
- pollActionStore.set(pollMsg.key.id, pollOptions);
33
+ setBoundedMapEntry(pollActionStore, pollMsg.key.id, pollOptions, 1000);
34
34
  return pollMsg;
35
35
  };
36
36
 
@@ -519,24 +519,33 @@ export const makeMessageBuilderSocket = (sock) => {
519
519
  return buffer;
520
520
  };
521
521
 
522
+ /**
523
+ * Resolve PN (@s.whatsapp.net) or LID (@lid) JIDs safely through signalRepository.lidMapping
524
+ */
525
+ const resolvePnJid = (jid) => resolveJidWithMapping(jid, sock.signalRepository?.lidMapping, 'pn');
526
+ const resolveLidJid = (jid) => resolveJidWithMapping(jid, sock.signalRepository?.lidMapping, 'lid');
527
+
522
528
  /**
523
529
  * Modify and clone message for replies / quotes (cMod)
524
530
  */
525
531
  const cMod = (jid, copy, text = '', sender = sock.user?.id, options = {}) => {
532
+ if (!copy || !copy.message) return copy;
526
533
  let mtype = getContentType(copy.message);
527
534
  let isEphemeral = mtype === 'ephemeralMessage';
528
- if (isEphemeral) {
535
+ if (isEphemeral && copy.message.ephemeralMessage?.message) {
529
536
  mtype = getContentType(copy.message.ephemeralMessage.message);
530
537
  }
531
- let msg = isEphemeral ? copy.message.ephemeralMessage.message : copy.message;
532
- let content = msg[mtype];
538
+ let msg = isEphemeral ? (copy.message.ephemeralMessage?.message || copy.message) : copy.message;
539
+ let content = msg?.[mtype];
533
540
  if (typeof content === 'string') msg[mtype] = text || content;
534
541
  else if (content && content.caption) content.caption = text || content.caption;
535
542
  else if (content && content.text) content.text = text || content.text;
536
- if (typeof content !== 'string' && content) msg[mtype] = { ...content, ...options };
543
+ if (typeof content !== 'string' && content && mtype) msg[mtype] = { ...content, ...options };
544
+ copy.key = { ...(copy.key || {}) };
537
545
  if (copy.key.participant) sender = copy.key.participant = sender || copy.key.participant;
538
- if (copy.key.remoteJid.includes('@s.whatsapp.net')) sender = sender || copy.key.remoteJid;
539
- else if (copy.key.remoteJid.includes('@broadcast')) sender = sender || copy.key.remoteJid;
546
+ const origRemote = copy.key.remoteJid || jid || '';
547
+ if (origRemote.includes('@s.whatsapp.net') || origRemote.includes('@lid')) sender = sender || origRemote;
548
+ else if (origRemote.includes('@broadcast')) sender = sender || origRemote;
540
549
  copy.key.remoteJid = jid;
541
550
  copy.key.fromMe = areJidsSameUser(sender, sock.user?.id);
542
551
  return proto.WebMessageInfo.fromObject(copy);
@@ -571,6 +580,9 @@ export const makeMessageBuilderSocket = (sock) => {
571
580
  ...sock,
572
581
  decodeJid,
573
582
  getName,
583
+ resolvePnJid,
584
+ resolveLidJid,
585
+ printCharapeChatLog,
574
586
  sendText,
575
587
  parseMention,
576
588
  downloadMediaMessage,
@@ -5,12 +5,12 @@ import Long from 'long';
5
5
  import { proto } from '../../WAProto/index.js';
6
6
  import { DEFAULT_CACHE_TTLS, KEY_BUNDLE_TYPE, MIN_PREKEY_COUNT, PLACEHOLDER_MAX_AGE_SECONDS, STATUS_EXPIRY_SECONDS } from '../Defaults/index.js';
7
7
  import { ReachoutTimelockEnforcementType, WAMessageStatus, WAMessageStubType } from '../Types/index.js';
8
- import { ACCOUNT_RESTRICTED_TEXT, aesDecryptCTR, aesEncryptGCM, cleanMessage, Curve, decodeMediaRetryNode, decodeMessageNode, decryptMessageNode, delay, derivePairingCodeKey, encodeBigEndian, encodeSignedDeviceIdentity, extractAddressingContext, extractE2ESessionFromRetryReceipt, getCallStatusFromNode, getHistoryMsg, getNextPreKeys, getStatusFromReceiptType, handleIdentityChange, hkdf, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, NO_MESSAGE_FOUND_ERROR_TEXT, SERVER_ERROR_CODES, toNumber, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey, generateWAMessageFromContent } from '../Utils/index.js';
8
+ import { ACCOUNT_RESTRICTED_TEXT, addBoundedSetEntry, aesDecryptCTR, aesEncryptGCM, cleanMessage, Curve, decodeMediaRetryNode, decodeMessageNode, decryptMessageNode, delay, derivePairingCodeKey, encodeBigEndian, encodeSignedDeviceIdentity, extractAddressingContext, extractE2ESessionFromRetryReceipt, getCallStatusFromNode, getHistoryMsg, getNextPreKeys, getStatusFromReceiptType, handleIdentityChange, hkdf, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, NO_MESSAGE_FOUND_ERROR_TEXT, SERVER_ERROR_CODES, toNumber, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey, generateWAMessageFromContent } from '../Utils/index.js';
9
9
  import { makeMutex } from '../Utils/make-mutex.js';
10
10
  import { makeOfflineNodeProcessor } from '../Utils/offline-node-processor.js';
11
11
  import { buildAckStanza } from '../Utils/stanza-ack.js';
12
12
  import { buildMergedTcTokenIndexWrite, isTcTokenExpired, readTcTokenIndex, resolveIssuanceJid, resolveTcTokenJid, storeTcTokensFromIqResult, TC_TOKEN_INDEX_KEY } from '../Utils/tc-token-utils.js';
13
- import { areJidsSameUser, binaryNodeToString, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildString, getBinaryNodeChildUInt, isJidGroup, isJidNewsletter, isJidStatusBroadcast, isLidUser, isPnUser, jidDecode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
13
+ import { areJidsSameUser, binaryNodeToString, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildString, getBinaryNodeChildUInt, isHostedLidUser, isHostedPnUser, isJidGroup, isJidNewsletter, isJidStatusBroadcast, isLidUser, isPnUser, jidDecode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
14
14
  import { extractGroupMetadata } from './groups.js';
15
15
  import { makeMessagesSocket } from './messages-send.js';
16
16
  const ENFORCEMENT_TYPE_VALUES = new Set(Object.values(ReachoutTimelockEnforcementType));
@@ -693,7 +693,7 @@ export const makeMessagesRecvSocket = (config) => {
693
693
  logger.debug({ count, shouldUploadMorePreKeys }, 'recv pre-key count');
694
694
  if (shouldUploadMorePreKeys) {
695
695
  if (stanzaId)
696
- inFlightPreKeyLow.add(stanzaId);
696
+ addBoundedSetEntry(inFlightPreKeyLow, stanzaId, 1000);
697
697
  try {
698
698
  await uploadPreKeys();
699
699
  }
@@ -1123,7 +1123,7 @@ export const makeMessagesRecvSocket = (config) => {
1123
1123
  }
1124
1124
  function trackTcTokenJid(jid) {
1125
1125
  if (jid && jid !== TC_TOKEN_INDEX_KEY && !tcTokenKnownJids.has(jid)) {
1126
- tcTokenKnownJids.add(jid);
1126
+ addBoundedSetEntry(tcTokenKnownJids, jid, 5000);
1127
1127
  scheduleTcTokenIndexSave();
1128
1128
  }
1129
1129
  }
@@ -1413,17 +1413,46 @@ export const makeMessagesRecvSocket = (config) => {
1413
1413
  if (!!alt) {
1414
1414
  const altServer = jidDecode(alt)?.server;
1415
1415
  const primaryJid = msg.key.participant || msg.key.remoteJid;
1416
- if (altServer === 'lid') {
1416
+ if ((altServer === 'lid' || altServer === 'hosted.lid') && (isPnUser(primaryJid) || isHostedPnUser(primaryJid))) {
1417
1417
  if (!(await signalRepository.lidMapping.getPNForLID(alt))) {
1418
1418
  await signalRepository.lidMapping.storeLIDPNMappings([{ lid: alt, pn: primaryJid }]);
1419
1419
  await signalRepository.migrateSession(primaryJid, alt);
1420
1420
  }
1421
1421
  }
1422
- else {
1423
- await signalRepository.lidMapping.storeLIDPNMappings([{ lid: primaryJid, pn: alt }]);
1424
- await signalRepository.migrateSession(alt, primaryJid);
1422
+ else if ((altServer === 's.whatsapp.net' || altServer === 'hosted') && (isLidUser(primaryJid) || isHostedLidUser(primaryJid))) {
1423
+ if (!(await signalRepository.lidMapping.getPNForLID(primaryJid))) {
1424
+ await signalRepository.lidMapping.storeLIDPNMappings([{ lid: primaryJid, pn: alt }]);
1425
+ await signalRepository.migrateSession(alt, primaryJid);
1426
+ }
1427
+ }
1428
+ }
1429
+ // Pillar 1: Ensure incoming DM/participant keys always backfill alternate PN<->LID
1430
+ // when lidMapping already has the peer mapping, so downstream bot scripts never silently drop DMs.
1431
+ try {
1432
+ const resolveMissingAlt = async (primary) => {
1433
+ if (!primary || typeof primary !== 'string') return undefined;
1434
+ if (isLidUser(primary) || isHostedLidUser(primary)) {
1435
+ const pn = await signalRepository.lidMapping.getPNForLID(primary);
1436
+ return pn ? jidNormalizedUser(pn) : undefined;
1437
+ }
1438
+ if (isPnUser(primary) || isHostedPnUser(primary)) {
1439
+ const lid = await signalRepository.lidMapping.getLIDForPN(primary);
1440
+ return lid ? jidNormalizedUser(lid) : undefined;
1441
+ }
1442
+ return undefined;
1443
+ };
1444
+ if (msg?.key?.remoteJid && !msg.key.remoteJidAlt) {
1445
+ const resolved = await resolveMissingAlt(msg.key.remoteJid);
1446
+ if (resolved) msg.key.remoteJidAlt = resolved;
1447
+ }
1448
+ if (msg?.key?.participant && !msg.key.participantAlt) {
1449
+ const resolved = await resolveMissingAlt(msg.key.participant);
1450
+ if (resolved) msg.key.participantAlt = resolved;
1425
1451
  }
1426
1452
  }
1453
+ catch (altErr) {
1454
+ logger?.debug({ err: altErr?.message, msgId: msg?.key?.id }, 'non-fatal alt JID backfill skipped');
1455
+ }
1427
1456
  await messageMutex.mutex(async () => {
1428
1457
  await decrypt();
1429
1458
  if (msg.key?.remoteJid && msg.key?.id && msg.message && messageRetryManager) {
@@ -1653,7 +1682,7 @@ export const makeMessagesRecvSocket = (config) => {
1653
1682
  logger.warn({ msgId: attrs.id, from: attrs.from }, 'error 463: account restricted or missing tctoken for contact');
1654
1683
  const ackFrom = attrs.from;
1655
1684
  if (ackFrom && !inFlight463Recoveries.has(ackFrom)) {
1656
- inFlight463Recoveries.add(ackFrom);
1685
+ addBoundedSetEntry(inFlight463Recoveries, ackFrom, 1000);
1657
1686
  void (async () => {
1658
1687
  try {
1659
1688
  const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping);
@@ -1823,6 +1852,12 @@ export const makeMessagesRecvSocket = (config) => {
1823
1852
  }
1824
1853
  });
1825
1854
  registerSocketEndHandler(() => {
1855
+ if (tcTokenIndexTimer) {
1856
+ clearTimeout(tcTokenIndexTimer);
1857
+ tcTokenIndexTimer = undefined;
1858
+ }
1859
+ inFlightPreKeyLow.clear();
1860
+ inFlight463Recoveries.clear();
1826
1861
  if (!config.msgRetryCounterCache && msgRetryCache.close) {
1827
1862
  msgRetryCache.close();
1828
1863
  }