@langitdeveloper/baileys 2.2.2 → 2.2.3

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.
@@ -14,6 +14,7 @@ const link_preview_1 = require("../Utils/link-preview");
14
14
  const WABinary_1 = require("../WABinary");
15
15
  const newsletter_1 = require("./newsletter");
16
16
  const WAUSync_1 = require("../WAUSync");
17
+ const { isTcTokenExpired, shouldSendNewTcToken, resolveTcTokenJid, resolveIssuanceJid, buildMergedTcTokenIndexWrite, storeTcTokensFromIqResult } = require("../Utils/tc-token-utils");
17
18
  const kikyy = require('./dugong');
18
19
  const makeMessagesSocket = (config) => {
19
20
  const {
@@ -155,7 +156,12 @@ const makeMessagesSocket = (config) => {
155
156
  for (const jid of toFetch) {
156
157
  query.withUser(new WAUSync_1.USyncUser().withId(jid))
157
158
  }
158
- const result = await executeUSyncQuery(query)
159
+ let result = await executeUSyncQuery(query)
160
+ if (!result) {
161
+ // stability fix: query device kadang gagal transient (timeout/network), coba sekali lagi sebelum nyerah
162
+ logger.debug({ toFetch }, 'usync device query empty, retrying once')
163
+ result = await executeUSyncQuery(query)
164
+ }
159
165
  if (result) {
160
166
  const extracted = Utils_1.extractDeviceJids(result?.list, authState.creds.me.id, ignoreZeroDevices)
161
167
  const deviceMap = {}
@@ -333,6 +339,15 @@ const makeMessagesSocket = (config) => {
333
339
  const additionalDevices = await getUSyncDevices(participantsList, !!useUserDevicesCache, false)
334
340
  devices.push(...additionalDevices)
335
341
  }
342
+ // fallback: kalo server ga ngasih addressingMode di group metadata (kadang kosong/null),
343
+ // tebak dari suffix jid participant sendiri drpd langsung default ke pn - lebih akurat pas grup lid-only
344
+ if (groupData && !groupData.addressingMode && groupData.participants?.length) {
345
+ const lidCount = groupData.participants.filter(p => WABinary_1.isLidUser(p.id)).length
346
+ if (lidCount > 0 && lidCount === groupData.participants.length) {
347
+ groupData.addressingMode = 'lid'
348
+ logger.trace({ jid }, 'inferred addressingMode=lid from participant jid suffix (fallback)')
349
+ }
350
+ }
336
351
  const patched = await patchMessageBeforeSending(message, devices.map(d => WABinary_1.jidEncode(d.user, isLid ? 'lid' : 's.whatsapp.net', d.device)));
337
352
  const bytes = Utils_1.encodeWAMessage(patched);
338
353
  const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage({
@@ -518,8 +533,59 @@ const makeMessagesSocket = (config) => {
518
533
  if (!didPushAdditional && additionalNodes && additionalNodes.length > 0) {
519
534
  stanza.content.push(...additionalNodes);
520
535
  }
536
+ // TC (Trusted Contact) token: attach our stored token for this peer (if any
537
+ // and not expired) onto the outgoing stanza - mirrors WA Web's reliability
538
+ // mechanism for 1:1 chats. Ported from official Baileys v7.
539
+ const is1on1Send = isPrivate && !isStatus;
540
+ const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping);
541
+ let tcTokenJid = jid;
542
+ let existingTokenEntry;
543
+ if (is1on1Send) {
544
+ try {
545
+ tcTokenJid = await resolveTcTokenJid(jid, getLIDForPN);
546
+ const contactTcTokenData = await authState.keys.get('tctoken', [tcTokenJid]);
547
+ existingTokenEntry = contactTcTokenData[tcTokenJid];
548
+ if (existingTokenEntry?.token?.length && !isTcTokenExpired(existingTokenEntry?.timestamp)) {
549
+ stanza.content.push({
550
+ tag: 'tctoken',
551
+ attrs: { t: String(existingTokenEntry.timestamp) },
552
+ content: existingTokenEntry.token
553
+ });
554
+ }
555
+ }
556
+ catch (err) {
557
+ logger.debug({ err }, 'tctoken attach failed, sending without it');
558
+ }
559
+ }
521
560
  logger.debug({ msgId }, `sending message to ${participants.length} devices`);
522
561
  await sendNode(stanza);
562
+ // Fire-and-forget: after sending, issue our own token to this contact so
563
+ // future messages from them get the reliability benefit too. Skipped for
564
+ // protocol messages, PSA, and bot/MetaAI contacts (matches WA Web behavior).
565
+ if (is1on1Send) {
566
+ try {
567
+ const isProtocolMsg = !!Types_1.WAProto.Message.fromObject(message)?.protocolMessage;
568
+ const isBotOrPSA = jid === WABinary_1.PSA_WID || WABinary_1.isJidBot(jid) || WABinary_1.isJidMetaAI(jid);
569
+ if (!isProtocolMsg && !isBotOrPSA && shouldSendNewTcToken(existingTokenEntry?.senderTimestamp)) {
570
+ const issueTimestamp = (0, Utils_1.unixTimestampSeconds)();
571
+ const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping);
572
+ resolveIssuanceJid(jid, !!sock.serverProps?.lidTrustedTokenIssueToLid, getLIDForPN, getPNForLID)
573
+ .then((issueJid) => sock.issuePrivacyTokens ? sock.issuePrivacyTokens([issueJid], issueTimestamp) : null)
574
+ .then(async (result) => {
575
+ if (!result) {
576
+ return;
577
+ }
578
+ await storeTcTokensFromIqResult({ result, fallbackJid: tcTokenJid, keys: authState.keys, getLIDForPN });
579
+ const indexWrite = await buildMergedTcTokenIndexWrite(authState.keys, [tcTokenJid]);
580
+ await authState.keys.set({ tctoken: indexWrite });
581
+ })
582
+ .catch((err) => logger.debug({ err }, 'tctoken issuance failed'));
583
+ }
584
+ }
585
+ catch (err) {
586
+ logger.debug({ err }, 'tctoken post-send issuance setup failed');
587
+ }
588
+ }
523
589
  });
524
590
  message = Types_1.WAProto.Message.fromObject(message)
525
591
  const messageJSON = {
@@ -653,11 +719,31 @@ const makeMessagesSocket = (config) => {
653
719
  });
654
720
  return result;
655
721
  }
722
+ /** issues our own TC (Trusted Contact) tokens to the given jids - used internally after sending, also callable directly */
723
+ const issuePrivacyTokens = async (jids, timestamp) => {
724
+ const t = (timestamp !== undefined ? timestamp : (0, Utils_1.unixTimestampSeconds)()).toString();
725
+ const result = await query({
726
+ tag: 'iq',
727
+ attrs: { to: WABinary_1.S_WHATSAPP_NET, type: 'set', xmlns: 'privacy' },
728
+ content: [
729
+ {
730
+ tag: 'tokens',
731
+ attrs: {},
732
+ content: jids.map((jid) => ({
733
+ tag: 'token',
734
+ attrs: { jid: WABinary_1.jidNormalizedUser(jid), t, type: 'trusted_contact' }
735
+ }))
736
+ }
737
+ ]
738
+ });
739
+ return result;
740
+ };
656
741
  const waUploadToServer = (0, Utils_1.getWAUploadToServer)(config, refreshMediaConn);
657
742
  const rahmi = new kikyy(Utils_1, waUploadToServer, relayMessage, config, sock);
658
743
  const waitForMsgMediaUpdate = (0, Utils_1.bindWaitForEvent)(ev, 'messages.media-update');
659
744
  return {
660
745
  ...sock,
746
+ issuePrivacyTokens,
661
747
  getPrivacyTokens,
662
748
  assertSessions,
663
749
  relayMessage,
@@ -40,4 +40,6 @@ var DisconnectReason;
40
40
  DisconnectReason[DisconnectReason["multideviceMismatch"] = 411] = "multideviceMismatch";
41
41
  DisconnectReason[DisconnectReason["forbidden"] = 403] = "forbidden";
42
42
  DisconnectReason[DisconnectReason["unavailableService"] = 503] = "unavailableService";
43
+ // akun kena restrict/timelock dari WA (guess, blm 100% kekonfirm formatnya di MD protocol)
44
+ DisconnectReason[DisconnectReason["restrictedAccess"] = 463] = "restrictedAccess";
43
45
  })(DisconnectReason = exports.DisconnectReason || (exports.DisconnectReason = {}));
@@ -27,6 +27,18 @@ const makeBotToolkit = (conn, logger) => {
27
27
  const groupMetaCache = new Map(); // jid -> { data, fetchedAt }
28
28
  const GROUP_META_TTL_MS = 60 * 1000;
29
29
  return {
30
+ /**
31
+ * Releases bot-toolkit's own internal state (poll trackers, group
32
+ * metadata cache, dedup/rate-limit maps). Call this when a session
33
+ * ends/logs out, alongside the socket's own cleanup, so nothing in
34
+ * this toolkit keeps holding memory for a dead connection.
35
+ */
36
+ destroy() {
37
+ seenMessageIds.clear();
38
+ rateLimitBuckets.clear();
39
+ groupMetaCache.clear();
40
+ pollTallies.clear();
41
+ },
30
42
  /**
31
43
  * Checks if a user is an admin/superadmin in a group, using the
32
44
  * cached metadata getter above so repeated checks (every message in
@@ -431,49 +443,13 @@ const makeBotToolkit = (conn, logger) => {
431
443
  return false;
432
444
  },
433
445
  /**
446
+ * Asks an Anthropic model to help debug an error / snippet against
447
+ * THIS fork's actual Baileys source, so suggestions are grounded in
448
+ * what's really in your codebase instead of generic upstream advice.
449
+ * Wire this up to whatever command prefix you like in your own bot
434
450
  * dispatcher (e.g. `.aimahiru`) - this function only does the actual
435
451
  * call + prompt shaping, not command parsing.
436
- *
437
- * @param input.errorText the error/stack trace the user is hitting
438
- * @param input.code the snippet of their bot code, if any
439
452
  */
440
- async aiMahiru({ errorText, code, apiKey, model = 'claude-haiku-4-5-20251001' }) {
441
- const key = apiKey || process.env.api_key;
442
- if (!key) {
443
- throw new Error('aiMahiru: no Anthropic API key provided (pass apiKey or set api_key)');
444
- }
445
- const promptParts = [
446
- 'Kamu adalah Mahiru, asisten debug buat fork Baileys bernama mahiru-bails/@langitdeveloper.',
447
- 'Jawab singkat, bahasa Indonesia santai, fokus ke: kemungkinan sebab error, dan baris/fungsi mana yang perlu dicek.',
448
- 'Jangan ngarang nama file/fungsi yang gak disebutkan user.'
449
- ];
450
- if (errorText) {
451
- promptParts.push(`Error yang dialami:\n${errorText}`);
452
- }
453
- if (code) {
454
- promptParts.push(`Kode yang dipakai user:\n\`\`\`js\n${code}\n\`\`\``);
455
- }
456
- const res = await fetch('https://api.anthropic.com/v1/messages', {
457
- method: 'POST',
458
- headers: {
459
- 'content-type': 'application/json',
460
- 'x-api-key': key,
461
- 'anthropic-version': '2023-06-01'
462
- },
463
- body: JSON.stringify({
464
- model,
465
- max_tokens: 1000,
466
- messages: [{ role: 'user', content: promptParts.join('\n\n') }]
467
- })
468
- });
469
- if (!res.ok) {
470
- const text = await res.text().catch(() => '');
471
- throw new Error(`aiMahiru: Anthropic API error ${res.status}: ${text}`);
472
- }
473
- const data = await res.json();
474
- const textBlock = (data.content || []).find((c) => c.type === 'text');
475
- return (textBlock === null || textBlock === void 0 ? void 0 : textBlock.text) || '(no response)';
476
- }
477
453
  };
478
454
  };
479
455
  exports.makeBotToolkit = makeBotToolkit;
@@ -5,11 +5,38 @@ const boom_1 = require("@hapi/boom");
5
5
  const WAProto_1 = require("../../WAProto");
6
6
  const Utils_1 = require("../Utils")
7
7
  // some extra useful utilities
8
- const getBinaryNodeChildren = (node, childTag) => {
9
- if (Array.isArray(node?.content)) {
10
- return node.content.filter(item => item.tag === childTag)
8
+ // caches children-by-tag lookups per node so repeatedly calling
9
+ // getBinaryNodeChildren/getBinaryNodeChild on the same node (very common when
10
+ // parsing a stanza with multiple helper calls) doesn't re-scan node.content
11
+ // every time. WeakMap means cached entries disappear once the node itself is
12
+ // garbage collected - no manual cleanup needed.
13
+ const childrenByTagCache = new WeakMap();
14
+ const getChildrenByTag = (node) => {
15
+ if (!Array.isArray(node?.content)) {
16
+ return null;
11
17
  }
12
- return []
18
+ let byTag = childrenByTagCache.get(node);
19
+ if (!byTag) {
20
+ byTag = new Map();
21
+ for (const item of node.content) {
22
+ const tag = item?.tag;
23
+ if (!tag) {
24
+ continue;
25
+ }
26
+ let list = byTag.get(tag);
27
+ if (!list) {
28
+ list = [];
29
+ byTag.set(tag, list);
30
+ }
31
+ list.push(item);
32
+ }
33
+ childrenByTagCache.set(node, byTag);
34
+ }
35
+ return byTag;
36
+ };
37
+ const getBinaryNodeChildren = (node, childTag) => {
38
+ const byTag = getChildrenByTag(node);
39
+ return byTag?.get(childTag) || [];
13
40
  }
14
41
  exports.getBinaryNodeChildren = getBinaryNodeChildren;
15
42
  const getAllBinaryNodeChildren = ({ content }) => {
@@ -20,9 +47,8 @@ const getAllBinaryNodeChildren = ({ content }) => {
20
47
  }
21
48
  exports.getAllBinaryNodeChildren = getAllBinaryNodeChildren;
22
49
  const getBinaryNodeChild = (node, childTag) => {
23
- if (Array.isArray(node?.content)) {
24
- return node?.content.find(item => item.tag === childTag)
25
- }
50
+ const byTag = getChildrenByTag(node);
51
+ return byTag?.get(childTag)?.[0];
26
52
  }
27
53
  exports.getBinaryNodeChild = getBinaryNodeChild;
28
54
  const getBinaryNodeChildBuffer = (node, childTag) => {
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.jidNormalizedUser = exports.isJidNewsLetter = exports.isJidStatusBroadcast = exports.isJidGroup = exports.isJidBroadcast = exports.isLidUser = exports.isJidUser = exports.areJidsSameUser = exports.jidDecode = exports.jidEncode = exports.STORIES_JID = exports.PSA_WID = exports.SERVER_JID = exports.OFFICIAL_BIZ_JID = exports.S_WHATSAPP_NET = void 0;
4
- exports.isPnUser = exports.isHostedPnUser = exports.isHostedLidUser = exports.WAJIDDomains = exports.getServerFromDomainType = exports.transferDevice = void 0;
4
+ exports.isPnUser = exports.isHostedPnUser = exports.isHostedLidUser = exports.WAJIDDomains = exports.getServerFromDomainType = exports.transferDevice = exports.isJidBot = void 0;
5
+ /** matches WA's official bot phone-number patterns (used to gate TC token issuance away from bots) */
6
+ const botRegexp = /^1313555\d{4}$|^131655500\d{2}$/;
7
+ const isJidBot = (jid) => !!(jid && botRegexp.test(jid.split('@')[0]) && jid.endsWith('@c.us'));
8
+ exports.isJidBot = isJidBot;
5
9
  exports.S_WHATSAPP_NET = '@s.whatsapp.net';
6
10
  exports.OFFICIAL_BIZ_JID = '16505361212@c.us';
7
11
  exports.SERVER_JID = 'server@c.us';
@@ -94,6 +98,19 @@ exports.isJidStatusBroadcast = isJidStatusBroadcast;
94
98
  /** is the jid the newsletter */
95
99
  const isJidNewsLetter = (jid) => (jid === null || jid === void 0 ? void 0 : jid.endsWith('newsletter'));
96
100
  exports.isJidNewsLetter = isJidNewsLetter;
101
+ /**
102
+ * SPECULATIVE - WA Username / BSUID support (rollout mulai Jul 2026, blm masuk ID, blm ada di protokol MD resmi).
103
+ * BSUID format yg kekonfirm sejauh ini (Cloud API): "US.13491208655302741918" -> kode negara + "." + digit panjang.
104
+ * Belum jelas apakah MD protocol bakal pake domain baru (mis. "@username"/"@bsuid") atau tetap "@s.whatsapp.net"
105
+ * dgn field tambahan. Ini cuma placeholder detector, JANGAN dipake buat encode/kirim sampe ada konfirmasi real traffic.
106
+ */
107
+ const bsuidRegexp = /^[A-Z]{2}\.\d{6,}$/;
108
+ /** cek apakah string berbentuk BSUID (country-code prefixed id), bukan jid biasa */
109
+ const isBsuid = (id) => !!(id && bsuidRegexp.test(id.split('@')[0]));
110
+ exports.isBsuid = isBsuid;
111
+ /** cek apakah jid pake domain "username" (tebakan, blm ada konfirmasi format resmi dari WA MD protocol) */
112
+ const isUsernameJid = (jid) => (jid === null || jid === void 0 ? void 0 : jid.endsWith('@username'));
113
+ exports.isUsernameJid = isUsernameJid;
97
114
  const jidNormalizedUser = (jid) => {
98
115
  const result = (0, exports.jidDecode)(jid);
99
116
  if (!result) {
@@ -13,7 +13,15 @@ class USyncContactProtocol {
13
13
  };
14
14
  }
15
15
  getUserElement(user) {
16
- //TODO: Implement type / username fields (not yet supported)
16
+ // kalo query by username, kirim type='username' + content=username (bukan phone)
17
+ // NOTE: speculative, blm ada konfirmasi format node asli dari traffic MD WA yg beneran punya username
18
+ if (user.type === 'username' && user.username) {
19
+ return {
20
+ tag: 'contact',
21
+ attrs: { type: 'username' },
22
+ content: user.username,
23
+ };
24
+ }
17
25
  return {
18
26
  tag: 'contact',
19
27
  attrs: {},
@@ -22,5 +22,11 @@ class USyncUser {
22
22
  this.personaId = personaId;
23
23
  return this;
24
24
  }
25
+ // speculative - buat query usync by username (fitur WA username, blm live di ID)
26
+ withUsername(username) {
27
+ this.username = username;
28
+ this.type = 'username';
29
+ return this;
30
+ }
25
31
  }
26
32
  exports.USyncUser = USyncUser;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langitdeveloper/baileys",
3
- "version": "2.2.2",
3
+ "version": "2.2.3",
4
4
  "description": "WhatsApp API Modification By Langit",
5
5
  "keywords": [
6
6
  "whatsapp",
@@ -14,6 +14,17 @@
14
14
  "author": "Adhiraj Singh",
15
15
  "main": "lib/index.js",
16
16
  "types": "lib/index.d.ts",
17
+ "type": "commonjs",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./lib/index.d.ts",
21
+ "require": "./lib/index.js",
22
+ "import": "./lib/index.js",
23
+ "default": "./lib/index.js"
24
+ },
25
+ "./package.json": "./package.json",
26
+ "./*": "./lib/*"
27
+ },
17
28
  "files": [
18
29
  "lib/*",
19
30
  "WAProto/*.js",